---
title: "Dependency-Aware SAS-to-R Migration"
author: "Songyue Chen"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Dependency-Aware SAS-to-R Migration}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

<!-- sas2r-example: offline knitr-setup -->
```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Overview

`sas2r` provides a dependency-aware migration framework that translates SAS programs and multi-module clinical pipelines into modular, reproducible R bundles.

`sas2r` analyzes macro definitions, `%include` relationships, dataset lineages, and configured outputs to schedule programs in dependency order. Unresolved dependencies and unsupported source constructs remain visible in the report.

## Migration Pipeline Architecture

A complete migration workflow follows six coordinated phases:

1. **Dependency analysis and scheduling**: Discovers module dependencies, macro definitions and data references. A deterministic coordinator assigns dependency-ready programs and called macros; cycles and unresolved dependencies remain findings.
2. **Translation and initial review**: Builds a deterministic baseline, hands unsupported source to the configured translator, checks the candidate and obtains independent review. Independent components can overlap in separate R processes when parallel mode is enabled.
3. **Immediate checks and repair**: Runs smoke execution and bounded source-grounded repairs one component at a time, including re-review and checks of affected consumers. Dependents wait until their providers finish this processing.
4. **Final review checkpoint**: Holds the selected code and helpers fixed, reuses reviews whose context still matches, and reviews remaining components. These reviews can overlap but do not repair code during the pass.
5. **Bundle execution and output checks**: Runs the complete generated bundle in dependency order inside an isolated subprocess using copy-on-write library registries. It checks required datasets and TLFs, compares configured dataset references, and runs bounded source-grounded repair followed by fresh execution. This phase remains serial.
6. **Authoritative reporting**: Produces machine-readable (`report/report.json`) and human-readable (`report/translation.md`) evidence, including unavailable checks and unresolved findings.

The coordinator owns selected revisions, checkpoints and the shared usage budget.
Each process uses the existing translator, reviewer and fixer context builders,
tools and settings with a snapshot of the selected dependencies. Processes report
results to the coordinator; they do not share a live conversation. In parallel
mode, an unresolved dependency finding defers its component and known descendants
while independent work continues. Required deferred execution or incomplete
review prevents the run from becoming `migration_ready` or `validated`; inspect
the reported status and reason for the affected components.

## Configuring a Clinical Study Migration (`_sas2r.yml`)

Create a `_sas2r.yml` file in your clinical study directory:

```yaml
project: cdsc_pilot_study

libraries:
  sdtm:
    path: data/sdtm
    engine: xpt
  adam:
    path: data/adam
    engine: xpt
    write: rds

outputs:
  datasets:
    - adam.adsl
    - adam.adae
    - adam.advs_bds

  # Golden SAS reference datasets for automated validation
  references:
    adam.adsl: data/reference/adsl.xpt
    adam.adae: data/reference/adae.sas7bdat
    adam.advs_bds: data/reference/advs.rds

  assertions:
    adam.adsl:
      keys: [STUDYID, USUBJID]
      numeric_tolerance: 1e-6
    adam.advs_bds:
      keys: [STUDYID, USUBJID, PARAMCD, AVISITN, ATPTN]
      numeric_tolerance: 1e-6

llm:
  provider: gemini              # or deepseek, openai, anthropic
  model: gemini-3.8-flash
  reasoning_effort: high
  max_output_tokens: 65536
  timeout_seconds: 900
  max_tries: 1
  capabilities:
    tool_calling: native
    structured_output: fallback
```

`max_parallel_translations` counts concurrent program or macro workflows, including
their reviews and repairs. It does not allocate one slot per agent role or per
CPU. Establish a baseline at 1, then evaluate 2 against the same source, inputs
and quality checks. Higher values depend on provider capacity and local memory;
local execution and repair remain serial. Set it with the `sas_translate()`
argument `max_parallel_translations`. Parallel mode is opt-in while
live quality and performance validation remains outstanding.

Gemini Flash and DeepSeek Flash are suggested starting points for evaluation;
stronger frontier models remain available. Choose using source review and output
evidence for the actual study. See the [provider settings guide](https://github.com/songyue-chen/sas2r/blob/main/docs/llm-providers.md)
for provider-specific output limits, timeouts and capabilities.
The adapter preserves native provider conversation history through tool calls.
Offline replay tests cover tool continuation and parallel workers on ellmer
0.5.0; they do not establish live-model translation quality or study-level accuracy.

## Inspect the setup offline

<!-- sas2r-example: offline vignette-preflight -->
```r
library(sas2r)
migration_dir <- tempfile("sas2r-migration-")
check <- sas_preflight("programs/",   # the folder of SAS programs to translate
                       config = "_sas2r.yml",
                       out_dir = migration_dir, diagnose = "off")
print(check)
check$libraries
check$inputs
check$unsupported
stopifnot(check$model_calls == 0L)
```

This reads source files and directory listings without model calls, dataset
reads, program execution, or output writes. The program folder is the `path`
argument, not a configuration key; a single `.sas` file also works, and
`recursive = TRUE` includes subfolders. Reuse `check$project` for
translation while sources remain unchanged. `migration.execution_order` can name
all executable root programs in their intended shared-session order; each WORK
read then uses the latest preceding completed write. Included programs execute
at their call sites. When the order is omitted, dependency inference remains the
default. With `diagnose = "auto"`, configured AI can suggest corrections for
findings in one bounded request; advice never overrides static checks.
Use `qc_profile()` with `outputs$profiles` and a named `profile` in each target's
assertions to require labels, formats, types/order, keys, uniqueness, row counts,
and per-variable reference tolerances. These checks add study requirements;
passing them alone does not establish SAS reference agreement.

## Declare a shared-session program order

This complete offline example inspects three programs. The second program reads
the first version of `work.tmp`, then replaces it. The third reads that replacement.

<!-- sas2r-example: offline vignette-execution-order -->
```r
ordered_study <- tempfile("sas2r-ordered-study-")
dir.create(ordered_study)
writeLines("data work.tmp; value=1; run;",
           file.path(ordered_study, "create.sas"))
writeLines("data work.tmp; set work.tmp; value=value+1; run;",
           file.path(ordered_study, "replace.sas"))
writeLines("data work.final; set work.tmp; run;",
           file.path(ordered_study, "read.sas"))
ordered_config <- list(migration = list(
  execution_order = c("create.sas", "replace.sas", "read.sas")
))
ordered_check <- sas_preflight(ordered_study, config = ordered_config,
                               diagnose = "off")
ordered_check$pipeline$execution_order
ordered_check$inputs[c("dataset", "status")]
stopifnot(ordered_check$pipeline$order_source == "configured",
          all(ordered_check$inputs$status == "generated"),
          ordered_check$model_calls == 0L)
unlink(ordered_study, recursive = TRUE)
```

For an R configuration list, these paths are relative to the scanned project
directory. In YAML they are relative to the configuration file. List every root
program exactly once; called macros, startup and includes keep their existing
roles. The order does not change which files `path` and `recursive` select.
Rescan the sources when changing an order on a previously scanned project.

Explicit order applies to translation, smoke checks and final execution.
Ordered roots translate sequentially, and smoke checks replay the preceding
session. Unknown macro effects can still leave state deferred. A later writer
does not make an earlier absent permanent input available. Omitting the order
retains dependency inference.

## Running a Migration in R

To migrate a single SAS script or an entire directory of SAS files, call
`sas_translate()`. These examples write under the R session temporary directory;
supply your own destination when you want to retain the files.

<!-- sas2r-example: network migration -->
```r
library(sas2r)

# Load API keys from environment
readRenviron("~/.Renviron")

# Translate and execute the study migration
result <- sas_translate(
  path = check$project,
  out_dir = migration_dir,
  max_parallel_translations = 1L,   # programs or macros translated at once
  execute = TRUE,
  max_program_repair_rounds = 1L,
  max_bundle_repairs_per_component = 2L,
  max_bundle_repair_rounds = NULL
  # Optional limits, commented out by default. Uncomment to enforce them; the
  # run stops admitting requests when a limit is reached. Requests per program vary by model
  # and study; size ceilings from a completed run's usage summary.
  # Catalog costs are estimates; an in-flight request may exceed the threshold.
  # , budget_usd = 10, budget_mode = "soft"
  # , usage_limits = list(max_calls = 700, max_tool_calls = 700, max_wall_time = 14400)
)

# Inspect the result object
print(result)
cat("Status:", result$status, "\n")
cat("Bundle Directory:", result$bundle_dir, "\n")
cat("Outputs Directory:", result$outputs_dir, "\n")
cat("Report:", result$report_path, "\n")

# View generated R code
cat(sas_code(result))

# Export selected code, generated outputs, reports, and the dependency/input guide
export_dir <- tempfile("sas2r-export-")
sas_write(result, export_dir)
```

Outputs preserve library subdirectories: ADSL is at
`file.path(result$outputs_dir, "datasets", "adam", "adsl.rds")`. The export contains a
dependency-ordered `run.R`, `run-order.json`, and a README with run/edit/move instructions.
Programs and macros are in `programs/` and `macros/`; saved automated outputs
are under `saved-outputs/`, separate from manual writes to `output/`. From the export directory, use `Rscript run.R` or
`source("run.R", chdir = TRUE)`. Install the R dependencies listed in its guide
and update input paths in `autoexec.R` and explicit source LIBNAME statements
when inputs move. Input datasets are external dependencies; the exported report
describes the original selected execution and is not refreshed by a manual rerun.

Repeat the same migration call with `resume = TRUE` to reuse saved revisions and
completed reviews when source, inputs, configuration, runtime, and prompts still
match. Changing only the parallel translation limit does not invalidate reuse.
Missing or changed artifacts regenerate and unavailable reviews are retried.
Passing or deferred smoke results can be reused when their full context matches;
full-bundle execution and output checks use fresh attempts. Parallel checkpoints
distinguish drafts from components that have completed immediate processing, and
save final reviews individually. Repair and recorded revisit counts survive
resume. The usage ledger remains cumulative across resumed runs. Every limit is
commented out by default so a run finishes without interruption; uncomment
`budget_usd` or a `usage_limits` ceiling only if you want the run to stop when it
is reached; requests per program vary by model and study, so size a ceiling
from a completed run's usage summary. Unknown-cost calls must not be read as
zero spend.

### Four Canonical Bundle Statuses

Each translation attempt resolves to one of four authoritative statuses:

- **`validated`**: Execution, required output checks, and lineage requirements pass, with a passing reference comparison for at least one required target. Other outputs can remain unreferenced.
- **`migration_ready`**: Execution, required output checks, and lineage requirements pass without passing reference evidence for a required target.
- **`needs_review`**: Execution was deferred (`execute = FALSE`), or required review/lineage evidence is incomplete or blocked. Programs may still have executed successfully.
- **`blocked`**: Bundle execution failed or required output checks failed, including missing/unreadable datasets and reference mismatches. Unavailable dependencies defer affected components while independent components can still execute.

Inspect coverage as well as status. The reports separate produced,
reference-compared, passed, and reference-passed outputs and list
`validated_targets` and `unreferenced_targets`. A `validated` bundle is not a
claim that every output was compared.

### Review completion and reference comparability

`reviewer ...: ok` means the agent call completed. Its semantic verdict is in the
review record; `coordinator ...: reviewed` can still be followed by repair.
Mechanical checks and smoke tests can pass while the reviewer finds a logic
issue. A new repaired revision is checked and reviewed again.

Reference datasets must correspond to the supplied SAS source, input snapshot,
formats, macros, and parameters. Different subject inclusion rules, KEEP lists,
or analysis visit definitions can produce discrepancies even when the generated
R follows the source. Trace differences to the source and inputs before treating
them as translation errors. Independent R reconstruction is useful diagnostic
evidence but is not an execution by SAS.

## Component Evidence Ladder

Within each bundle, individual components begin without an evidence level and
accumulate evidence bound to their revision:

- `reviewed_only`: Evaluated by an independent reviewer without material issues.
- `runtime_verified`: A reviewed revision has execution coverage from a smoke attempt or full bundle execution.
- `output_verified`: The component's output requirements passed the migration gate.
- `reference_validated`: Output lineage has passing reference-comparison evidence under configured tolerances.

Smoke outcomes are recorded separately. A smoke pass with unavailable review
does not earn a higher evidence level. Reference provenance is established
separately from the comparison result.

## Attempt Isolation & Copy-on-Write

The library helpers separate input reads from generated writes during translation:

- The library registry reads original input members and directs generated writes
  to attempt-specific output paths. This is a helper-level convention, not a
  read-only filesystem mount or sandbox; arbitrary generated R can still access
  files outside the registry.
- Write targets and scratch WORK libraries are isolated under `<run_id>/diagnostics/bundle_attempts/bundle_attempt_001/`. Every run has its own timestamp-first folder. Open its `START_HERE.html` for reports and all available selected code in `bundle/`. Saved deliverables go to `outputs/`; requested WORK datasets count as deliverables, while unrequested scratch stays in diagnostics. Manual reruns write to `bundle/output/` and do not change the original evidence.
- Multi-round repairs select improved attempts deterministically and prune rejected attempts according to configuration.

## Model Privacy & Clinical Data Protection

`sas2r` keeps reference comparison answers out of translation and repair agents:

- **Transmission Boundary**: Prompts carry SAS and R code, input schema metadata, helper interfaces and execution diagnostics. Reference comparison summaries, digests, values and reports stay in local reports and the public comparison API. Project tool overrides cannot restore comparison access for these agents.
- **Agent evidence**: Both `code_only` and the legacy `bounded` policy exclude dataset row numbers, subject identifiers, key values, cell values, previews and raw runtime messages. Focused mismatch investigation receives approved aggregate patterns; fixers and candidate acceptance remain reference-blind. Source code and comments are not automatically de-identified.
- **Human diagnosis**: The start page shows dataset dimensions, row alignment, shared column names, measured differences, repair decisions and numbered source/code links. AI explanations reuse current reviews or make one bounded request under the existing usage budget; `migration.report_diagnosis: off` disables them. Advice cannot change validation or feed repairs. A 100% row-alignment result does not mean equal values.
- **Data Residency**: All dataset reading, execution, and comparison run in local R processes on your infrastructure; confirm your configured endpoint meets your organization's data residency obligations before enabling an LLM provider.

## Parity Disclaimer

> **THIS IS NOT PARITY**: Automated translation by `sas2r` does not guarantee regulatory equivalence or replace independent statistical validation. All migrated R code and generated artifacts must be formally validated according to standard clinical operating procedures prior to regulatory submission.
