/*--------------------------------------------------------------------------------------\ | PROGRAM NAME: | | scdm_control_flow.sas | | | |---------------------------------------------------------------------------------------| | PURPOSE: | | The purpose of this program is to define selective and sequential execution | | of QA modules in the program 00.0_scdm_data_qa_review_master_file.sas. | |---------------------------------------------------------------------------------------| | MAJOR STEPS PERFORMED BY THE PROGRAM: | | | | 0- Perform setup for this macro | | 1- Clean up control flow dataset to enable safe and consistent querying | | 2- Retrieve modules explicitly chosen to run (i.e. where execute_flag=y) | | 3- Ensure that all the necessary modules are run given dependencies | | a) Modules for optional tables are only selected if defined in CC | | b) If any utilization modules are selected, all utilization modules | | will be run | | 4- Set up macro variable lists for processing each module | | 5- Loop through and execute each module | | 6- Call request-level "signature file consolidation" and "log checker" macros | | 7- Run scdm_snapshot | | 8- If specified, run mother-infant identification | | 9- If specified, run qa_common_components | | | |---------------------------------------------------------------------------------------| | DEPENDENCIES/CONSTRAINTS/CAUTIONS | | This macro is to be called by 00.0_scdm_data_qa_review_master_file.sas. | | Please refer to the master program for details. | | | |---------------------------------------------------------------------------------------| | PROGRAM INPUT: | | see scdm_data_qa_review_master_file.sas | | | | PROGRAM OUTPUT: | | see Workplan PDF | |---------------------------------------------------------------------------------------| | CONTACT: | | Sentinel Coordinating Center | | info@sentinelsystem.org | \*-------------------------------------------------------------------------------------*/ *-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-; * PLEASE DO NOT EDIT BELOW WITHOUT CONTACTING THE SENTINEL OPERATIONS CENTER ; *-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-; %global end_qa module table; /***************************************************************************************************/ /* QA LOCAL SET-UP AND PROCESSING */ /***************************************************************************************************/ %macro run_qa_package; proc printto log="&msoc.qa_package.log" new; run; %* overall processing macros; %let end_qa=0; %* Include standard and utility macros; %include "&INFOLDER.macros/scdm_standard_macros.sas" /nosource2; %include "&INFOLDER.macros/qa_create_l2_xtable_ds.sas"/nosource2; %include "&INFOLDER.macros/_customize_input.sas" /nosource2; %include "&INFOLDER.scdm_sas_log_checker_directory_cc.sas" /nosource2; %include "&INFOLDER.scdm_qasignaturerequest.sas" /nosource2; %include "&INFOLDER.scdm_formats.sas" /nosource2; %include "&INFOLDER.scdm_data_qa_review-partition.sas" /nosource2; %soc_lib(DPLOCALO,&DPLOCAL) ; /* always point to main dplocal libary - needed for partitioned data*/ %* validate patid to partition crosswalk file; %partition_meta_chks; %partition_expected_scdmtab_chks; %partition_expected_scdmpart_chks; %partition_appropriate_table_chks; %partition_dup_chks; %partition_consistent_fmt_chks; /* Initialize global macro variables */ /* list of tabid/tablenames without patid column - in lower case*/ %global/readonly tabid_exc=fac pvd prs ; %global/readonly sql_tabid_exc="fac", "pvd", "prs" ; /* key variable not partitioned (in lower case) */ %global/readonly varname_exc=facilityid providerid surveyid questionid questionverid prm_code ; %global/readonly sql_varname_exc="facilityid", "providerid", "surveyid", "questionid", "questionverid", "prm_code"; %**** list modules that should not run in parallel(in upcase) ; %global/readonly module_nospawn_sql=%str("L1", "L2", "L3", "DATES", "OVLAP") ; %global/readonly module_nospawn=%str(L1 L2 L3 DATES OVLAP) ; %global/readonly do_partitions= 1 ; %global sql_tabidlist tabidlist tablename_sorted sql_tablename_sorted; %global prefix_length pnum_length ; %let tabidlist = ; %let sql_tabidlist = ; %let tablename_sorted = ; %let sql_tablename_sorted = ; %let prefix_length = 11 ; %let pnum_length=%sysfunc(length(&numpartitions)) ; %put |&=pnum_length| ; %global SPAWN SPAWNED dplocalo ; %if &NumSession ge 1 and &SASConnect eq Y %then %do; /* macro vars if spawning */ %let SPAWN=1; %include "&INFOLDER.macros/remote_macros.sas" /nosource ; %let dplocalo=dplocalo ; %end ; %else %do; /* macro vars if not spawning */ %let SPAWN=0; %let SPAWNED=0 ; %let dplocalo=dplocal ; %end ; %put |&=SPAWN| |&=dplocalo|; %* read-in control flow input file ; data control_flow; infile "&INFOLDER.control_flow.csv" dlm=',' dsd truncover firstobs=2; informat module $5. ; informat execute_flag $1. ; informat sascode $45. ; informat cc_table $12.; informat seqno best.; format module $5. ; format execute_flag $1. ; format sascode $45. ; format cc_table $12.; format seqno best. ; format unique_id $15. ; input module :$5. execute_flag :$1. sascode :$45. cc_table :$12. seqno :8. unique_id :$15.; /* standardize capitalization */ module = lowcase(module) ; execute_flag = lowcase(execute_flag) ; unique_id = lowcase(unique_id); run; proc sort data=control_flow; by seqno; run; proc sql noprint; select strip(module), quote(strip(module)) into :tabidlist separated by ' ', :sql_tabidlist separated by ', ' from work.control_flow where upcase(execute_flag) = 'Y' and cc_table ^= 'X'; select symget(cc_table), quote(symget(cc_table)) , max(length(symget(cc_table))) into :tablename_sorted separated by ' ' , :sql_tablename_sorted separated by ', ' , :prefix_length trimmed from work.control_flow where upcase(execute_flag) = 'Y' and cc_table ^= 'X' order by 1 ; ;quit; **** cross_varID ; %global cross_varID sql_cross_varID retain_ds ; %let cross_varID= ; proc sql noprint; select distinct(strip(lowcase(variable))), quote(strip(lowcase(variable))) into :cross_varID separated by ' ', :sql_cross_varID separated by ', ' from infolder.lkp_all_l1 where upcase(crossvar)="X" ; ;quit; %let retain_ds = patid encounterid providerid facilityid questionid questionverid prm_code surveyid; %* customize input files as necessary; %kill_directory (kill_list=custom); %* defensive coding - remove content from customs folder to avoid issues with reruns of package; %customize_input; %* defensive pre-check: abort program if NumPartitions is not ge 1 *; %if (%isBlank(&NumPartitions)=1 or &NumPartitions <1) %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: NumPartitions must be >=1. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The NumPartitions parameter must be populated with whole number >=1'; put '==> Please go back to the master program and populate NumPartitions with valid value.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* defensive pre-check: abort program if NumPartitions>1 and ParTable is null *; %if %isBlank(&ParTable.)=1 and (&NumPartitions.>1) %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: ParTable parameter not populated. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The ParTable parameter cannot be blank'; put '==> Please go back to the master program and populate ParTable.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* defensive pre-check: abort program if _SasGrid=Y and _GridSrv is null *; %if &SasGrid=Y and %isBlank(&GridSrv)=1 %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: GridSrv parameter not populated. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The GridSrv parameter cannot be blank if SasGrid=Y.'; put '==> Please go back to the master program and populate GridSrv.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* defensive pre-check: abort program if _SasGrid=Y and _SASConnect ne Y *; %if &SasGrid=Y and &SASConnect ne Y %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: SASConnect parameter has an unexpected value. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The SASConnect parameter must be Y if SasGrid=Y.'; put '==> Please go back to the master program and populate SASConnect as Y.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* defensive pre-check: abort program if NumSession is not a positive integer or 0 *; %if &NumSession ne %sysfunc(int(&NumSession)) %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: NumSession parameter has an unexpected value. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The NumSession parameter must be a positive integer or 0.'; put '==> Please go back to the master program and populate NumSession correctly.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* defensive pre-check: abort program if Execute_CC=Y and _ROOT_DPLOCAL variable not populated *; %if &Execute_CC=Y and %isBlank(&_ROOT_DPLOCAL)=1 %then %do; data _null_; put 90*'!'; put ' '; put 'Defensive Pre-Check: _ROOT_DPLOCAL parameter not populated. RUN_QA_PACKAGE macro is aborting.'; put ' '; put '==> The _ROOT_DPLOCAL parameter cannot be blank if Execute_CC=Y.'; put '==> Please go back to the master program and populate _ROOT_DPLOCAL.'; put ' '; put 90*'!'; put ' '; run; %abort cancel 99 ; %end ; %* confirm ETL value is valid *; %* identify ETL values as non-digit (includes missing); %if %sysfunc(notdigit(&ETL)) %then %do; data _null_; put 80*'!'; put ' '; put 'Pre-Module Check 2: ETL er'"ror detected. RUN_QA_PACKAGE macro is aborting. "; put ' '; put "==> The ETL parameter cannot be blank or include any non-numeric value(s). Please contact SOC DMQA for next steps."; put ' '; put 80*'!'; put ' '; run; %abort cancel 99 ; %end ; %* Create ETL Version table *; %if %sysfunc(exist(msoc.etl_version,data)) %then %do; /* begin if etl_version ds exists */ %put ; %put ==> An ETL_version table already exists and will be reviewed to; %put ==> ensure that the ETL in the ETL_version table is equal to the expected ETL; %put ; proc sql noprint; select etl into :ETL_VersionTable trimmed from msoc.etl_version ; quit; %put ; %put ====> CURRENT ETL: &ETL. ; %put ====> ETL from ETL_Version table in this QA package MSOC folder: &ETL_VersionTable.; %put ; %if &ETL.=&ETL_VersionTable. %then %do; %put ; %put ==> ETL Versions match and the program will continue; %put ==> HOWEVER, unless otherwise specified in the master program, all ; %put ==> existing SAS datasets in the local output directories ('dplocal','msoc'); %put ==> will be deleted for data quality assurance purposes ; %put ; %end; %else %do; data _null_; put 70*'!'; put ' '; put "Pre-Module Check 3: Different ETL content detected in MSOC library. RUN_QA_PACKAGE macro is aborting."; put ' '; put ' '; put 70*'!'; run; %abort cancel 99; %end; %end; /* end if etl_version ds exists */ %kill_directory (kill_list=dplocal msoc); data msoc.etl_version; length DP $6; dp=upcase("&dpid."); ETL=&ETL.; run; %if %sysevalf(&syscc.>4) %then %do; data _null_; putlog 80*'!'; putlog ' '; putlog 'Pre-Module Check 4: System er'"ror detected. RUN_QA_PACKAGE macro is aborting."; putlog ' '; putlog ' '; putlog 80*'!'; run; %abort cancel 99 ; %end ; %* ensure that the site has populated all expected table names in the master file *; %local t tabct abortct tablelist; %let tabct=0; %let abortct=0; proc sql noprint; select cc_table , count(*) into :tablelist separated by "*" , :tabct trimmed from control_flow (where=(lowcase(cc_table) ne 'x' and execute_flag='y')) ; quit; %do t=1 %to &tabct.; %let table=%scan(&tablelist.,&t.,%str(*)); %let table=%qleft(%qtrim(&table)); %if %length(%superq(&table)) = 0 %then %do; %let abortct=%eval(&abortct. + 1); data _null_; put 70*'!'; put "SCDM &table. parameter not specified in master progam"; put "but defined by SOC inputfiles/control flow.csv file "; put ' '; put 70*'!'; run; %end; %end; %if "&abortct." ne "0" %then %do; data _null_; put 70*'!'; put ' '; put "Pre-Module Check 5: RUN_QA_PACKAGE macro is aborting because an expected SCDM table "; put "name was not defined by your site for the ETL under review. "; put ' '; put 70*'!'; run; %abort cancel 99; %end; %* ensure that the site has NOT populated an unexpected table names in the master file *; %local t tabct abortct tablelist; %let tabct=0; %let abortct=0; proc sql noprint; select cc_table , count(*) into :tablelist separated by "*" , :tabct trimmed from control_flow (where=(lowcase(cc_table) ne 'x' and execute_flag='n')) ; quit; %if %eval(&tabct. ge 1) %then %do; %do t=1 %to &tabct.; %let table=%scan(&tablelist.,&t.,%str(*)); %let table=%qleft(%qtrim(&table)); %if %length(%superq(&table)) ne 0 %then %do; %let abortct=%eval(&abortct. + 1); data _null_; put 70*'!'; put ' '; put "SCDM &table. name is defined by DP, but not expected"; put "at the site. Only the SCDM tables defined by SOC in "; put "inputfiles/control_flow.csv file should be present in the "; put "directory and named in the master program. "; put ' '; put 70*'!'; run; %end; %end; %if "&abortct." ne "0" %then %do; data _null_; put 70*'!'; put ' '; put "Pre-Module Check 6: RUN_QA_PACKAGE macro is aborting because an unexpected SCDM table"; put "name was defined by your site for the ETL under review."; put ' '; put 70*'!'; run; %abort cancel 99; %end; %end; %* ensure ZIP3 populated with valid value *; %if %isBlank(&demtable)=0 %then %do; proc sql noprint; select length into :ZIPLEN trimmed from dictionary.columns where upcase ( libname ) = 'QADATA' and upcase ( name ) = 'POSTALCODE' and PRXCHANGE ('s/\d+$//', 1, trim(lowcase(memname)) ) = lowcase( "&demtable" ) ; quit; %if &ZIP3 = Y %then %do ; %if &ZIPLEN gt 3 %then %do; data _null_; put 70*'!'; put ' '; put "Pre-Module Check 7: RUN_QA_PACKAGE macro is aborting because ZIP code length"; put "of three was indicated, but longer ZIP codes detected in DEMOGRAPHIC table."; put ' '; put 70*'!'; run; %abort cancel 99; %end; %end ; %if &ZIP3 eq %then %do ; %if &ZIPLEN le 3 %then %do; data _null_; put 70*'!'; put ' '; put "Pre-Module Check 7: RUN_QA_PACKAGE macro is aborting because ZIP code length"; put "of three was detected, but not indicated for DEMOGRAPHIC table."; put ' '; put 70*'!'; run; %abort cancel 99; %end; %end; %end; /* prepare file that includes list of modules and accompanying SAS code to be executed and */ /* verify all required logic will be executed with respect to dependencies in QA package */ /* Create separate control_flow dataset for CC*/ data msoc.cc_control_flow; set control_flow; run; data msoc.control_flow_3; set control_flow(where=(execute_flag = 'y')); by seqno; run; /* Conditionally execute module, aborting processing and reviewing errors when error(s) detected */ %let module_list=; %let sascode_list=; %* load modules and code to run into macro parameters to be used in do loop; proc sql noprint; select module , sascode , seqno into :module_list separated by ' ' , :sascode_list separated by '~' , :seqno_list from msoc.control_flow_3 order by seqno ; quit; %put |&=module_list| ; %let total_starttime = %sysfunc(datetime()); %if &SPAWN %then %do ; *Initialize connections to remote SAS Grid or SAS/CONNECT sessions*; %signon ; %end; %*Loop through and execute each module in sequence ; %do z = 1 %to %sysfunc(countw(&module_list)); %kill_directory (kill_list=work); %if %length(&module_list) > 0 %then %let module = %scan(&module_list., &z.); %else %let module = ; %if %length(&sascode_list) > 0 %then %let sascode = %scan(&sascode_list., &z.,~); %else %let sascode = ; %let tabid = %sysfunc(strip(&module)); %put |&=tabid| |&=z| |&=end_qa| ; %if &end_qa.=0 %then %do; data _null_; put 75*'-'; put ' '; put "==> Begin Execution of module: &module., sascode: &sascode..sas"; put ' '; put 75*'-'; run; %if &SPAWN %then %do ; %* some module do NOT need to be run in parallel ; %if not %eval(%upcase(&module) in &module_nospawn) %then %do ; %waitForAvailableSession ; %let SPAWNED=1 ; %put |&=SPAWNED| |&=module| |&=&openSessID| |&=z| |&=end_qa| ; %syslput _USER_ /remote=mySess&openSessID.; %* pass all user macro var to spawned process ; rsubmit mySess&openSessID. wait=no cmacvar=myRsubmitVar&openSessID. inheritlib=(dplocal=dplocalo msoc qadata infolder) ;; %nrstr(%put ..Start of rsubmitted code for &tabid --- vvvvvvvvvvvvvvvvvvvv ;) ; %include "&INFOLDER.macros/do_module.sas" ; /* pass macro variable from spawned to original dplocal folder */ %nrstr(%let &module._end_qa=&end_qa) ;; proc sql noprint; create table dplocalo.macvalue_&module. as select name, value, offset from dictionary.macros where substr(upcase(name),1,length("&module")+1) in (%upcase("&module._")) or upcase(name) in ("END_QA") order by name, offset ;quit; %nrstr(%put ..End of rsubmitted code for &tabid --- ^^^^^^^^^^^^^^^^^^^^^^ ;) ; endrsubmit; %end ; %else %do ; /* no-spawn module(except L1). L1 is run in the module and do not need to run here */ %if not %eval(%upcase(&module) in L1 ) %then %do ; * Wait for all remote sessions to complete; WAITFOR _all_ %do s = 1 %to &numSession.; mySess&s. %end; ; %let SPAWNED=0 ; %include "&INFOLDER.macros/do_module.sas" ; %end ; %end; %end; /* if spawn */ %else %do ; /* if not spawning - run modules one by one in sequence */ %let SPAWNED=0 ; %include "&INFOLDER.macros/do_module.sas" ; %end; %* not %eval(&SPAWN and %upcase(&module) in L1 ) ; %if &end_qa. ne 0 %then %do; %let z=99; %end; %end; %* %if &end_qa.=0 ; %end; %* end loop z - loop thru modules; %if &SPAWN %then %do; *Close connections to remote SAS Grid or SAS/CONNECT sessions*; %signoff ; %end; %let total_stoptime = %sysfunc(datetime()); /* Stack runtimes datasets, sum them for total runtime */ data msoc.runtimes(keep=module starttime stoptime runtime); set dplocal.runtimes_: end=last; output; if last then do; module = "Total RunTime"; StartTime=put(&total_starttime,e8601dz.); StopTime=put(&total_stoptime,e8601dz.); diff_times = &total_stoptime - &total_starttime; hour = hour(diff_times); minute = minute(diff_times); second = floor(second(diff_times)); runtime = catx(" ", hour , "h", minute, "m", second, "s"); output; end; run; proc datasets lib=dplocal nolist nodetails; delete runtimes_:; run; %* If qa did not abort and masking specified in the master program file, call the results masking program *; %if &z.<99 %then %do; %if %lowcase(&execute_mask.)=y and %length(&masktabid_list) > 0 %then %do; %inc "&infolder.scdm_data_qa_review-maskresults.sas"; %end; %end; %soc_qasignaturerequest; proc printto; run; %* Run scdm_snapshot program *; %snapshot_run %* If specified in the master program file, call the Mother-Infant Identification program *; %if %lowcase(&execute_MI.)=y %then %do; proc printto log="&msoc.mir_package.log" new; run; %if &do_partitions eq 1 %then %do; %str(%inc "&infolder./mir/mir_master_program_pd.sas") ; %end; %else %do; %str(%inc "&infolder./mir/mir_master_program.sas") ; %end; *redirect libraries back to QA package request id root folder; %redirect_libs(&_root.) proc printto; run; %end; %* If specified in the master program file, call the qa_common_components program *; %if %lowcase(&execute_CC.)=y %then %do; %cc_run %end; %mend run_qa_package; %run_qa_package; proc printto; run; %logcheck(logdir=&MSOC, logdir_out=msoc); *-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-; * End scdm_control_flow.sas ; *-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-;