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.
A complete migration workflow follows six coordinated phases:
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.
_sas2r.yml)Create a _sas2r.yml file in your clinical study
directory:
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: fallbackmax_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 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.
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.
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.
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.
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.
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.
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.
The library helpers separate input reads from generated writes during translation:
<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.sas2r keeps reference comparison answers out of
translation and repair agents:
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.migration.report_diagnosis: off disables them.
Advice cannot change validation or feed repairs. A 100% row-alignment
result does not mean equal values.THIS IS NOT PARITY: Automated translation by
sas2rdoes 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.