KovaRisk: When the Interface Knew More Than the System ──────────────────────────────────────────...KovaRisk: When the Interface Knew More Than the System ──────────────────────────────────────────...
The network for creativity
Join 1.25M professional creatives like you
Connect with clients, get discovered, and run your business 100% commission-free
Creatives on Contra have earned over $150M and we are just getting started
KovaRisk: When the Interface Knew More Than the System
──────────────────────────────────────────
The Expert in the Room
Compliance officers don't struggle to understand risk. They struggle to act on it fast enough.
The team behind KovaRisk understood this precisely. They had spent years inside financial institutions watching the same dysfunction repeat: alerts buried in spreadsheets, investigations tracked in email threads, audit trails reconstructed after the fact. They knew what the interface needed to feel like because they'd lived with the one that didn't.
So they built it. Fast. Exactly as they'd imagined it.
What emerged was sharp: a risk monitoring dashboard with filterable alert feeds, entity profiles with 12-month risk trajectories, a rule engine with toggle controls, and an audit log that felt immutable. The scenario switcher let compliance teams stress-test different alert load states. The side panel made investigations feel contained and intentional.
It looked like a system that had survived production.
It hadn't been asked to yet.
──────────────────────────────────────────
What Existed Was a Strong Interface - Not a System
Every alert in KovaRisk was generated at startup. Every risk score was computed by a random seed function. Every status change - Investigating, Resolved, Escalated - lived in component state. Every timeline event was appended to an in-memory array. Every rule toggle disappeared on refresh.
The audit log recorded nothing. The export downloaded a snapshot of what React was holding at that moment. The entity risk history was a curve drawn from a formula, not a record.
The logic was there. But it had nowhere to live.
A compliance officer investigating a high-risk wire transfer would open the side panel, read the plain-English rule explanation, mark the alert as Investigating, add an internal note - and lose every one of those actions the moment they refreshed the browser. No colleague could see what they'd done. No regulator could verify it had happened.
In financial compliance, that's not a UX problem. It's a liability.
The prototype validated the workflow brilliantly. It exposed exactly how a compliance team would move through their day. But three things were missing: a source of truth, a coordination layer, and a trail that could be audited under pressure.
──────────────────────────────────────────
They Didn't Need More Features - They Needed a System Behind the Interface
The team came with a clear idea and a working prototype. What they needed was the architecture that made the prototype a product - the layer that turned interface actions into durable facts.
Not a rebuild. A foundation.
──────────────────────────────────────────
The Layer That Made It Dependable
Data Models: Giving State a Home
The first thing to reconstruct was where the data should actually live.
KovaRisk's frontend implied a clear schema - alerts, entities, rules, audit events - but none of it persisted. The production system needed a PostgreSQL core with five primary entities:
• Alert — with foreign keys to Entity, Rule, Transaction, and a JSONB timeline column for ordered event history • Entity — with risk tier, jurisdiction metadata, and a one-to-many relationship to RiskScore snapshots • Rule — with active/disabled state, trigger thresholds, false-positive tracking, and a versioning mechanism so changes to rules didn't retroactively alter historical alerts • AuditEvent — append-only, with actor ID, action type, target reference, and a server-generated timestamp that clients cannot modify • InternalNote — owned by an alert, with authorship and a soft-delete flag to preserve compliance integrity
Every status change, note, escalation, and flag the UI handled ephemerally became a write to this schema.
The Alert Generation Engine: Replacing the Seed Function
In the prototype, 85 alerts appeared because a loop ran 85 times at startup.
In production, alerts are the output of a Transaction Monitoring Service - a background process that runs continuously against incoming transaction streams.
This service:
• Evaluates each transaction against every active Rule definition • Computes a risk score using rule weights, entity risk tier, jurisdiction flags, and behavioral baselines • Creates an Alert record only when a threshold is breached • Emits an event to a notification queue for high-risk triggers
The rule engine the UI let users toggle wasn't decorative. Each rule mapped to an evaluation function in the monitoring service. Disabling a rule didn't just grey out a card - it removed it from the active evaluation set. Re-enabling it didn't retroactively generate alerts it would have caught; it resumed from the point of activation. That distinction mattered for regulatory defensibility.
──────────────────────────────────────────
Async Workflows: The Operations the UI Implied But Couldn't Sustain
Several interactions in the prototype implied workflows that couldn't complete synchronously.
Escalation - When an alert was escalated, the UI changed a status badge. In production, escalation triggers a queue job that: notifies the senior compliance officer via a configured channel, creates a case record linking the alert, and starts a response SLA timer. The UI reflects the outcome - it doesn't produce it.
Scheduled Screening - The Sanctions Screening Match rule in the prototype was static. In production, it's a nightly job that re-screens all active entities against updated OFAC, EU, and UN sanctions lists - generating new alerts if a previously clean entity now appears. The results feed back into the alert pipeline.
Report Export - The dashboard's Export Report button downloaded a text file of whatever React was holding in memory. In production, report generation is an async job: the user requests the report, the job runs server-side against the live database, and a download link is returned when ready. The content is a verifiable, timestamped record - not a UI snapshot.
──────────────────────────────────────────
The Audit Log: From Feed to Fact
The prototype's audit log was populated by a generateAuditLog function. It looked comprehensive and immutable. It was neither.
Production audit events are written by the API layer on every state-modifying operation - before the response is returned to the client. The table is append-only. No update operations are permitted on AuditEvent records. Timestamps are server-generated in UTC and stored with full precision. Actor identity comes from the authenticated session, not from a string the client sends.
The audit log the interface displayed was a simulation of accountability. The production version is the accountability.
──────────────────────────────────────────
Tech Stack 1. Frontend: React + Vite, React Router v6, Recharts, React Context + local state, TanStack Query 2. Backend: Node.js + TypeScript, Fastify, Prisma, PostgreSQL, Redis, BullMQ, Passport.js + express-session 3. Infrastructure: AWS ECS / Railway / Render, S3, AWS Secrets Manager / Doppler, Sentry + Datadog, GitHub Actions 4. External Integrations: OFAC / ComplyAdvantage, Refinitiv World-Check, SendGrid / SMTP, Webhooks 
──────────────────────────────────────────
From Interface to System
The prototype answered the right questions. It proved the workflow was sound, the information hierarchy was correct, and the alert investigation pattern worked the way compliance officers needed it to.
What it couldn't answer was: what happens when two investigators open the same alert simultaneously? What happens when a rule change needs to take effect immediately across 200 pending alerts? What happens when a regulator asks for every action taken on a specific entity over the past 18 months?
Those questions don't live in the interface. They live in the system.
KovaRisk's interface was always strong. What it needed was the architecture to make it real - persistent, coordinated, auditable, and defensible under scrutiny.
The logic existed from the beginning. We gave it somewhere to live.
Post image
Back to feed
The network for creativity
Join 1.25M professional creatives like you
Connect with clients, get discovered, and run your business 100% commission-free
Creatives on Contra have earned over $150M and we are just getting started