← Blog

Designing a Risk Taxonomy That Survives Contact With Reality

I got two risks into seeding real data before the taxonomy broke. The second one was a payment processing failure, and I couldn't decide where it belonged. Operational, because a process failed? Financial, because it hits revenue directly? Both answers felt half right, which meant the taxonomy was wrong, not the risk.

That's the fragmentation problem showing up in miniature. A register that can't consistently answer what kind of risk this is doesn't replace the five disconnected spreadsheets. It just becomes a sixth one with better styling.

Category, Subcategory, Type

The model that survived has three levels, not two and not five. A Category (Operational, Financial, People, Technology, Legal/Regulatory & Compliance) breaks into Subcategories, which break into Types. Every risk, incident, and product risk in the system points to a Type, not a Category. The category is just where it rolls up for reporting.

1 Category: Operational Risk. Owned by ops leadership, sets the dashboard colour.
2 Subcategory: Business Process Risk. Groups related failure modes.
3 Type: Internal Fraud & Misconduct. Rated CRITICAL, the level every risk actually attaches to.

Five categories, not three or eight, because each one maps to a single owner. Fewer categories and a payment processing failure has to live somewhere it doesn't quite fit, next to risks nobody on that team actually owns. More categories and you're back to a taxonomy nobody can hold in their head, which defeats the point of a shared register.

Every level carries both a name and a code. Internal Fraud & Misconduct is also OPS-1.1-002. The name is what people read. The code is what reports, exports, and historical scores key off. Rename a category next year and every risk referencing OPS-1.1-002 still resolves correctly, because nothing downstream depends on the label.

Admin-configurable, not code-configurable

Risk managers can add a new Type or Subcategory from the admin panel, no deploy required. But nothing in the taxonomy ever gets deleted, only toggled inactive. Every risk and incident already logged points to its category and type by foreign key, so removing one outright would either orphan history or cascade delete it. Toggling off just stops it from appearing in new-risk forms while every past record stays intact.

What I got wrong

I'd been calling this a versioned taxonomy in my own planning notes, and that's only half true. The active and inactive toggle is a soft form of versioning, but there's no real history behind it. No record of who renamed a category or when a type's materiality rating changed. There's a generic audit log table in the schema built for exactly this: entity type, old values, new values, who, when. It's already wired up elsewhere in the app. It just isn't wired up to taxonomy edits yet. Right now, if someone edits a category definition, the previous version is simply gone. That's a real gap, not a hypothetical one, and it's next on the list.

Where it stands

Five categories, admin-editable subcategories and types underneath, stable codes that don't break under renaming, and dashboards that group and colour by category automatically. What it doesn't have yet is a real change history. I know what the taxonomy looks like today, not what it looked like three months ago.

Next up: why risks and incidents don't map to categories one to one at all. They map many to many, and that turned out to be the harder schema decision.

If you've solved the versioned taxonomy without a full audit system problem before, I'd like to hear how.