3,000 Records, Zero Guesswork
- Role
- Period
- Where

Context
Crimson was swapping out its HRIS. The thing about a migration like this is that it almost never fails loudly — it fails quietly. A record that never made the jump, a work history stapled to the wrong person, a document filed under the wrong heading. Nobody notices until six months later, when an auditor or an upset employee does.
Problem
There were more than 3,000 records in the old system and far fewer profiles waiting in the new one, and the data in between was a mess: locations recorded as anything from a city to a whole continent, managers listed by nickname, people who’d left with no email left to match them on. The import engine, for its part, would reject a row in silence over a single stray hyphen.
What I built
So before I built a migration pipeline, I built a reconciliation one. The matching ran in two passes — email first, then a name-based fallback — and that second pass quietly recovered hundreds of records email alone would have dropped on the floor. The moment that actually scared me was a cluster of records that showed up in one system but not the other. Four passes later it had shrunk to five cases, each with a reason I could point to. Not “probably fine.” Explained.
Then came the bulk loads — thousands of rows of compensation, role, and employment history. When something failed I sorted it into a named bucket instead of blindly retrying, and the handful that were genuinely stuck got escalated with evidence the vendor could actually act on. The documents went batch by batch, audited at 100% — every text file read, every scan and image looked at with my own eyes.
Outcome
What we ended up with was a migration where every gap had an explanation, not a shrug. And the matching logic didn’t get thrown away afterward — I packaged it into a reusable AI skill, so the next census audit is one prompt instead of one week. That skill is its own story, over in the AI ops tooling case study.
| Legacy record | Name | Resolved by | |
|---|---|---|---|
| EMP - 1042 | ✓ | ✓ | Pass 1 · email |
| EMP - 1043 | ✓ | — | Pass 1 · email |
| EMP - 1051 | — | ✓ | Pass 2 · name |
| EMP - 1067 | — | ✓ | Pass 2 · name |
| EMP - 1080 | ✓ | ✓ | Pass 1 · email |
| EMP - 1095 | — | — | Explained exception |