Case Study · Clínica Alemana · 2025–2027

Migrating 300 SAS Processes to BigQuery —
Without Migrating Blind.

~300 SAS Enterprise Guide processes, one deadline. The hard part was never writing SQL — it was the order. The visual order of steps in SAS is not the execution order. A deterministic engine resolves it, flags the risks, and lets AI translate against a single standard — so 300 migrations come out consistent, not 300 different ways.

~300 SAS processes in scope
16 / 16 Validated · executable order
5 → 48 Steps per process (range)
1 Master prompt for all 300

2025 — 2027 · Migration engine + governance · Role: Data Engineer & Migration Architect

BigQuery Python SAS EG Topological sort Standard SQL Dual-run Generative AI Excel governance Jira Oracle passthrough Kahn's algorithm

Migrating 300 processes by hand isn't slow because of volume. It's slow because of hidden order.

Clínica Alemana's clinical-studies reporting ran on ~300 SAS Enterprise Guide processes (.egp files). Each one produces the gestion tables that feed clinical studies — and each one is a graph of dozens of interdependent steps, with a hard deadline to reach BigQuery by 31 May 2027.

The trap is subtle. When you open an .egp, the steps appear in a visual order. But that order is not the execution order. In one real process, 24 of 32 nodes were out of sequence — a step reading a WORK table that a later step hadn't created yet. Translate in visual order and the SQL fails, or worse, runs and produces silently wrong data.

With a 2–3 person team, doing this by hand 300 times wasn't an option. The system had to resolve the order, flag what needs human judgement, and keep every translation consistent — so the migration could scale without losing control.

Without the engine

A developer opens each .egp, reverse-engineers the dependency graph by hand, guesses the order, and translates. The most expensive error — migrating wrong in silence — only surfaces weeks later in a dual-run mismatch nobody can explain.

With the engine

The engine resolves the true execution order deterministically, marks every risky pattern before translation, and hands the AI a process-specific prompt plus a shared master standard. The developer validates — they don't reverse-engineer.

A six-step pipeline. Each step consumes what the previous one produced.

The system is a Python pipeline of six ordered steps, plus two GUI apps. The order matters: extraction feeds lineage, lineage feeds governance, and the final step — the core — reorders each process and prepares the AI material. Nothing in the chain depends on the AI for correctness; the AI only translates what the deterministic code has already resolved.

Pipeline · .egp → migration contract
1Process EGPs
stable IDs · folders
2Global lineage
dependencies · waves
3Governance
Excel · dictionary
4Gantt
sprint plan
5Panel
HTML monitor
6Contracts
reorder · risks · AI
Data layers · raw mirror → cns final
SAS (source)Legacy .egp tables, fragmented across departments
raw_*Mirror in BigQuery — validation checkpoint for dual-run
cns_gestudio · _bqFinal curated table — the migrated output

Reordering isn't a heuristic. It's a topological sort over the real data dependencies.

The heart of the system reads every step's SQL, extracts which tables it creates and which it reads, and builds a dependency graph. Then it resolves the execution order with Kahn's algorithm (topological sort) — deterministic, reproducible, and verifiable. The same process always yields the same order, and the engine self-validates: no step may read a WORK table that an earlier step hasn't created.

This is the deliberate boundary: the deterministic code decides the order; the AI never does. The AI receives a correct sequence and only translates SQL.

Why determinism matters here
Reproducible

The same .egp always produces the same execution order. No prompt drift, no run-to-run variance.

Self-validated

After ordering, the engine checks that every WORK read resolves to an earlier create. If not, it flags a cycle for manual review.

Proven at scale

Validated on 16 real processes, 5 to 48 nodes. The 48-node case has one table feeding 11 parallel branches — it resolved cleanly.

Parser coverage · 6 real-world SAS patterns absorbed
Literal SAS namesWORK."PRESUP1"n → normalized
Implicit joinsFROM a t1, b t2 with aliases
Oracle passthroughexcluded — not a data table
Missing codeflagged, not silently skipped
CREATE VIEWPROC TRANSPOSE temp tables
Multi-table DATA stepOUT=, INSERT, APPEND, MERGE

Before translating, the engine warns about what will break in dual-run.

Every migration has points only a human should decide. Instead of letting the AI guess, the engine scans each process and surfaces the risky patterns up front, in the prompt and in the governance Excel. One real scan found a hardcoded Oracle password in plain text — password=aqgestudio — and flagged it as a security risk that must never reach BigQuery.

Oracle passthroughNot data: connect to oracle blocks are grants/DDL. Resolved as IAM/permissions, not SQL.
Plain-text credentialsPasswords detected in source. Flagged: never port to BigQuery — use Secret Manager.
SAS date functionsMDY, DATEPART, INTNX — the #1 source of dual-run differences.
SAS macros%MACRO / &var require redesign — BigQuery has no macros.
Sequential logicRETAIN, FIRST., LAST. rewritten as window functions.
Code not extractedNodes with no code in the .egp cache. The contract is incomplete — review the original.
End-to-end traceability: the same alert surfaces in four places — the contract markdown (🚫 SIN CÓDIGO per step), the AI prompt ("special attention"), the global index, and the governance Excel (a red-highlighted row with a comment). The alert lives wherever a person might make a wrong decision.

Rules written once, consistent across 300 processes. The master prompt closes the gap.

A subtle failure mode appeared early: the AI made inconsistent architecture decisions. One process used CTEs and CREATE OR REPLACE; another used temp tables and DELETE + INSERT for the same shape of problem. Both work — but 300 processes in 300 different styles is unmaintainable technical debt.

The fix separates the WHAT from the HOW. The process prompt carries the what (this process's ordered steps, tables, risks). The master prompt carries the how (fixed architecture, a SAS→BigQuery date recipe, output format, dual-run checklist) — written once, attached to all 300.

The WHAT · per process

Ordered steps, source and target tables, detected risks. Unique to each .egp. This is what changes 300 times.

The HOW · master prompt

CTEs by default, temp only on 3+ reuse. A date/type recipe. A target-decision tree. TODO[CATEGORY] tags for human review. Written once.

AI marks every uncertain decision with a category — so the reviewer filters fast:
TODO[DATE] TODO[TYPE] TODO[ORACLE] TODO[MACRO] TODO[WINDOW] TODO[BUSINESS] TODO[SCHEMA]

Nothing is "migrated" until SAS and BigQuery agree. Dual-run is the close.

Every candidate query closes with a dual-run checklist — row counts, sums of key numeric columns, and a date sample compared between SAS and BigQuery. The raw layer is kept precisely as this validation checkpoint. The maturity curve tells the story: across six batches of real cases, the engine absorbed five parser patterns, then one, then zero, then zero. It reached stability.

16 / 16 Executable order Every validated process produces a runnable sequence — verified, no premature reads.
48 Max nodes resolved One table feeding 11 parallel branches. The hardest real case passed cleanly.
5→1→0 Findings per batch The discovery curve flattened — the parser now covers the dominant SAS profile.
The honest limit: the engine accelerates, it doesn't replace judgement. Business logic, dual-run validation, and Tier-3 processes with %MACRO still need a person. Recognising that precisely is what makes the system trustworthy — it tells you when something needs a human, instead of failing in silence.
"The most expensive error in a mass migration is migrating wrong in silence. The whole system exists so that can't happen." — Working principle from this project

Systems that fail safe, not silent.

This case shows how I turn an unbounded, error-prone migration into a deterministic, traceable, semi-automated system — resolving execution order with graph algorithms, surfacing risk before it bites, and using AI where it adds leverage without ceding correctness.

Contact Javier