Skip to content

Admin ability to generate synthetic data for an exercise to easily support UR / Testing AB#307770 #565

Open

Description

@davidgouge

Blocked by: the Window / Exercise / Ingest refactor (#466, branch 466-exercise-refactor-2). Start this work only after that branch is in main. This design uses its releases, PublishedDatasets and CheckingExerciseBlobPaths.

Background

During UR sessions, some organisations do not agree to the use of their live data. At the moment we make fake data by hand and upload it. We want an admin to make this data in the admin app, for one or more LAESTABs, in a few clicks.

All checking data is keyed on LAESTAB, so a LAESTAB list is the input.

If a user already signed in and saw no data or real data, they must see the fake data on their next page load. (The original request said "invalidate the cache". The design below does this without a cache eviction. See Caches.)

Decisions

Question Decision
Environments All, production included. So the guard rails below are requirements, not extras.
Unit of generation The whole window. The admin starts it from a window or exercise page, but it makes data for every exercise in the window from one fake population per LAESTAB. Results, campus, aims, value added and summary then name the same students as pupil data (they join on CYPMD_ID).
Window types All window types and all exercises. No branch on CheckingWindowType or on the exercise kind. Any exercise with a published schema gets fake data: for example, a KS4 June window with a results enquiry gets fake results.
Students per LAESTAB 60 included, 20 not included by default. The admin can change both for each generation.
How values are made Schema + field rules. Walk each published schema. Known fields get realistic values. Unknown fields get plausible filler from their JSON type. A new data share works with no code change.
Lifecycle One-off. Generation makes a new release. The next real run of the exercise is built from the supplier CSVs again, so the real data comes back for those schools.
Change requests made on fake data Mark and block. They are saved and the school sees them, but they never go to Zendesk and egress never pulls them.
The school's existing requests Leave them. The confirm page shows how many requests and drafts each LAESTAB already has.
Permission ManageWindow. Every generation writes an audit row.
Typo risk in production The confirm page is enough. No school-visible marker and no school name lookup.

Scope

1. Admin screens

  • A Generate synthetic data link on the window summary page, gated by [RequireAdminSection] on ManageWindow (see AdminRouteGateTests).
  • Form (admin/windows/{id}/synthetic-data):
    • LAESTABs, one per line. Accept nnn/nnnn and nnnnnnn (the DfeNumber rule). Normalise with LaestabNormaliser. Remove duplicates. Set an upper limit (suggest 50).
    • Number of included and of non-included students per LAESTAB. The admin can change them for each generation. Defaults: 60 included, 20 not included.
  • Confirm page (GET confirm then POST, like DeleteExerciseController). It shows:
    • each exercise that will get a new release, and each exercise that is skipped, with the reason (no published schema, or UsesExerciseStorage = false);
    • per LAESTAB: does it have real data now, and how many change requests and drafts does it hold;
    • a clear warning that schools at these LAESTABs see fake data until the next run of each exercise, or until an admin makes an earlier release live. In production the warning names the environment.
  • Progress page. Copying the other schools' files can take time (see section 3), so run it as a background job with a progress stream, like validate.
  • Result. Each exercise's Data releases table shows the new release, marked as synthetic, with its LAESTABs.

2. Generating the data

  • One population per LAESTAB, shared by every exercise in the window. Each fake student has a stable CYPMD_ID, UPN (with a valid check letter), forename, surname, sex and DOB (age to suit the window's key stage), plus inclusion status. Every dataset's rows for that school come from this list.
  • Deterministic. Seed the generator from window id + LAESTAB. Then a second generation for the same school gives the same students, and a UR script stays correct.
  • Rows are supplier-shaped CSV rows, made from the dataset's schema (x-csv column names, x-ingress.source columns and x-ingress.separator). Push them through the same row transform the ingress run uses, so stamping (INCLUDED, SOURCE), the merge of journey slots, and MergedFileIsDatasetFile are identical to a real run. Do not write a second description of the output shape.
  • Inclusion by file of origin. On 16-19, split the population across the included and non-included slots. On KS4, set P_INCL to an included code (401, 403, 414, 421, 431) or another code.
  • Field rules. Use a registry of named generators, matched on property name. Same pattern as IFormatValidator / IJourneyCondition: pure logic in Application, registered in the Application DependencyManager. Start with: names, SEX, DOB, UPN, CYPMD_ID, LAESTAB, URN, P_INCL, results fields (QAN, SYLLABUS, SESSION, QUAL_NAME, grades), and point scores. Unknown property: by JSON type (string → "Sample {label} {n}", number → small integer, nullable → sometimes null).
  • Layouts. Vertical (e.g. the 16-19 Summary share) gets one record per school. Table and InclusionTabs get one row per student (results: 1 to 4 rows per student).
  • Datasets. Use CheckingExerciseDto.PublishedDatasets, never DatasetsToIngest. Retired slots are not generated.

3. Releases and storage

The release rule is "never write over a release's output". So generation never edits the current release's blobs. For each exercise that it generates:

  1. Make a new release whose parent is the current release. It names the same slot CSVs as its parent (no new CSVs). It also records that it is synthetic and lists its LAESTABs.
  2. Server-side copy every file of the parent release to the new release, except the files of the target LAESTABs.
  3. Write the fake files for the target LAESTABs: one per dataset (DatasetBlobName) and the merged file (journey slots only).
  4. Make the new release current in one save, through the same code as PublishReleaseAsync.

If the current release is already synthetic, its LAESTAB list carries over to the new release (it is the union).

An exercise with no current release (e.g. its first file has not arrived): its release holds only the fake schools.

To remove fake data before the next run, the admin makes the parent release live from the Data releases table. No new screen is needed.

4. Caches

No IMemoryCache eviction is needed, and an eviction would not be enough: production runs more than one web replica, and an eviction clears only one. The cache keys already contain the current release:

  • CheckYourPupilDataRepository: pupils:{windowId}:{laestab}:{current release ids}
  • StudentResultsBlobClient: the blob path, which names the release
  • CheckingDataReader (exercise tabs, downloads, HasSchoolDataAsync): no cache

So a new current release is read at once on every replica. Add a test that pins this for each cached reader, so that a later change to a key cannot break it.

A user who is in a journey when the data changes holds the old pupil in their session. They must start the journey again. The confirm page says to generate before the session starts.

5. Change requests on fake data

  • Stamp ChangeRequest.IsSynthetic when a request is saved or submitted and its school's LAESTAB is in the synthetic list of the release it is stamped with (CheckingExerciseReleaseId). The flag stays set: a draft resumed after a real run still names fake pupils.
  • Requests with the flag:
    • show to the school as usual (Amendment Requests, the submitted view);
    • are not decided by the RulesConsumer, not queued by the close sweep (ICloseExerciseService) or by results-enquiry submit, and not claimed by the ZendeskConsumer;
    • are not pulled by data egress.
  • The admin Requests list shows them as synthetic.

6. Audit

Write a deliberate audit row (SyntheticData/Generate) with the window, the exercises, the new release ids, the LAESTABs and the user, in addition to the generic capture of the release rows.

Not in scope

  • A school-facing "this is fake data" marker. UR needs the page to look real.
  • A school name lookup (GIAS) on the confirm page.
  • A new screen to remove fake data. Use "Make live" on the parent release.
  • Exercises with UsesExerciseStorage = false. They have no releases, so they are skipped.
  • Fake data that survives a re-run.

Acceptance criteria

  • A ManageWindow admin can enter one or more LAESTABs for a window, confirm, and see progress until the job is done.
  • Each exercise with a published schema gets a new current release, marked synthetic, listing the LAESTABs. Other schools' files in that release are the same as in the parent.
  • A school user at a target LAESTAB who is already signed in sees the fake data on the next page load, on every replica, with no restart and no wait for cache expiry.
  • The fake population is the same across every exercise of the window (results and data shares name students that are in pupil data).
  • The same LAESTAB and window give the same students each time.
  • The journeys (pupil data, results enquiry) work on the fake data for every window type, and a new data share works with no code change.
  • The admin can set the number of included and non-included students for each generation (defaults 60 and 20).
  • A change request on fake data is saved and shown to the school, but never reaches Zendesk or egress.
  • "Make live" on the parent release brings back the real data. The next real run also brings it back.
  • Exercises that cannot be generated are listed on the confirm page with the reason, and are not changed.
  • Each generation writes an audit row.
  • The controller has [RequireAdminSection] (pinned by AdminRouteGateTests), and the views meet the accessibility rules in CLAUDE.md.

AB#307770

Activity

  1. changed the title [-]Admin ability to generate synthetic data for an exercise to easily support UR / Testing[/-] [+]Admin ability to generate synthetic data for an exercise to easily support UR / Testing AB#307770[/+] on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions