Skip to main content

When Your Migration Becomes a Platform: Scope Growth Done Right

We were hired to migrate 377 legacy BI objects. We ended up building a 161-model analytics platform across 7 domains. Here's why that was the right outcome.

AC
Arturo Cárdenas
Founder & Chief Data Analytics & AI Officer
March 20, 2026 · Updated March 20, 2026 · 9 min read
When Your Migration Becomes a Platform: Scope Growth Done Right

Key Takeaway

Scope grew 5x over five months on a cloud security analytics engagement. Not because of poor planning — because we built architecture designed to support it. The framework we'd use again: when to say yes, when to say no, and why domain isolation is the mechanism that makes scope growth safe.

We were hired to migrate 377 legacy BI objects. We ended up delivering a 161-model analytics platform across 7 domains. Scope grew by nearly 5x over five months.

That's not a cautionary tale. It's what good architecture looks like from the outside.

Every project management article on the internet is about scope creep prevention. Nobody writes the case for when scope growth is actually the right outcome — because the original infrastructure was built to support it. We just finished one of those engagements. This is what it looked like, why we kept saying yes, and the framework we'd use to decide all over again.

What the original scope was

The engagement started in November 2025. A cloud security SaaS company needed to migrate legacy BI logic — 377 objects spread across a legacy BI system that had calcified over five years — into a modern dbt + Snowflake + Sigma stack.

The initial target was metering-based revenue analytics for one product line: usage data from 12 cloud regions, consolidated into billing models that Finance could trust. Everything inherited from the legacy system needed to land in dbt with validated parity against historical invoiced amounts.

That was it. One domain. One product. One team of direct stakeholders.

The dbt project structure we established looked like this:

models/
├── staging/
│   └── product_alpha/          # 12 regional metering views
├── intermediate/
│   └── product_alpha/          # deduplication, union, enrichment
└── marts/
    └── product_alpha/          # billing facts, ARR, dimensions

By the end of November, the MVP was running: tiered pricing macros, regional staging views, net revenue calculation, ELA targets. Two weeks from first commit to working fact tables.

By the end of March, that project had grown to 161 models across 7 domains.

Scope growth timeline: November through March showing model count growth from ~34 to 161, with annotations at each expansion — initial metering (Nov), validation phase (Dec), FY27 pricing sprint (Jan), platform expansion (Feb-Mar)


The expansions, one by one

December: Validation depth

The first expansion wasn't new domains — it was depth. Proving parity to 0.002% variance across all historical revenue required more models than the original spec anticipated: ARR models, dimension tables, unpivoted fact tables formatted specifically for Sigma, archive patches to fill historical gaps.

This could be called scope creep. We'd call it working backward from the real requirement.

"We migrated the data" is not the same as "Finance can trust these numbers." The additional modeling work was what closed that gap. Without it, the entire engagement would have ended with a technically complete migration that nobody was confident using.

The structural investment paid forward: the validation patterns we built in December — full outer join variance queries, grain-level debugging, parity-first sequencing — carried forward to every expansion that followed. When we got to FY27 pricing in January, we already had the validation infrastructure to run it at 0.00% per-batch variance.

January: FY27 pricing sprint

In January, the Finance team had a hard deadline: new fiscal year pricing needed to be live before Q1 bookings opened. New tier rates, 11 regional multipliers, a restructured discount hierarchy.

This was clearly outside the original migration scope. We said yes for two reasons.

First, the architecture supported it. The domain isolation pattern we'd established — each product domain in its own schema, no cross-domain dependencies — meant a new pricing model could live alongside existing revenue models without touching them. We didn't have to refactor anything to add this.

models:
  project_name:
    staging:
      product_alpha:
        +schema: slv_product_a
      pricing_fy27:
        +schema: slv_pricing_fy27
    marts:
      product_alpha:
        +schema: gld_product_a
      pricing_fy27:
        +schema: gld_pricing_fy27

Second, the Finance team's ability to trust the new pricing models depended on them sitting next to the validated revenue models they'd just signed off on. You can't validate FY27 pricing in isolation from the revenue data that the pricing was designed to generate. They had to coexist.

We delivered in 9 batches over 9 days. The story of how we ran that sprint is documented separately here. The short version: batch-based delivery with validation gates between each one, seed-driven pricing tables instead of hardcoded macros, and Finance in the QA loop from day one.

February: Platform expansion

February is where the scope story gets interesting.

The original engagement had one product domain. In February, the team asked whether the same infrastructure could support analytics for other product lines — other product lines with their own analytics needs, plus a front-end segment data source that had been running on its own reporting stack.

The ask was "can you look at whether this is feasible?" It turned out to be more than feasible.

Because we had established a clear pattern for domain isolation — staging models per domain, schema routing, no cross-contamination — adding new domains was additive, not invasive. Each new domain got its own schema tier and could be developed and deployed independently of everything already running.

models/
├── staging/
│   ├── product_alpha/
│   ├── product_beta/         # added Feb
│   ├── product_platform/     # added Feb
│   ├── product_agent/        # added Feb
│   └── product_gamma/        # added Feb
├── intermediate/
│   └── [mirrors staging structure]
└── marts/
    └── [mirrors staging structure]

This is exactly the scenario domain isolation is designed for. The existing domains kept running. New domains came online without a big-bang migration. The engineering team went from 1 contributor to 4, each owning a distinct domain, without stepping on each other.

We also added two capabilities that hadn't been in the original scope: a Streamlit-in-Snowflake app for semantic querying (natural language against revenue data via Cortex Analyst), and a migration of the Sigma input table workflow so Finance could make pricing adjustments without engineering involvement.

Neither of these was in the original spec. Both emerged from the team understanding, after four months of working together, what was actually possible with the infrastructure they now had.

Value delivered per expansion: bar chart showing cumulative value across four phases — initial migration, validation depth, FY27 pricing, platform expansion — with annotations for the specific capabilities each phase unlocked


How we managed it operationally

Saying yes to scope growth is easy. Managing it without degrading existing deliverables is the hard part.

Three practices kept the project coherent through five months and 161 models across 7 domains.

Milestone tracking with explicit validation gates. Every phase ended with a formal validation milestone before we started the next one. December's ARR validation milestone ("0.002% variance across all revenue data — complete") was committed to the repo alongside the code that achieved it. January's FY27 pricing milestone ("all 9 batches validated — complete") was logged the same way. The history of the project is readable in the commit log.

# PLAN.md (excerpt, January milestone)

## FY27 Pricing Sprint

Status: COMPLETE ✓

Batches delivered:
- Batch 1-2: Seeds + core pricing macros ✓
- Batch 3-4: Regional multipliers + Snowflake JOIN+QUALIFY fix ✓
- Batch 5: Seed-driven v2 revenue models — validated 0.00% ✓
- Batch 6-7: Account discounts with Finance feedback ✓
- Batch 8: [blocked on partner data — skipped, completed with Batch 9] ✓
- Batch 9: ARR models with FY27 discounts — 265,638 rows, 0.00% variance ✓

Signed off by Finance: 2026-01-31

Domain isolation as a first-class constraint. We enforced a rule from week one: no model in domain A is allowed to ref() a model in domain B. Every mart is self-contained. Shared lookup data (fiscal calendar, regional metadata) lives in shared seed tables, not in another domain's models.

This sounds like discipline for its own sake. It becomes a business continuity feature when the scope grows. When February's expansion added four new domains, none of them required touching the existing billing models. The finance team's dashboards kept running. The new domains developed in parallel.

Communicating scope changes as distinct work packages. When we agreed to the FY27 pricing sprint and the platform expansion, we explicitly framed each as a new work package with its own scope, timeline, and acceptance criteria — not as an extension of the original project. This mattered for two reasons: it gave the team clear milestones to celebrate, and it made it easier to say "that's a Phase 4 question" when something came up during Phase 3.


When to say yes, when to say no

After five months of navigating this, here's the framework we'd use again.

Say yes when:

The new work is architecturally additive. If you can add it without modifying what's already running, the risk is low and the upside is high. New domains in an isolated multi-domain architecture are the clearest case.

The new work shares a validation dependency with existing work. If the FY27 pricing models needed to sit next to the revenue models to be validated, that's not scope creep — that's the natural shape of the problem.

The new work was always latent in the scope. "Migrating the data" implicitly required "Finance can trust the numbers," which required more validation depth than the original estimate anticipated. That's not scope growth. That's discovering the real scope.

The client's confidence in you is the driver, not organizational inertia. When a team asks for more because the first deliverable worked, that's a different conversation than when scope grows because nobody had a clear spec to begin with.

Say no when:

The new work requires modifying validated deliverables. If adding a new domain means touching the billing models Finance signed off on, you're taking on risk you can't easily price. Protect what's already validated.

The new work doesn't have a clear owner. Every expansion in this engagement had a named stakeholder who would be using the output and signing off on the results. If nobody owns it, it doesn't get properly scoped, validated, or handed off.

The timeline is driving the scope rather than the scope driving the timeline. If the ask is "can you add this by end of month" and the honest answer is "only if we cut corners," that's when scope creep stops being productive. The honest answer to that question matters more than the yes.

The original scope is still unvalidated. Don't expand before you've proven parity on what you already built.


The project that started as "migrate 377 legacy BI objects" ended as a revenue analytics platform running 161 models across 7 domains, with Finance self-service tooling, a semantic layer prototype, and a dev workflow that four engineers could use independently.

None of that was in the original spec. All of it was built on architecture that made it possible to say yes safely.

That's the scope growth worth having.

Topics

analytics engineering scope managementdbt domain isolationsnowflake analytics platformscope growth analyticsdbt project architecturemulti-domain dbtanalytics engineering best practicesBI migration strategydbt staging intermediate martsanalytics platform design
Share this article:
AC

Arturo Cárdenas

Founder & Chief Data Analytics & AI Officer

Arturo is a senior analytics and AI consultant helping mid-market companies cut through data chaos to unlock clarity, speed, and measurable ROI.

Ready to turn data into decisions?

Let's discuss how Clarivant can help you achieve measurable ROI in months.