← Back to work
Case 02 · Enterprise systems

Merging class records without touching the source.

A rule-based merge tool for district administrators managing student roster data, built around a hard constraint: the operation had to feel safe enough that someone would actually run it on live enrollment records.

Work shown with enterprise-identifying details removed. Product name and interface are my own; original assets are not shown.

Role
UX/UI Designer
Users
District data administrators
Tools
Figma, Claude prototyping, React
Status
Shipped to production
The context

Schools and their software have to agree on what a class is.

School districts run a student information system that holds every student, teacher, course, and class section. That data has to reach dozens of learning applications, and it has to arrive in a shape those applications can read. A roster platform sits between the two, pulling data out of the district's system, transforming it, and syncing it onward.

The person doing that work is a district data administrator. They are technical, but they are not engineers, and the records they are handling are real students in real classrooms. A bad sync does not produce a bug report. It produces a teacher who cannot see half their roster on the first day of school.

The problem

One real class, scattered across many records.

Student information systems frequently split a single real-world class into several separate records. The same teacher, same course, same room, but recorded once per period, per section, or per term depending on how the district set things up years ago.

Downstream, every one of those fragments syncs as its own distinct class. A teacher who runs one combined section sees three. Applications that assign work by class assign it three times. And there was no way to combine them during the import at all.

What administrators did instead
  • Restructure the data upstream in the student information system, which is slow, political, and often not theirs to change
  • Accept the duplicates and let every connected application inherit the fragmentation
  • Maintain manual workarounds per vendor, repeated every time the roster refreshed
The before state here is an absence rather than an interface. There was no merge capability to redesign, so there is no prior screen to show.
The real design problem

The word "merge" was the hardest part.

Building a rule builder was the tractable half. The difficult half was that merge, to almost everyone, means combine and destroy. Two things go in, one thing comes out, and the originals are gone.

This feature does not work that way. It creates a new class from the matching ones and leaves every original record exactly as it was. Nothing is deleted, no schedule changes, no student is permanently moved. It adds structure rather than replacing it.

An administrator who misreads that model either never runs the feature, or runs it once and panics.

Which made the mental model the actual deliverable. If the interface could not make the non-destructive behavior obvious before someone clicked anything, the merge logic underneath it did not matter, because nobody would trust it enough to use it.

The design

Three decisions, in order, one at a time.

I structured the merge as three sequential steps rather than a single configuration form. Each step asks one question, and the answer to the first determines what the next two can even offer.

Step one
What must two classes share to be merged?
The administrator selects the fields that have to match. Classes must match every selected field, with no partial matches and no confidence scoring. If a field is checked, it is required.
PeriodTeacherSubjectClass typeSchoolCourseTermClass titleGrades
Districts structure their data differently, so the fixed list was never going to cover everyone. An add metadata option pulls additional fields from the class record into the list, where they behave exactly like the built-in ones.
Step two
What should the merged values resolve to?
Matching on a field does not settle what the merged class inherits. If two sections span different terms, one of them has to win. Step two sets that resolution for term, subject, and grade, and doubles as a filter that narrows which classes qualify in the first place.
Longest termAll termsMost commonAllEmpty
Locked until step one has a selection
Step three
What should the merged class be called?
Administrators either keep the original title or compose a new one from the fields they matched on in step one, combined with text functions. The title is built from data they have already committed to, so it cannot reference a field the merge does not actually guarantee.
Keep original titleCompose from matched fieldsUPPER_CASETRIMLEFTRIGHT
Locked until step one has a selection
The problem
  • No way to combine class records during the import at all
  • A single real class arrives downstream as several separate records
  • The only remedies sit outside the tool: restructure the source system, or accept the duplicates
  • Every connected application inherits the fragmentation
The design
  • Merge rules defined in three ordered steps
  • Match fields, resolution rules, and the merged title each decided explicitly
  • The result previewed against real records before anything is saved
  • Original records untouched; merged classes added alongside them
Design principle at work
Sequence is a safety mechanism, not just tidiness. Locking steps two and three until step one is answered makes it structurally impossible to configure a merge rule around a field you never required.
Three collapsed steps each showing a summary line, with the result preview below and the guide panel alongside

Each step collapses to a single summary line once answered, so the full rule stays readable at a glance without a separate review screen.

A smaller decision

The rule has to be legible while you build it.

A completed step collapses to one line stating what was chosen. Three answered steps become three short sentences, which means the administrator can read their entire rule back before running anything.

That removed the need for a dedicated confirmation screen. A review step that introduces information the user has not already seen is a warning sign; a review step that only repeats it is a delay. Keeping the summary inline meant the rule was always under review.

The guide

Half the guidance is about what the tool refuses to do.

A guide panel sits beside the configuration and changes with the step in view. On step one it explains that matching is strict. On step two it explains that a rule can be left open. On step three it explains which title fields are available and why.

Before any step is opened, it explains the model itself: what a merge creates, what happens to the originals, and what the feature will never do.

What this feature does not do
  • Does not delete classes
  • Does not modify schedules
  • Does not move students permanently
  • Does not replace source data

That list is the piece of this project I would defend hardest. It is not documentation and it is not a disclaimer. It is the thing that lets an administrator run a bulk operation on student enrollment records without hesitating, because it answers the question they are actually asking, which is not how does this work but what could this break.

Design principle at work
When an action sounds more destructive than it is, naming the boundaries is part of the interface. Reassurance that arrives after the click is too late.
The guide panel explaining merge logic, the process, and what the feature does not do
Try it

The merge panel, rebuilt and interactive.

I rebuilt this as a standalone prototype with fictional records, embedded below. Select match fields and watch the result preview. Source records stay exactly where they are; the merged class appears alongside them.

Sample data is fictional. Interface rebuilt from scratch in my own design system. Open full screen ↗
The decision

Strict matching, or close enough?

Step one requires classes to match every selected field. There is no partial match, no similarity threshold, and no confidence score. Given how differently districts structure their data, a looser rule would have caught more of the fragments administrators actually wanted combined.

I chose strict anyway. A fuzzy match that merges two classes an administrator did not intend to merge is a silent error on live enrollment records, and the entire feature rests on the promise that nothing surprising happens. Catching fewer fragments is a cost the administrator can see and correct. Merging the wrong ones is not.

Impact

A structural fix that districts can make themselves.

The feature shipped to production and is in use by districts today. Fragmented class records can now be resolved during the import itself, without restructuring the student information system and without per-vendor workarounds downstream.

Because merges create new records rather than altering existing ones, administrators can define a rule, see the result, and adjust it without any risk to their source data. The reversibility is what made the feature usable at all.

What I learned

The rule builder was never the hard part.

Lesson 01
Naming what a feature will not do is design work.
The boundaries panel did more for adoption than any screen in the flow. When an operation sounds destructive, the absence of a clear promise reads as a risk.
Lesson 02
Sequence can enforce what validation cannot.
Gating later steps behind earlier ones prevented whole categories of invalid configuration without a single error message.
Lesson 03
A visible summary beats a review screen.
Collapsing each answered step into one readable line kept the whole rule reviewable throughout, instead of saving verification for the end.
Lesson 04
Fixed field lists fail real data.
No predetermined set of match fields survives contact with how differently districts record the same information. The escape hatch was not an edge case, it was the requirement.

The interface question was how to combine two class records. The design question was how to make someone believe nothing would be lost when they did.

Thanks for reading.