Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Report a fund valuation breach

A GOV.UK-standard prototype for a regulatory notification service

When a fund's net asset value is calculated wrongly, someone has to decide fast whether the error is material. This prototype takes that judgement off the user.

Enter 0.85 at the error-percentage question for the material route, or 0.12 for the non-material route.

Live prototype Design notes

GOV.UK Prototype Kit GOV.UK Frontend Node Nunjucks

WCAG 2.2 Pages Routes Licence

Important

This is a prototype, not a real service. It is not affiliated with the FCA or any regulator, and it does not submit data anywhere. Fund names, firm names and reference numbers are invented. Built to demonstrate service design and interaction patterns.


Contents


The problem

A UK authorised fund publishes a price. If that price was wrong — a stale asset valuation, a fee applied twice, a deal struck at the wrong valuation point — the manager has to work out how serious it is.

That single judgement changes everything downstream:

If the error is material If it is not
Notify the regulator within 5 business days Record it in the breaches register
Pay redress to affected investors Correct the affected valuation points
Evidence root cause and control changes Tell the depositary

In practice the decision often falls to whoever spotted the error first — frequently an operations analyst at a third-party administrator rather than a compliance specialist — working under time pressure, against guidance they may not have read that week.

It is a judgement with a defined numerical threshold. That makes it exactly the kind of judgement a service should make on the user's behalf.


The service journey

Flowchart of the service journey: start page, fund reference number, confirm fund details, type of breach, breach dates, error value as a percentage of NAV, then a decision node splitting into a material route and a non-material route, rejoining at investors affected, check your answers and confirmation.

Twelve pages. One question per page. The journey splits once, on a value the service calculates rather than a question it asks.

# Page Purpose
1 / Start page — who can use it, what you need, the 5-day deadline
2 /reference-number Fund product reference number (PRN)
3 /fund-details Confirm pre-held register data — catches a wrong PRN early
4 /breach-type Pricing error, late trade, asset mispricing, fee error, box management
5 /breach-dates First and last affected valuation points, plus identification date
6 /breach-value Error as a percentage of NAV, and monetary value
branch Materiality calculated in app/routes.js
7a /material-breach Obligations, task list, deadline explanation
7b /non-material-breach What still applies, prospectus warning
8 /investor-impact Investors affected; redress and root cause on the material route only
9 /check-answers Declaration before submission
10 /confirmation Reference number and route-specific next steps
11 /design-notes Design rationale, patterns, what to test next
12 /start-again Clears the session for repeat demos

The materiality decision

This is the point of the prototype.

Two-column comparison. Left, headed 'Don't ask this': a radio question reading 'Is this a material breach?' with three objections — pushes a regulatory judgement onto the user, inconsistent answers across firms, hides the reasoning. Right, headed 'Ask this instead': a numeric field for error as a percentage of net asset value, with three benefits — same assessment every time, data usable for supervision, service explains the outcome. Below, a number line from 0 to 1 percent with the FCA materiality threshold marked at 0.5 percent.

The obvious question to ask is "Is this a material breach?" with a yes/no radio. That would be a mistake:

  • it asks the user to interpret regulatory guidance under time pressure
  • it produces inconsistent answers across firms, making the collected data useless for supervision
  • it hides the reasoning, so nobody can audit how the conclusion was reached

Instead the service asks for the error as a percentage of net asset value, applies the 0.5% threshold itself, and then tells the user which route they are on and why — in one sentence, before they commit to a longer journey.

Two consequences worth noting:

  1. Every notification is assessed identically. The regulator gets comparable data rather than a field full of firm-specific interpretation.
  2. The non-material route still explains what to do. "Not material" is not "nothing to do" — the record, correction and depositary notification all still apply. A service that just said "you don't need to report this" would be actively misleading.

There is also a warning on the non-material route that a fund's prospectus can commit it to a stricter threshold than the guidance, which then overrides the default.

The logic

Implemented in app/routes.js as one commented function:

const MATERIALITY_THRESHOLD = 0.5

function parsePercent (raw) {
  if (raw === undefined || raw === null) return null
  const cleaned = String(raw).replace(/[%,\s]/g, '')
  if (cleaned === '') return null
  const value = Number(cleaned)
  return Number.isFinite(value) ? value : null
}

router.post('/materiality', (req, res) => {
  const percent = parsePercent(req.session.data.errorPercent)

  if (percent === null) {
    // No usable figure — send the user back rather than guess a route.
    return res.redirect('/breach-value')
  }

  req.session.data.errorPercentParsed = percent
  req.session.data.isMaterial = percent >= MATERIALITY_THRESHOLD ? 'yes' : 'no'

  return res.redirect(
    req.session.data.isMaterial === 'yes' ? '/material-breach' : '/non-material-breach'
  )
})

Three deliberate details:

  • Forgiving parsing. These figures get pasted out of spreadsheets, so "0.75 %", "1,200" and stray whitespace all parse. Rejecting a valid number because of a trailing percent sign would be a self-inflicted wound.
  • Decided once, stored once. The outcome is written to the session so later pages and check-answers cannot disagree with each other.
  • No guessing. Unusable input returns the user to the question rather than defaulting to a route. A live service would render an error summary here.

How it is built

Four stacked layers. Views: start, question pages, branch pages, check answers, confirmation, design notes. Routing: routes.js parsing the percentage, applying the 0.5 percent threshold and storing the decision. Session: prn, breachType, errorPercent, isMaterial. Framework: GOV.UK Prototype Kit 13, GOV.UK Frontend 6, Nunjucks, Express. A side panel lists the components used.
Layer What it does
Views Nunjucks templates, one per page, extending a shared GOV.UK-branded layout
Routing A single POST /materiality handler holding the branch logic
Session Prototype Kit session data — prn, breachType, errorPercent, isMaterial
Framework GOV.UK Prototype Kit 13.20 on Express, GOV.UK Frontend 6.4 components

Components used: input · radios · checkboxes · date-input · summary-list · task-list · notification-banner · warning-text · panel · details · character-count · table · tag · inset-text · back-link · button


Screens

Start page
GOV.UK start page for the service, listing who can use it and what information is needed, with a warning that material breaches must be reported within 5 business days, and a green Start now button.
Question page
A GOV.UK radio question asking what type of valuation breach is being reported, with hint text under each option and an 'or' divider before a final option.
The value question — where the branch is decided
A GOV.UK page asking for the error as a percentage of net asset value, with a percent suffix, and the monetary value with a pound prefix, plus a collapsible section explaining how to calculate the percentage.
Material route — 0.85% entered
A GOV.UK page with an important notification banner reading 'This is a material breach — an error of 0.85% of net asset value is at or above the 0.5% materiality threshold', followed by a task list of remaining sections and an explanation of the 5 business day deadline.
Non-material route — 0.12% entered
A GOV.UK page explaining that an error of 0.12% is below the materiality threshold, what the user must still do, what they do not need to do, and a warning that a prospectus may commit the fund to a lower threshold.
Check your answers
A GOV.UK check-your-answers page with three summary lists grouped by fund, breach and investors, each row having a Change link, and the materiality shown as a red Material tag with supporting text.
Design notes
A GOV.UK page setting out the design rationale, a table of patterns used and the reason for each, accessibility decisions, and a numbered list of things to test next.
Mobile
The same radio question rendered at mobile width, showing the GOV.UK responsive layout with full-width touch targets.

Running it

The quickest option is the live deployment — no setup needed.

To run locally, Node.js 22 or later:

git clone https://github.com/davendra/fundadmin-gov-prototype.git
cd fundadmin-gov-prototype
npm install
npm run dev

Open http://localhost:3000

Seeing both routes

At the error-percentage question:

Enter Route What you should see
0.85 Material Important banner, task list, 5-day deadline, redress and root-cause questions later
0.12 Non-material Below-threshold explanation, prospectus warning, no redress questions
0.5 Material Boundary case — the threshold is inclusive
0.75 % Material Forgiving parsing: the stray percent sign and space are stripped
(blank) Returns you to the question rather than guessing

/start-again clears the session between demos.


Deployment

Live at fund-admin.gov.davendra.com on Vercel. The Prototype Kit assumes a writable filesystem and a long-lived process, and neither holds on serverless, so three things are adapted:

Sessions move to a signed cookie. The Kit stores session data as JSON files under .tmp/sessions. A serverless function has no shared filesystem between invocations, so file-backed sessions silently lose data — which would break the materiality branch, since it depends on the stored decision surviving a redirect. cookie-session is registered ahead of the Kit's own middleware, so the whole session travels in a signed cookie and any invocation can read it.

Sass is compiled at build time. The Kit compiles stylesheets at boot, which fails on a read-only filesystem. scripts/vercel-build.js calls the Kit's own generateAssetsSync during the build instead, then the function serves the output as a static file. The build fails loudly if the stylesheet is missing or implausibly small, rather than deploying an unstyled prototype.

Runtime files are bundled explicitly. Vercel's static tracer cannot see that the Kit loads views and plugin manifests dynamically, so the first deploy returned 500s with an empty function bundle. includeFiles in vercel.json ships app/, .tmp/ and the Kit's packages, and KIT_PROJECT_DIR pins the project root because cwd is /var/task.

USE_AUTH=false disables the Kit's production password gate. That gate is correct for real government prototypes — it stops unfinished work being published — but this is a public portfolio piece, so the pages carry their own prototype notices and the deployment sends X-Robots-Tag: noindex, nofollow.


Patterns used, and why

Pattern Where Reason
One thing per page Throughout Each question can be answered from a different source document
Check answers before you submit Before sending This is a regulatory declaration — the user attests to accuracy
Confirm pre-held data Fund details Reference-number lookups fail silently; showing the fund name catches a wrong PRN
Branch on a calculated value After the value question Removes a regulatory judgement from the user
Task list Material route only That journey is long enough that users will leave and return
Character count Root cause Free text has a downstream length limit; showing it prevents lost work
Warning text Non-material route A prospectus can commit a fund to a stricter threshold
Details Value question Calculation help, available without cluttering the question

Accessibility

Built to WCAG 2.2 AA intent, following GOV.UK Design System guidance:

  • Question as h1 on every question page, so screen-reader users hear the question first rather than the service name
  • Every input has a visible label, programmatically associated — no placeholder-as-label
  • Fieldset and legend around every radio and checkbox group
  • Visually hidden text on change links in check-answers, naming what each link changes
  • Prefix and suffix elements for % and £ rather than placeholder hints
  • inputmode set on numeric fields for correct mobile keyboards, without blocking paste
  • Materiality conveyed in text as well as tag colour — never colour alone
  • Skip link, landmarks and heading hierarchy inherited correctly from the GOV.UK layout

Verified across question pages: h1 count 1, zero placeholder misuse, fieldsets present on all grouped inputs, 7 visually hidden change links.


Testing

The branch logic and both journeys were verified end to end:

Test Input Expected Result
Below threshold 0.35 → non-material
At threshold 0.5 → material (inclusive)
Above threshold 1.2 → material
Messy input "0.75 %" parsed → material
Empty input "" → back to question
Non-numeric "abc" → back to question
Session persistence PRN 445591 shown on check-answers
Conditional fields non-material route redress + root cause absent
Dynamic back link non-material route points to /non-material-breach
Route-specific outcome both routes different confirmation next-steps

All 12 pages return HTTP 200 with no template rendering errors.


What I would test next

With real users, in this order — and I would expect to be wrong about at least one of these:

  1. Does the percentage question work at all? The assumption is that users have this figure to hand. If they arrive with a monetary error and no percentage, the calculation belongs in the service and the question order is wrong.
  2. Is one error percentage enough? Multi-class funds may have a different error per share class. If users cannot answer without splitting it, this becomes a repeating pattern — a change that would ripple through the whole journey.
  3. Is the calculated result trusted? If users override the service's answer or ring the regulator anyway, the explanation is not doing its job.
  4. Does the deadline land? The 5 business day clock starts at identification, not submission. Whether the current wording conveys that is testable, and I would expect to get it wrong first time.

Deliberately not built

Being explicit about scope, because a prototype that pretends to be production-ready is worse than one that admits its edges:

  • Server-side validation with error summaries and inline messages
  • A real Financial Services Register lookup — the fund details are stubbed
  • Save and return, and authentication
  • The repeating pattern for multi-share-class funds
  • Any actual submission, storage or notification

Repository structure

app/
├── config.json            Service name and plugin config
├── routes.js              The materiality branch — the interesting file
├── filters.js             Nunjucks filters (unused)
├── data/
│   └── session-data-defaults.js
└── views/
    ├── layouts/main.html          Extends govuk-branded
    ├── index.html                 Start page
    ├── reference-number.html      PRN
    ├── fund-details.html          Confirm register data
    ├── breach-type.html           Radios with hints and divider
    ├── breach-dates.html          Three date inputs
    ├── breach-value.html          Percentage and monetary value
    ├── material-breach.html       Material route
    ├── non-material-breach.html   Non-material route
    ├── investor-impact.html       Conditional questions by route
    ├── check-answers.html         Summary lists with change links
    ├── confirmation.html          Panel and route-specific next steps
    └── design-notes.html          Rationale and testing plan
docs/images/               Diagrams and screenshots

Context

Built as a working demonstration of GOV.UK service design applied to a regulated financial-services problem.

The domain is deliberate. I spent five and a half years leading AI and automation at a global fund administrator, so the fund valuation, NAV, depositary and materiality concepts here are drawn from that work rather than researched for the exercise. Wiring up Design System components is straightforward; knowing which question not to ask is the part that comes from the domain.

The diagrams were generated with Go Bananas, an AI image platform I built and run.

About

GOV.UK-standard prototype for a fund valuation breach notification service. Demonstrates calculating a regulatory materiality judgement rather than asking the user for it. Prototype Kit 13 + GOV.UK Frontend 6.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages