Evaluation against customer test data
A US regional bank wanted to evaluate models against its own test data. DataRobot could do that only in a legacy UI that was no longer actively developed. My team was moving that capability into the new product’s experiment workflow, and I owned our part of it. It was my first project of that size where I owned delivery directly.
The migration spanned seven model-insight views plus prediction-explanation alignment. I studied the domain and the legacy flow with AI coding agents: attachment and eligibility rules, tables, charts and views. The requirements and designs covered supervised training; the unsupervised path wasn’t in them and stayed out of scope. It came back later.
The designs put Add buttons in several places, especially next to charts. Upload a dataset from a chart, and it’s unclear what owns it. I argued for one upload point at the experiment level, with every other view selecting from datasets already attached. That’s what we built.
The old state management and legacy charts were held together by wrappers and heavy prop drilling, and many components read the selected dataset. I didn’t rewrite that layer. I added a shared React context store, selectors and a transformation layer that turns the data into a UI-friendly shape. New cells and views read from it; the old charts kept working.
I cut delivery into small steps, each testable, releasable and useful on its own: one attachment with a basic table, then multiple attachments and switching, then scoring, then table integrations, then one chart at a time. Ten epics, five release trains, no big-bang launch.
The browser can’t reliably tell at upload whether a dataset is compatible; those rules live on the backend and can be expensive to run. So after an upload the UI refetches the eligible datasets and warns if the new one isn’t among them. A real incompatibility can still surface at scoring, but users get the earliest reliable feedback and the client doesn’t duplicate domain logic.
The supervised workflow shipped in about five months, and customers used it in production. An external QA pass before broad use found about 15 defects, mostly UI state and unusual-data edge cases, none critical or blocking.
Then a customer reported that their datasets didn’t work, without mentioning the experiment was unsupervised. The first theory was an encoding problem with Japanese characters. Chasing it turned up a real backend bug, but fixing that didn’t fix the report. Once the theory changed, I traced the cause to the unsupervised setup: its eligibility rules still lived in the legacy flow, and neither the new UI nor the backend supported that variant. I fixed both sides. The fix merged and reached staging before I left DataRobot.
Next time I’d treat the requested workflow as a starting point, not the system boundary: map adjacent modes and dependencies first, and write down what’s supported, unsupported and unknown, with the validation assumptions. I walked management through it: what went wrong, why and what should change.
Stack — React, context-based state layer.
Customer-defined evaluation metrics
Built-in metrics are a baseline, but each domain has its own idea of an accurate model. Customers’ data engineers wanted to define their own evaluation metrics: a YAML file describing them and a Python file with the calculations. That was possible only through the API. The work had customer validation calls and POC demos behind it, and it was tied to revenue at risk and a separate renewal blocker. It came to me when my previous task finished, and I was expected to own it through delivery.
The backend core already handled uploads. What was missing was the product around it, in three phases: a registry for uploading and inspecting packages of up to 50 metrics; experiment setup, where custom and built-in metrics sit side by side and an eligible custom metric can drive optimization; and post-modeling results showing the package, its metrics and the one that drove optimization. Each phase needed backend work too.
I kept the requirements and documentation, wrote the specs, broke the work into about 60 stories, proposed UX changes and built the React UI. On the Python side I extended existing endpoints and payloads, such as a PUT for the new project setting and a large GET that had to carry custom-metric data; I decided that GET’s response shape. I also added the new endpoints the UI needed.
The design asked users to upload both files and click Validate before seeing any feedback. Validation meant three checks: the YAML format, the Python format, and whether the Python implemented what the YAML declared. On the design-review call I proposed running them as the files arrive: check the first file on upload, the second on its upload, then cross-validate the pair. It was approved on the spot. The Validate button went away, and the endpoint changed to accept one file at a time, detect its type and run the right check. The cost was one or two extra calls; the gain was much earlier feedback.
Manual happy-path testing at the end of a phase caught a blocker: with a custom optimization metric, the experiment wouldn’t start. My research agent traced the save path to two validation points hard-coded for built-in metrics, so the custom metric was rejected and never reached the modeling engine. The backend lead confirmed it, and I took the fix: built-ins keep their existing validation, and custom metrics are checked against their selected package, at both points. Integration tests cover those seams.
The backend fix merged. The UI still needed a small change: send the right field and treat built-in and custom metrics differently. All three phases were built behind a feature flag; one UI fix was still in review when I left DataRobot.
Next time I’d join during backend design and walk the user flows through before implementation. I relied too much on the backend team’s assurances; deeper code research would have exposed the hard-coding sooner. Now I verify the complete flow myself.
Stack — React, Python API changes, YAML/Python file validation, integration tests.
Multi-agent engineering workflow
On the evaluation-metrics work I carried the React UI, the Python API changes, the tests and the maintenance, flaky tests included, often at the same time. Over about three months of that work, I built a workflow for myself to keep all of it moving. It formalizes how I already worked instead of replacing my judgment. The design question was where the agents stop and I start.
My answer splits the work by judgment. Framing, architecture, decomposition, review, validation and acceptance stay with me. Agents get bounded research and implementation, nothing more.
Every loop starts with a bounded task or spec that I write. A feature-level orchestrator picks research or implementation subagents; it never implements anything itself. Each agent gets its own git worktree, so parallel changes stay apart. The agent makes the change and runs the checks and tests. Then I review the complete diff and decide which feedback is valid. The workflow also drafts PR descriptions from a template, checks PR status and assesses review comments, and a small tool I wrote opens diffs for my review.
Research agents do their most useful work before I change any code. Given a bug, one traces the complete code path and writes a reference doc with files, lines and functions, before I write the bug report or spec. On the evaluation-metrics blocker, that doc became a bug task with specs. I treat an agent’s findings as a lead, not a verdict: I check the evidence and confirm it with the backend or domain owner.
Two rules apply to the code itself: write the minimum necessary, and make every PR simpler with each review round.
With research, implementation and testing running in parallel, I kept up to four tracks moving at once. That’s how I took on API changes and integration testing alongside frontend delivery.
Stack — Custom orchestration scripts, isolated git worktrees per agent, an LLM coding agent for research and implementation, a small tool for opening diffs.
Visual schema-mapping editor
At Edvantis I did hands-on frontend engineering and technical coordination in a team of five or six engineers, on a logistics platform built from distributed micro-frontends.
One feature mapped a customer’s database schema onto the platform’s internal model. Until then, mappings were written by hand in a powerful CSV DSL: field type, subtype, transformation rules, calculated fields. On a large schema, manual mapping was hard and error-prone, the DSL’s capabilities were hard to discover, and nothing showed a preview.
The DSL itself was the unclear part. I had no access to the mapping engine’s code, and my starting point was an incomplete document with no example CSVs. I worked closely with the feature architect to formalize the domain before building the UI from scratch, and the mapper’s developer filled in context. From the docs, those explanations and the format’s behavior, I reconstructed the DSL’s syntax, semantics and transformations.
I aimed the editor at the common, relatively simple mappings and left the rest to the DSL. The customer schema sits on the left, the internal model on the right. Users drag between nodes to create 1:1, 1:many and many:1 connections, and each transformation type has its own presentation.
The backend returned large nested schemas as an awkward array structure. I built a normalized in-memory model on JavaScript Maps, maps referencing maps, which forms a nested, circular graph shaped for traversal. Two transformer facades convert between that model and the backend format in both directions, so the UI never touches the raw shape.
Schemas ran to about 1,000–2,000 nodes. Expand and collapse plus virtualization kept most of the tree out of the DOM. I profiled data access and rendering continuously instead of assuming the model stayed fast. That data model and its profiling were my best decisions on the project.
I verified the work mainly with unit tests, plus some integration tests, fixtures and runs against existing spreadsheets. Users could build the common mappings visually instead of writing CSV by hand, and scrolling and expanding stayed responsive on those schemas.
Next time I’d do fuller discovery of the DSL up front, split its rules into separate documents, push for closer parity with the DSL, and decompose the work more carefully.
Stack — A normalized Map-based data model, drag-and-drop UI, virtualized rendering.
SkillReveal: frontend lead from zero
SkillReveal was an early-stage platform connecting interviewers with candidates, for mock interviews and for real interviews that companies outsourced to it; later it added a job board, with outsourced interviews sold separately. I joined as lead frontend developer in a six-person team, when the product was a few design screens. Frontend and backend were built in parallel, fast, sometimes late into the night.
Nothing was settled yet: not the stack, not the architecture, not the order of features. I set the frontend direction from the first commit. For state I chose Recoil, because atomic state had worked well for me before. It kept UI separate from data, let me derive state from atoms, and needed little external tooling. Redux felt too boilerplate-heavy for an MVP moving that fast. The stack and architecture stayed in place for as long as I was there. I also worked through data-shape questions with the backend engineer.
The CEO specified the views. Together we decided what to ship fast, what to postpone and what to prioritize; I brought feasibility, UI decisions and the user’s perspective. I also ran several interviews on the platform myself, and my feedback as a practicing interviewer went into the UI. The MVP took about three months.
The assessment page was one of the most important in the product. It generated competency sections and scoring controls dynamically, and its state was too coupled: one score selection re-rendered every component on the page. I spent about a month restructuring the data flow and rendering. Afterward a score change re-rendered only the component it affected, and scoring, comments and derived results triggered only the work that depended on them. We released it, and users reported better responsiveness, especially on mobile.
I also customized Material UI into an in-house design system, so recurring patterns became reusable components.
During my time there, SkillReveal reached 20 paying customers, roughly $15K in monthly recurring revenue and about 4,000 registered users across client teams, interviewers and candidates.
I’d make many of those early calls differently today; I was less product-minded then.
Stack — React, Recoil, a customized Material UI design system.
2011–2021
Earlier work
Jonny Git — lead frontend developer at a startup from the same incubator as SkillReveal, on a product that analyzed individual and team activity in Git repositories; built from scratch with one other engineer, with a focus on frontend performance.
N-iX — real-time trading (Angular/NgRx), from a web prototype of a .NET trading app to the production UI with the full team; a visual questionnaire builder; and a video-processing pipeline editor (React/Redux, real-time SVG manipulation).
Lohika — UI components for a highly configurable analytics dashboard with real-time data: D3/SVG charts and hand-built virtualization for large lists.
GlobalLogic — migrated a Chrome-extension messenger to a desktop application built with web technologies (Node-WebKit); built HTML5 apps for media-player devices on a customized Opera runtime, under tight memory limits.
Viseven — interactive e-detailing presentations that pharma sales reps used with healthcare professionals, plus a lightweight CSS framework for chart markup and animation.
Full history in the CV · serhii@babich.dev