****************************************************************************************************
*                                           PROGRAM OVERVIEW
****************************************************************************************************
*
* PROGRAM: ms_cidadenom.sas  
* Created (mm/dd/yyyy): 12/19/2014
*
*--------------------------------------------------------------------------------------------------
* PURPOSE:
*  This macro will compute denominators.                                        
*   
*  Program inputs:                                                                                   
*   -Many datasets created by ms_cidanum
* 
*  Program outputs:                                                                                                                                       
*   -The DPLocal.&RUNID._DenomCounts output table
*
*  PARAMETERS:                                                                       
*            
*  Programming Notes:                                                                                
*                                                                           
*
*--------------------------------------------------------------------------------------------------
* CONTACT INFO: 
*  Sentinel Coordinating Center
*  info@sentinelsystem.org
*
***************************************************************************************************;

%macro ms_cidadenom();

    %put =====> MACRO CALLED: ms_cidadenom ;

    /*Only compute denominators if OUTPUTDENOM ne N and USERSTRATA file is specified and contains relevant tables*/
	%ISDATA(dataset=userstrata_denom);     	
    %if "&outputdenom" ne "N" and %eval(&nobs.>0) %then %do;

        /*Timing*/
        %ms_starttimer(timestart=denomruntime);

        /*---------------------------------------------------*/
        /* Original enrollment periods                       */
        /*---------------------------------------------------*/

        *If death censoring is required, it was already applied (Enr_End was previously truncated by CIDAnum);
        data worktemp._denomorig&Group. (keep=PatId EligEpisode Enr_Start Enr_End)
             worktemp._denom&Group.(keep=PatId DenomEnrStartdt DenomEnrEnddt EligEpisode);
        set worktemp.enr_&enrollmentnum.;

        where Enr_End>=&startdate.;
        format Enr_Start Enr_End DenomEnrStartDt DenomEnrEndDt date9.;
        length Enr_Start Enr_End DenomEnrStartDt DenomEnrEndDt 4;

        * Resctrict eligibility to Study period (minus maximum of required lookbacks);
        DenomEnrStartDt=Enr_Start;
        * Initially truncate start eligibility enrollment start by max of lookback periods;
        DenomEnrStartDt=DenomEnrStartDt + &ENRDAYS.;
        DenomEnrEndDt=Enr_End;
    	if DenomEnrEndDt >= DenomEnrStartDt;
        rename episode=EligEpisode;
        run;

        /*---------------------------------------------------*/
        /* Inclusion required (POV3 Incl) for eligibility    */
        /*---------------------------------------------------*/  
        %if &excl_incl = Y %then %do;
		
            *For EACH condition, shave periods for all subconditions;    
            %let MAXCOND=;
            proc sql noprint;
                select max(cond)
			    into: maxcond
                from _IT&INCLUSIONCODES.;
            quit;
            %put &=MAXCOND;
        
            %DO COND=1 %TO &MAXCOND.;
                *Determine how many levels of subconditions needed and if condition is an inclusion;
                %let MAXSUBCOND=;
                proc sql noprint;
                    select max(subcond), max(CondInclusion) into :maxsubcond, :inclusion
                    from _IT&INCLUSIONCODES. (where=(COND=&COND.));
                quit;
                %put &=MAXSUBCOND &=INCLUSION;

                /*Create &cond specific dataset*/
        		proc copy in=worktemp out=work memtype=data;
           		select _denom&Group.;
        		quit;
				
        		proc datasets library=work noprint;
        		change _denom&Group.=_Denom&Group._&cond.;
        		quit;

                %DO SUBCOND=1 %TO &MAXSUBCOND.;
                    *Determine if subcond is an inclusion or exclusion;
                    %let SUBINCLUSION=0;
					%let codedays = 1;
					%let subcumdose = 0;
					%let subminafdd = 0;
					%let submaxafdd = 0;
                    proc sql noprint;
                    select max(SubCondInclusion),
                           codedays,
						   max(mincumdose),
						   max(maxafdd),
						   max(minafdd)
                           into: subinclusion,
						       : codedays,
							   : subcumdose,
							   : submaxafdd,
							   : subminafdd
                    from _IT&INCLUSIONCODES. (where=(COND=&COND. and SUBCOND=&SUBCOND.));
                    quit;
                    %put &=SUBINCLUSION;

                    *Merge time periods for each subcondition and shave for each;
                    %IF &SUBINCLUSION. = 1 %THEN %DO;
                        data _EligIncl_&subcond.;
						    format EligStart EligEnd date9.;
							length EligStart EligEnd 4;
							%if %eval(&codedays.) > 1 %then %do; 
								retain id;
							%end; ;
                            set _inclexcl (where =(COND=&COND. and subcond=&SUBCOND.));
							by patid adate expiredt;
						   *When cumdose or afdd specified create a de-duped inclexcl dataset. This is necessary for codedays computation;
						    %if &computepov3cumdose. = Y | &computepov3afdd. = Y %then %do;
						      if not first.expiredt then delete;
							%end;
                            
                            EligStart=ADate-coalesce(CondTo,99999);
                            if dateonly = 'N' then do;
                                EligEnd=Expiredt-coalesce(CondFrom,-99999);
                            end;
                            else do;
                                EligEnd=ADate-coalesce(CondFrom,-99999);
                            end;
                            if EligStart<=EligEnd;
                            %if %eval(&codedays.) > 1 %then %do;
								lag_adate = lag (adate);
								if first.patid then id=1;
								else if lag_adate ne adate then id+1;
                            %end;
                            keep PatId EligStart EligEnd %if %eval(&codedays.) > 1 %then %do; id %end; ;
                        run;

                        *only keep time periods that meet codedays criteria;
                            %if %eval(&codedays.) = 1 %then %do;
                                data _overlap_spans_&subcond.;
                                    set _EligIncl_&subcond.;
                                run;
                            %end;
                            %else %do;
                                proc sql noprint;
                                    create table _overlap_spans_&subcond.(where=(eligstart <=eligend) rename=overlap_start=eligstart rename=overlap_end=eligend) as
                                    select distinct
                                        a1.patid,
                                        max(a1.eligstart %do ref = 2 %to &codedays.; ,a&ref..eligstart %end;) as overlap_start format date9.,
                                        min(a1.eligEnd %do ref = 2 %to &codedays.; ,a&ref..eligEnd %end;) as overlap_end format date9.
                                    from _EligIncl_&subcond. as a1
                                        %do ref = 2 %to &codedays.;
                                        , _EligIncl_&subcond. as a&ref.
                                        %end;
                                    where a1.Patid %do ref = 2 %to &codedays.; = a&ref..PatId %end; and (a1.id %do ref = 2 %to &codedays.; < a&ref..id %end;); 
                                quit;
                            %end;

                        %MergeTimePeriods(dataset=_overlap_spans_&subcond.,
                                          DateStart=EligStart,
                                          DateEnd=EligEnd); 
         
                        %ms_shaveoutside(reffile=_overlap_spans_&subcond.,
                                         refstart=EligStart,
                                         refend=EligEnd,  
                                         KeepPartBf=Y,
                                         ToShaveFile=_Denom&Group._&cond.,
                                         ToShaveStart=DenomEnrStartDt,
                                         ToShaveEnd=DenomEnrEndDt,
                                         shaverx=N,
                                         outfile=_Denom&Group._&cond.);
                        
						/* If mincumdose or average filled cumulative dose is specified then restrict eligibility periods */
						%if %sysevalf(&subcumdose. > 0) | %sysevalf(&subminafdd. > 0) | %sysevalf(&submaxafdd. > 0) %then %do;
						   %ms_dose_denom (infile = _denom&group._&cond.
						                            ,condvalue = &cond.
						                            ,subcondvalue = &subcond.
						                            ,subcumdose = &subcumdose.
						                            ,submaxafdd = &submaxafdd.
						                            ,subminafdd = &subminafdd);
													
                           %ms_shaveoutside(reffile=_inclexcl_cumdose,
                                         refstart=eligcumdosestart,
                                         refend=eligcumdoseend,  
                                         KeepPartBf=Y,
                                         ToShaveFile=_Denom&Group._&cond.,
                                         ToShaveStart=DenomEnrStartDt,
                                         ToShaveEnd=DenomEnrEndDt,
                                         shaverx=N,
                                         outfile=_Denom&Group._&cond.(drop=eligstart eligend eligcumdoseend eligcumdosestart));
						%end;
						%else %do;
                        *Drop refstart and refend since _Denom&Group can be looped;
                          data _Denom&Group._&cond.;
                            set _Denom&Group._&cond.(drop=eligstart eligend);
                          run;
						%end;
                    %END;*If subinclusion=1;
                    %IF &SUBINCLUSION. = 0 %THEN %DO;
                        data _UneligExcl_&subcond.;
						    format UneligStart UneligEnd date9.;
							length UneligStart UneligEnd 4;
							%if %eval(&codedays.) > 1 %then %do; 
								retain id;
							%end; ;
                            set _inclexcl (where =(COND=&COND. and subcond=&SUBCOND.));
							by patid adate expiredt;
						   *When cumdose or afdd specified create a de-duped inclexcl dataset. This is necessary for codedays computation;
						    %if &computepov3cumdose. = Y | &computepov3afdd. = Y %then %do;
						      if not first.expiredt then delete;
							%end;
                            
                            UneligStart=ADate-coalesce(CondTo,99999);
                            if dateonly = 'N' then do;
                                UneligEnd=Expiredt-coalesce(CondFrom,-99999);
                            end;
                            if dateonly = 'Y' then do;
                                UneligEnd=Adate-coalesce(CondFrom,-99999);
                            end;
                            if UneligStart<=UneligEnd;
                            %if %eval(&codedays.) > 1 %then %do;
								lag_adate = lag (adate);
								if first.patid then id=1;
								else if lag_adate ne adate then id+1;
                            %end;
                            keep PatId UneligStart UneligEnd %if %eval(&codedays.) > 1 %then %do; id %end; ;
                        run;

                        *only keep time periods that meet codedays criteria;
                        %if %eval(&codedays.) = 1 %then %do;
                            data _overlap_spans_&subcond.;
                                set _UneligExcl_&subcond.;
                            run;
                        %end;
                        %else %do;
                            proc sql noprint;
                                create table _overlap_spans_&subcond.(where=(Uneligstart <=Uneligend) rename=overlap_start=Uneligstart rename=overlap_end=Uneligend) as
                                select distinct
                                    a1.patid,
                                    max(a1.UneligStart %do ref = 2 %to &codedays.; ,a&ref..Uneligstart %end;) as overlap_start format date9.,
                                    min(a1.UneligEnd %do ref = 2 %to &codedays.; ,a&ref..UneligEnd %end;) as overlap_end format date9.
                                from _UneligExcl_&subcond. as a1
                                    %do ref = 2 %to &codedays.;
                                    , _UneligExcl_&subcond. as a&ref.
                                    %end;
                                where a1.Patid %do ref = 2 %to &codedays.; = a&ref..PatId %end; and (a1.id %do ref = 2 %to &codedays.; < a&ref..id %end;); 
                            quit;
                        %end;

                        %MergeTimePeriods(dataset=_overlap_spans_&subcond.,
                                          DateStart=UneligStart,
                                          DateEnd=UneligEnd);

                        *Verify if there is data or else e.r.r.o.r when shaving;
                        %ISDATA(dataset=_UneligExcl_&subcond.);
                        %IF %EVAL(&NOBS.>=1) %THEN %DO;
                            %ms_shaveinside(reffile=_Denom&Group._&cond.,
                                            refstart=DenomEnrStartDt,
                                            refend=DenomEnrEndDt,  
                                            id=EligEpisode,                     
                                            ToShaveFile=_overlap_spans_&subcond.,
                                            ToShaveStart=UneligStart,
                                            ToShaveEnd=UneligEnd,
                                            outfile=_Denom&Group._&cond.);
                        %end;
											
					     /* If mincumdose or average filled cumulative dose is specified then restrict eligibility periods */
					    %if %sysevalf(&subcumdose. > 0) | %sysevalf(&subminafdd. > 0) | %sysevalf(&submaxafdd. > 0) %then %do;
                            %ms_dose_denom (infile = _denom&group._&cond.
						                             ,condvalue = &cond.
						                             ,subcondvalue = &subcond.
						                             ,subcumdose = &subcumdose.
						                             ,submaxafdd = &submaxafdd.
						                             ,subminafdd = &subminafdd);
													 
                            %isdata(dataset=_inclexcl_cumdose);
                            %if %eval(&nobs.>0) %then %do;
                            %ms_shaveinside(reffile=_Denom&Group._&cond.,
                                            refstart=DenomEnrStartDt,
                                            refend=DenomEnrEndDt,  
                                            id=EligEpisode,                     
                                            ToShaveFile=_inclexcl_cumdose ,
                                            ToShaveStart=eligcumdosestart,
                                            ToShaveEnd=eligcumdoseend,
                                            outfile=_Denom&Group._&cond.);
                            %end;
						%end;
                    %END;*If subinclusion=0;
                %END;*loop on subcond;

                %put &=INCLUSION;
                %IF &INCLUSION.=1 %THEN %DO;
                    proc append base=_Denom&Group._STACK_INC
                        data=_Denom&Group._&cond.(keep=patid DenomEnrStartDt DenomEnrEndDt eligepisode) force;
                        run;
                %END;

                %IF &INCLUSION.=0 %THEN %DO;
                    proc append base=_Denom&Group._STACK_EXCL
                        data=_Denom&Group._&cond.(keep=patid DenomEnrStartDt DenomEnrEndDt eligepisode) force;
                        run;
                %END;

            %END;*End Loop Cond;

            *Output dataset for inclusion=1 and for exclusions=0 and then (a) shaveoutside (b) shaveinside denom; 
            *For inclusions, need to merge periods;

            *First shaveoutside denom file but need to merge inclusion periods together;
            %IF %SYSFUNC(exist(_Denom&Group._STACK_INC))=1 %THEN %DO;
                %MergeTimePeriods(dataset=_Denom&Group._STACK_INC,
                                  DateStart=DenomEnrStartDt,
                                  DateEnd=DenomEnrEndDt);


                *_Denom&Group._STACK_INC includes all time periods where the potential index dates will meet at least one of the cond inclusions;
            	proc datasets library=work noprint;
            	modify _Denom&Group._STACK_INC;
            	rename DenomEnrStartDt=DTSTART
                       DenomEnrEndDt=DTEND;
            	quit;

                %ms_shaveoutside(reffile=_Denom&Group._STACK_INC,
                                 refstart=DTSTART,
                                 refend=DTEND,  
                                 /*id=EligEpisode,*/ 
                                 KeepPartBf=Y,
                                 ToShaveFile=worktemp._denom&Group.,
                                 ToShaveStart=DenomEnrStartDt,
                                 ToShaveEnd=DenomEnrEndDt,
                                 shaverx=N,
                                 outfile=worktemp._denom&Group./*_incl*/);   

            %END;

            %IF %SYSFUNC(exist(_Denom&Group._STACK_EXCL))=1 %THEN %DO;

                *merge all periods of ineligibilities;
                %MergeTimePeriods(dataset=_Denom&Group._STACK_EXCL,
                                  DateStart=DenomEnrStartDt,
                                  DateEnd=DenomEnrEndDt);

                *_Denom&Group._STACK_EXCL includes all time periods where the potential index dates will meet at least one of the cond exclusions;
            	proc datasets library=work noprint;
            	modify _Denom&Group._STACK_EXCL;
            	rename DenomEnrStartDt=DTSTART
                       DenomEnrEndDt=DTEND;
            	quit;

                *remove all these periods from the ELIG;
                %ms_shaveinside(reffile=worktemp._denom&Group.,
                                refstart=DenomEnrStartDt,
                                refend=DenomEnrEndDt,  
                                id=EligEpisode,                     
                                ToShaveFile=_Denom&Group._STACK_EXCL,
                                ToShaveStart=DTSTART,
                                ToShaveEnd=DTEND,
                            outfile=worktemp._denom&Group.);
            %END;

            proc sort data=worktemp._denom&Group. out=worktemp._denom&Group. nodupkey;                        
            by patid DenomEnrStartDt DenomEnrEndDt eligepisode;
            run;

        %END;*If there are inclusion codes for this group;
		
        /*-------------------------------------------------------------
          Create separate prevalance and incidence cidadenom datasets 
        -------------------------------------------------------------*/
        %macro cidadenom_prev_inc (l=, ds_suffix =, where_clause = );

            /*---------------------------------------------------*/
            /* Group Index (POV1) to remove from eligibility     */
            /*---------------------------------------------------*/

            %if %eval(&l.=1) %then %do;
               data worktemp._temp&group.;
                 set worktemp._denom&Group.;
               run;
            %end;
            %if %eval(&l.=2) %then %do;
              data worktemp._denom&Group.;
                 set worktemp._temp&group.;
              run;
              
              proc datasets nowarn noprint lib = worktemp;
                delete _temp&group.;
              quit;
            %end;
            
            %global previnc_washper;
            /* Need to use washper2 when autoprev = Y */
            %if %str("&autoprev.") = %str("Y") and %str("&ds_suffix") = "" %then %do;
              %let previnc_washper = &washper2.;
            %end;
            %else %do;
              %let previnc_washper = &washper.;
            %end;
            
            %ISDATA(dataset=_groupindex);
            %IF %EVAL(&NOBS.>=1) & (%EVAL(&previnc_washper. ne 0)) %THEN %DO;
                data _UneligGroupIndex;
                set _groupindex;
                format UneligStart UneligEnd date9.;
				length UneligStart UneligEnd 4;
                UneligStart=ADate + 1;  * Added a +1 so a member can remain eligible the day of index date;
                UneligEnd=ExpireDt + min(&previnc_washper.,99999);
                if UneligStart<=UneligEnd;
                keep PatId UneligStart UneligEnd;
                run;
        
                %MergeTimePeriods(dataset=_UneligGroupIndex,
                                  DateStart=UneligStart,
                                  DateEnd=UneligEnd);
                %ms_shaveinside(reffile=worktemp._denom&Group.,
                                refstart=DenomEnrStartDt,
                                refend=DenomEnrEndDt,  
                                      id=EligEpisode, 
                                ToShaveFile=_UneligGroupIndex,
                                ToShaveStart=UneligStart,
                                ToShaveEnd=UneligEnd,
                                outfile=worktemp._denom&Group.);
            %END;            
    
            /*---------------------------------------------------*/
            /* Follow-up Event (POV5) to remove from eligibility */
            /*---------------------------------------------------*/
            %ISDATA(dataset=_fupevent);*Will only apply to type 2 analysis;
            %IF %EVAL(&NOBS.>=1) %THEN %DO;
                data _UneligFupEvent;
                set _fupevent;
                format UneligStart UneligEnd date9.;
				length UneligStart UneligEnd 4;
                UneligStart=sum(ADate, - &BLACKOUTPER., 1); * Added a +1 so a member can be eligible the day of event;
                if dateonly = 'N' then do;
                    UneligEnd=ExpireDt + min(&FUPWASHPER.,99999);
                end;
                if dateonly = 'Y' then do;
                    UneligEnd=Adate + min(&FUPWASHPER.,99999);
                end;
                if UneligStart<=UneligEnd;
                keep PatId UneligStart UneligEnd;
                run;
        
                %MergeTimePeriods(dataset=_UneligFupEvent,
                                  DateStart=UneligStart,
                                  DateEnd=UneligEnd);
                %ms_shaveinside(reffile=worktemp._denom&Group.,
                                refstart=DenomEnrStartDt,
                                refend=DenomEnrEndDt,  
                                id=EligEpisode, 
                                ToShaveFile=_UneligFupEvent,
                                ToShaveStart=UneligStart,
                                ToShaveEnd=UneligEnd,
                                outfile=worktemp._denom&Group.);
            %END;
    
            /*-----------------------------------------------------*/
            /* Follow-up Washout (POV6) to remove from eligibility */
            /*-----------------------------------------------------*/
            %ISDATA(dataset=_fupwash);
            %IF %EVAL(&NOBS.>=1) %THEN %DO;
        
                data _UneligFupWash;
                set _fupwash;
                format UneligStart UneligEnd date9.;
				length UneligStart UneligEnd 4;
                UneligStart=ADate + 1;  * Added a +1 so a member can remain eligible the day of index date;
                if dateonly = 'N' then do;
                    UneligEnd=ExpireDt + min(&FUPWASHPER.,99999);
                end;
                if dateonly = 'Y' then do;
                    UneligEnd=Adate + min(&FUPWASHPER.,99999);
                end;
                if UneligStart<=UneligEnd;
                keep PatId UneligStart UneligEnd;
                run;
        
                %MergeTimePeriods(dataset=_UneligFupWash,
                                  DateStart=UneligStart,
                                  DateEnd=UneligEnd);
                %ms_shaveinside(reffile=worktemp._denom&Group.,
                                refstart=DenomEnrStartDt,
                                refend=DenomEnrEndDt,  
                                id=EligEpisode, 
                                ToShaveFile=_UneligFupWash,
                                ToShaveStart=UneligStart,
                                ToShaveEnd=UneligEnd,
                                outfile=worktemp._denom&Group.);
            %END;

            *this macro is to avoid having to output one record per interval per stratification level
             which for certain partners, can become quite large.;
            %macro CreateSums(infile=,outfile=,by=,dimout=,timeperiod=month,numtimeperiod=&nummonths.);
				
                proc sort data = &infile.(keep= Patid DenomEnrStartdt DenomEnrEnddt &by.)
        				  out=_temp;
                 by &by. patid;
                run;
               
                data _temp;
                set _temp;
                By &by. PatId;

                array _NumPtsYM(*) _NumPtsYM1-_NumPtsYM&numtimeperiod.;   		 *Pts level temporary vector to avoid double counting patients in same year/month or year/quarter;   
				array _NumPtsY(*) _NumPtsY1-_NumPtsY&numyears.;   				 *Pts level temporary vector to avoid double counting patients in same year;
				array NumPtsYM(*) NumPtsYM1-NumPtsYM&numtimeperiod.;             *accumator of unique patient in the same year/month or year/quarter;
				array NumPtsY(*) NumPtsY1-NumPtsY&numyears.;                 	 *accumator of unique patient in the same year;

				%if "&outputdenom" = "Y" %then %do;
				array NumMemDaysYM(*) NumMemDaysYM1-NumMemDaysYM&numtimeperiod.; *accumator of unique days in the same year/month or year/quarter;       
                array NumMemDaysY(*) NumMemDaysY1-NumMemDaysY&numyears.;     	 *accumator of unique days in the same year;
				%end;

                %do p=1 %to &numtimeperiod.;
                    %let p1=%eval(&p.+1);
                    if %MS_PeriodsOverlap(period1=DenomEnrStartDt DenomEnrEndDt,period2=&&&timeperiod.PER&p. &&&timeperiod.PER&p1.-1) then do;
                        _NumPtsYM(&p.)=1;
						%if "&outputdenom" = "Y" %then %do;
                        	NumMemDaysYM(&p.)=sum(NumMemDaysYM(&p.),Min(DenomEnrEndDt,&&&timeperiod.PER&p1.-1)-max(DenomEnrStartDt,&&&timeperiod.PER&p.)+1);
						%end;
                    end;                      
                %end;

                %do p=1 %to &numyears.;
                    %let p1=%eval(&p.+1);
                    if %MS_PeriodsOverlap(period1=DenomEnrStartDt DenomEnrEndDt,period2=&&YEARPER&p. &&YEARPER&p1.-1) then do;
                        _NumPtsY(&p.)=1;
						%if "&outputdenom" = "Y" %then %do;
                        	NumMemDaysY(&p.)=sum(NumMemDaysY(&p.),Min(DenomEnrEndDt,&&YEARPER&p1.-1)-max(DenomEnrStartDt,&&YEARPER&p.)+1);
						%end;
                    end;                      
                %end;
         
                if last.PatId then do;
                    do i = 1 to &NUMYEARS.;               
                        NumPtsY(i)=sum(NumPtsY(i),_NumPtsY(i));
                    end;
                    do i = 1 to &numtimeperiod.;
                         NumPtsYM(i)=sum(NumPtsYM(i),_NumPtsYM(i));
                    end; 
                    call missing(of _Num:);
                end;

                if last.&dimout. then do;
                    output;
                    call missing(of Num:);
                end;

                keep &by. Num:;

                retain Num: _Num:;
                run;

                %ISDATA(dataset=_temp);
                %IF %EVAL(&NOBS.=0) %THEN %DO;
                    proc sql noprint;
                        insert into _temp set NumPtsYM1=.;          
                    quit;       
                %END;
                
				proc transpose data=_temp %if "&outputdenom" = "Y" %then %do; (drop=NumMemDays:) %end; 
							   out=_PTS prefix=NumPts;
                by &by.;
                run;

				%if "&outputdenom" = "Y" %then %do;
	                proc transpose data=_temp(drop=NumPts:) out=_Days prefix=NumMemDays;
	                by &by.;
	                run;
				%end;
                
                /* Set mergenoby to nowarn to avoid triggering an e.r.r.o.r. or w.a.r.n.i.n.g. at this step */
                options mergenoby=nowarn;
                
                data &outfile.;
                length &timeperiod. year 3;
				%if "&outputdenom" = "Y" %then %do;
	                merge _Days(rename=_NAME_=_Days_)
	                      _PTS(rename=_NAME_=_PTS_);
	                *No by;
                %end;
				%else %do;
					set _PTS(rename=_NAME_=_PTS_);
				%end;
                if _PTS_=:"NumPtsYM" then do;
                    per=input(tranwrd(_PTS_, 'NumPtsYM' , ''),best.);
                    date=intnx("&timeperiod.",&ExpPeriodStartDt.,per-1,'begin');
                    %if %str("&timeperiod.") = %str("quarter") %then %do; length quarter 3; quarter = qtr(date); %end;
                    %else %do; &timeperiod.=&timeperiod.(date); %end;
                    year=year(date);
                end;
                else do;
                    per=input(tranwrd(_PTS_, 'NumPtsY' , ''),best.);
                    date=intnx('year',&ExpPeriodStartDt.,per-1,'begin');
                    year=year(date);
                end;
                
                Rename %if "&outputdenom" = "Y" %then %do; NumMemDays1=DenNumMemDays %end;
                       NumPts1=DenNumPts;
                
                keep  year &timeperiod. &by. Num:;
                run;
                   
                /* Reset mergenoby to original value (as stored in macro variable) */
                options mergenoby=&mergenoby.;

                proc datasets nowarn noprint lib=work;
                    delete _days _pts _temp;
                quit;
             
            %mend CreateSums;
   
            /*-----------------------------------------------------*/
            /* Make sure the number of enrollment days remaining   */
            /* after each potential elig days is sufficient        */
            /*-----------------------------------------------------*/

            * Get first Index Dates and Event Dates to handle member days correctly according to CohortDef;
                proc sql noprint;
                create table _Denom as 
                select den.PatId,
                       den.EligEpisode,
                       den.Enr_Start,
                       den.Enr_End,
                       min(lst.IndexDt) as FIndexDt format date9. length=4,
                       min(lst.FEventDt) as FEventDt format date9. length=4
                from worktemp._denomOrig&Group. as den
                left join _ptsmasterlist &where_clause. as lst
                on den.PatId=lst.PatId
                group by den.PatId, den.EligEpisode, den.Enr_Start, den.Enr_end
                order by PatId, EligEpisode;
                quit;
            
                proc sort data = worktemp._denom&Group.(keep=PatId EligEpisode DenomEnrStartdt DenomEnrEnddt)
        				  out=_denom&Group.;
                by PatId EligEpisode;
                run;
            
                data _denom&Group.;
                merge _denom&Group.(in=a keep=PatId EligEpisode DenomEnrStartDt DenomEnrEndDt) 
                      _Denom;
                by PatId EligEpisode;
                format AdjustedEnrEndDt date9.;
                length AdjustedEnrEndDt 4;
                if a;
            
                * Adjust eligibility end date;
                AdjustedEnrEndDt = min(Enr_End, &censordate.) - Max(0,&MinEpisDur. - 1,&MinDaySupp. - 1,&BlackoutPer.,&reqdaysaftind., &reqdaysaftepi.+max(&MinEpisDur. - 1,&MinDaySupp. - 1,&BlackoutPer.));
                DenomEnrEndDt = min(DenomEnrEndDt,AdjustedEnrEndDt);
                drop AdjustedEnrEndDt;
            
                /*secondary episode observation window*/
                /*remove invalid time due to obsfrom-obsto enrollment/data availability criteria*/
                if DenomEnrStartDt < enr_start + max(0,-1*&obs_from_index.,-1*&obs_from_epiend.) then DenomEnrStartDt = enr_start + max(0,-1*&obs_from_index.,-1*&obs_from_epiend.);
                if DenomEnrEndDt > min(enr_end, &censordate.) - max(0,&obs_to_index., &obs_to_epiend.) then DenomEnrEndDt = min(enr_end, &censordate.) - max(0,&obs_to_index., &obs_to_epiend.);
            
                if "&COHORTDEF."="01" then DenomEnrEndDt=min(DenomEnrEndDt,FIndexDt);
                if "&COHORTDEF."="03" then DenomEnrEndDt=min(DenomEnrEndDt,FEventDt);
            
                MemberDays = DenomEnrEndDt - DenomEnrStartDt + 1;
            
                * Because DenomEnrEndDt can have been truncated, keep only the observations with positive member days;
                if MemberDays>0;
                run;

            /*---------------------------*/
            /* Add DOB/SEX/Race/Hispanic */
            /*---------------------------*/
            
        	*Demogs;   
                proc sql noprint;
        	    create table worktemp._denom&Group. as
                    select distinct pts.*, 
                                    birth_date, 
                                    case when sex in('F', 'M') then sex
                                        else 'O'                       
                                    end as sex,
                                    race,
                                    hispanic,
                                    zip,
                                    zip_date
        	    from worktemp.enr_&enrollmentnum. as DEM 
                         inner join 
        	         _denom&Group. as pts            
        	    on DEM.patid = pts.patid;        
                quit;
            
                    /** Zip Codes **/
                    %if "&geog." = "Y" %then %do;
                        proc sql noprint;
                            create table _zip&Group. as
                            select pts.*,
                                 lkup.statecode, 
                                 lkup.hhs_region,
                                 lkup.cb_region
                            from worktemp._denom&Group. as pts
                            left join infolder.&zipfile. as lkup
                            on pts.zip = lkup.zip
                            order by pts.patid;
                        quit;
            
                        data worktemp._denom&Group.(drop=statecode cb_region hhs_region);
                            length zip3 state hhs_reg cb_reg $7;
                            set _zip&Group.;
                            if missing(zip) then do;
                                zip3 = 'Missing';
                                state = 'Missing';
                                hhs_reg = 'Missing';
                                cb_reg = 'Missing';
                            end;
                            else if missing(statecode) then do;
                                zip3 = 'Invalid';
                                state = 'Invalid';
                                hhs_reg = 'Invalid';
                                cb_reg = 'Invalid';
                            end;
                            else do;
                                zip3 = substr(strip(zip),1,3);
                                state = statecode;
                                cb_reg = cb_region;
                                hhs_reg = hhs_region;
                                if statecode ne '' and missing(hhs_region) then do;
                                    hhs_reg = 'Other';
                                end;
                                if statecode ne '' and missing(cb_reg) then do;
                                    cb_reg = 'Other';
                                end;
                            end;
                        run;
                    %END;
                    %else %do;
                        data worktemp._denom&Group.;
                            set worktemp._denom&Group.;
                            length zip3 state cb_reg hhs_reg $7 Zip_uncertain $1;
                            zip3 = '';
                            state = '';
                            cb_reg = '';
                            hhs_reg = '';
                            Zip_uncertain = '';
                        run;
                    %end;
    
                *truncate periods such that all interval lies in Minage-MaxAge;
                data worktemp._denom&Group.;
			    set worktemp._denom&Group.;

    			    format MinAgeDt MaxAgeDt sfup date9.;
    			    length MinAgeDt MaxAgeDt sfup 4;

    			    MinAgeDt = intnx(scan("&AGETYP.",1),birth_date,&MINAGE.,'sameday');

    			    if &MAXAGE.=99999 then MaxAgeDt=intnx('Years',birth_date,110,'sameday');
    			    else MaxAgeDt= intnx(scan("&AGETYP.",&NBSTRAT.),birth_date,&MAXAGE.+1,'sameday'); 

    			    *Member not meetin minimum age yet;
    			    if MinAgeDt > DenomEnrStartDt then DenomEnrStartDt=MinAgeDt;  *can be pushed beyond DenomEnrEndDt;

    			    *Member has reached or will reach will reach maxage ;
    			    if MaxAgeDt <= DenomEnrEndDt then DenomEnrEndDt=MaxAgeDt-1;   *can be pushed before DenomEnrStartDt;
    			    
    			    *Adjust;
    			    if DenomEnrStartDt <= DenomEnrEndDt;    

    			    /* At this point, each day in the DenomEnrStartDt-DenomEnrEndDt intervals represents
    			       a potential index date if the patients would have started one of the study drugs.
    			       Now, we need to restrict only to the ExpPeriodStartDt-ExpPeriodEndDt period. This will simplify accumulating days
    			       for year and year/Month strata, especially if the ExpPeriodStartDt day is not the first day of the month
    			       and or the first month of the year */
    			       
    			    sfup=input("&ExpPeriodStartDt.", best.);    
    			    DenomEnrStartDt=max(DenomEnrStartDt,&ExpPeriodStartDt.);
    			    DenomEnrEndDt=min(DenomEnrEndDt,&ExpPeriodEndDt.);
    			    if DenomEnrStartDt<=DenomEnrEndDt;
    			    patient=1;            
    				%if "&outputdenom" = "Y" %then %do;	MemberDays=DenomEnrEndDt-DenomEnrStartDt+1;	%end;				
			    run;
                
                /*Save denominators for future processing*/
                *Never Exposed Cohort;
                %if "&NEVEREXPOSEDCOHORT." = "Y" %then %do; 
                    data worktemp._nvrexp&Group.;
                        set worktemp._denom&Group.;
                    run;
                %end;
                
                *create individual denom contribution to final table;
                *need to do this individually to make sur we dont multiple-count
                *patients in the presence of different strata;
                /* If no observations in _denom&group, square the table */
                %ISDATA(dataset=worktemp._denom&Group.);
                %IF %EVAL(&NOBS.=0) %THEN %DO;
                  *Square table;
                   %ms_squaredtableshell(squarevars = sex agegroupnum race hispanic
                                                      %if "&geog" = "Y" %then %do; zip_uncertain %end;
                                                      %if %index(%lowcase(&allstrat.), zip3) > 0 %then %do; zip3 %end;
                                                      %if %index(%lowcase(&allstrat.), state) > 0 %then %do; state %end;
                                                      %if %index(%lowcase(&allstrat.), hhs_reg) > 0 %then %do; hhs_reg %end;
                                                      %if %index(%lowcase(&allstrat.), cb_reg) > 0 %then %do; cb_reg %end;
                                        ,analysis= N);    
 
                   data worktemp._denom&Group.;
                     set worktemp._denom&Group.
                         _mastersquare;
                     patient=0;
                   run;                      
                %END;
           
                /*macro to sum denominators for each stratification*/
				%macro demomlevel(levelvars=, indataset=, outdataset=, where=1, createsumsby=, createsumsdimout=, createsumsinfile=);

					data _denomlevel;
    					set userstrata_denom;
    					where %if &ds_suffix. eq _prev %then %do;
								tableid in ("t2cidaprev", "t2itsprev")
							  %end;
							  %else %do;
								tableid not in ("t2cidaprev", "t2itsprev")
							  %end;
						and levelvars = "&levelvars.";
                        call symputx('denomlevel', levelid);
					run;

					* Check if level is required in userstrata file;
					%ISDATA(dataset=_denomlevel);
				    %if %eval(&nobs.>=1) %then %do;
						* Check if level needs the CreateSums macro output;
						%if %length(&createsumsby.) > 0 %then %do;
							%ISDATA(dataset=&indataset.);
				    		%if %eval(&nobs.=0) %then %do;
								%CreateSums(infile=&createsumsinfile.,
				                            outfile=&indataset.,
				                            by=&createsumsby.,
				                            dimout=&createsumsdimout.
											/* quarter periods */
											%if %index(&levelvars., quarter) > 0 %then %do;
											,timeperiod=quarter
				                            ,numtimeperiod=&numquarters.
											%end;
											/* geographic should not produce month/quarter output */
											%if %index(&levelvars., zip_uncertain) > 0 or %index(&levelvars., zip3) > 0 or 
												%index(&levelvars., state) > 0 or%index(&levelvars., hhs_reg) > 0 or %index(&levelvars., cb_reg) > 0 %then %do;
											,timeperiod=month
											,numtimeperiod=0
											%end;
											);
							%end;
						%end;
						%else %do;
						    proc means data=&indataset. nway noprint missing;
						    var %if "&outputdenom" = "Y" %then %do; MemberDays %end; Patient;
						    class &levelvars. PatId;
							where &where.;
						    output out=&outdataset.(drop=_:) %if "&outputdenom" = "Y" %then %do; sum(MemberDays)=DenNumMemDays %end; max(Patient)=DenNumPts;
						    run;
						%end;

					    proc means data= %if %length(&createsumsby.) > 0 %then %do; &indataset. %end; %else %do; &outdataset. %end; nway noprint missing;
					    var %if "&outputdenom" = "Y" %then %do; DenNumMemDays %end; DenNumPts;
						%if %length(&levelvars.) > 0 %then %do; 
						class &levelvars;
						%end;
						where &where.;
					    output out=&outdataset.(drop=_:) %if "&outputdenom" = "Y" %then %do; sum(DenNumMemDays)= %end; sum(DenNumPts)=;
					    run;

						* Add level number;
                        data &outdataset.;
                            set &outdataset.;
                            length level $3;
                            level = "&denomlevel";
                        run;
					%end; /* level is required */

				%mend demomlevel;

				*Overall;
				%demomlevel(levelvars=, indataset=worktemp._denom&Group., outdataset=_denom_lvl_ALL);

				*Sex;
				%demomlevel(levelvars=sex, indataset=worktemp._denom&Group., outdataset=_denom_lvl_S);

				*Sex Year;
				%demomlevel(levelvars=sex year, indataset=_denom_lvl_SYM, outdataset=_denom_lvl_SY, where=%str(year ne . and month=.),
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Year;
				%demomlevel(levelvars=year, indataset=_denom_lvl_SYM, outdataset=_denom_lvl_Y, where=%str(year ne . and month=.), 
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Year Month;
				%demomlevel(levelvars=month year , indataset=_denom_lvl_SYM, outdataset=_denom_lvl_YM, where=%str(year ne . and month ne .),
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Sex Year Month;
				%demomlevel(levelvars=month sex year, indataset=_denom_lvl_SYM, outdataset=_denom_lvl_SYM, where=%str(year ne . and month ne .),
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Year Quarter;
				%demomlevel(levelvars=quarter year , indataset=_denom_lvl_SYQ, outdataset=_denom_lvl_YQ, where=%str(year ne . and quarter ne .),
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Sex Year Quarter;
				%demomlevel(levelvars=quarter sex year, indataset=_denom_lvl_SYQ, outdataset=_denom_lvl_SYQ, where=%str(year ne . and quarter ne .),
							createsumsby=sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);
			              
				*Race;
				%demomlevel(levelvars=race, indataset=worktemp._denom&Group., outdataset=_denom_lvl_R);

				*Race Year;
				%demomlevel(levelvars=race year, indataset=_denom_lvl_RYM, outdataset=_denom_lvl_RY, where=%str(year ne . and month eq .),
							createsumsby=race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);

				*Race Year Month;
				%demomlevel(levelvars=month race year, indataset=_denom_lvl_RYM, outdataset=_denom_lvl_RYM, where=%str(year ne . and month ne .),
							createsumsby=race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);
				
				*Race Year Quarter;
				%demomlevel(levelvars=quarter race year, indataset=_denom_lvl_RYQ, outdataset=_denom_lvl_RYQ, where=%str(year ne . and quarter ne .),
							createsumsby=race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);

				*Race Sex;
				%demomlevel(levelvars=race sex, indataset=worktemp._denom&Group., outdataset=_denom_lvl_R_S);

				*Race Sex Year;
				%demomlevel(levelvars=race sex year, indataset=_denom_lvl_RSYM, outdataset=_denom_lvl_RSY, where=%str(year ne . and month eq .),
							createsumsby=race sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Race Sex Year Month;
				%demomlevel(levelvars=month race sex year, indataset=_denom_lvl_RSYM, outdataset=_denom_lvl_RSYM, where=%str(year ne . and month ne .),
							createsumsby=race sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);
				
				*Race Sex Year Quarter;
				%demomlevel(levelvars=quarter race sex year, indataset=_denom_lvl_RSYQ, outdataset=_denom_lvl_RSYQ, where=%str(year ne . and quarter ne .),
							createsumsby=race sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Hispanic;
				%demomlevel(levelvars=hispanic, indataset=worktemp._denom&Group., outdataset=_denom_lvl_H);

				*Hispanic Year;
				%demomlevel(levelvars=hispanic year, indataset=_denom_lvl_HYM, outdataset=_denom_lvl_HY, where=%str(year ne . and month eq .),
							createsumsby=hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Year Month;
				%demomlevel(levelvars=hispanic month year, indataset=_denom_lvl_HYM, outdataset=_denom_lvl_HYM, where=%str(year ne . and month ne .),
							createsumsby=hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Year Quarter;
				%demomlevel(levelvars=hispanic quarter year, indataset=_denom_lvl_HYQ, outdataset=_denom_lvl_HYQ, where=%str(year ne . and quarter ne .),
							createsumsby=hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.);

				*Hispanic Sex;
				%demomlevel(levelvars=hispanic sex, indataset=worktemp._denom&Group., outdataset=_denom_lvl_H_S);

				*Hispanic Sex Year;
				%demomlevel(levelvars=hispanic sex year, indataset=_denom_lvl_HSYM, outdataset=_denom_lvl_HSY, where=%str(year ne . and month eq .),
							createsumsby=hispanic sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Sex Year Month;
				%demomlevel(levelvars=hispanic month sex year, indataset=_denom_lvl_HSYM, outdataset=_denom_lvl_HSYM, where=%str(year ne . and month ne .),
							createsumsby=hispanic sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Sex Year Quarter;
				%demomlevel(levelvars=hispanic quarter sex year, indataset=_denom_lvl_HSYQ, outdataset=_denom_lvl_HSYQ, where=%str(year ne . and quarter ne .),
							createsumsby=hispanic sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.);

				*Hispanic Race Year;
				%demomlevel(levelvars=hispanic race year, indataset=_denom_lvl_HRYM, outdataset=_denom_lvl_HRY, where=%str(year ne . and month eq .),
							createsumsby=hispanic race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Race Year Month;
				%demomlevel(levelvars=hispanic month race year, indataset=_denom_lvl_HRYM, outdataset=_denom_lvl_HRYM, where=%str(year ne . and month ne .),
							createsumsby=hispanic race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);
				
				*Hispanic Race Year Quarter;
				%demomlevel(levelvars=hispanic quarter race year, indataset=_denom_lvl_HRYQ, outdataset=_denom_lvl_HRYQ, where=%str(year ne . and quarter ne .),
							createsumsby=hispanic race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.);

				%if "&agegroup_out" = "Y" %then %do;
					/*-------------------------------------------------*/
				    /* further breaking down periods by age categories */
				    /*-------------------------------------------------*/
					data worktemp._denom&Group.age(rename=AgeDenomEnrEndDt=DenomEnrEndDt rename=AgeDenomEnrStartDt=DenomEnrStartDt);
    					length agegroupnum 3;
    					set worktemp._denom&Group.;
    					format AgeGroup $9.; 
    					format threshdate AgeDenomEnrStartDt AgeDenomEnrEndDt date9.;
    					length threshdate AgeDenomEnrStartDt AgeDenomEnrEndDt 4;
    					AgeDenomEnrStartDt=DenomEnrStartDt;
    					AgeDenomEnrEndDt=DenomEnrEndDt;        
    					do i=&NBSTRAT. to 1 by -1;       
    					threshdate=intnx(scan("&AGETYP.",i),birth_date,input(scan("&AGETHRESH.",i),best.),'sameday'); 
    					if threshdate <= AgeDenomEnrEndDt then do;
    					   AgeGroup = scan("&AGESTRAT.",i,' ');
    					   agegroupnum = i;
    					   AgeDenomEnrStartDt=max(threshdate,DenomEnrStartDt);                
    					   %if "&outputdenom" = "Y" %then %do; MemberDays=AgeDenomEnrEndDt-AgeDenomEnrStartDt+1; %end;				   
    					   output;
    					   AgeDenomEnrEndDt=threshdate-1;
    					   if AgeDenomEnrEndDt < DenomEnrStartDt then leave;
    					end;
    					end;         
    					keep PatId AgeDenomEnrEndDt AgeDenomEnrStartDt sex race hispanic AgeGroup agegroupnum Patient %if "&outputdenom" = "Y" %then %do; MemberDays %end; threshdate
    						 %if "&geog" eq "Y" %then %do; hispanic zip3 state hhs_reg cb_reg %end;; 
					run;

					*AgeGroup;
					%demomlevel(levelvars=agegroupnum, indataset=worktemp._denom&Group.age, outdataset=_denom_lvl_AG);

					*Sex AgeGroup;
					%demomlevel(levelvars=agegroupnum sex, indataset=worktemp._denom&Group.age, outdataset=_denom_lvl_SAG);

					*Sex AgeGroup Year;
					%demomlevel(levelvars=agegroupnum sex year, indataset=_denom_lvl_SAGYM, outdataset=_denom_lvl_SAGY, where=%str(year ne . and month=.),
								createsumsby=agegroupnum sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.age);
					
					*Sex AgeGroup Year Month;
					%demomlevel(levelvars=agegroupnum month sex year, indataset=_denom_lvl_SAGYM, outdataset=_denom_lvl_SAGYM, where=%str(year ne . and month ne .),
								createsumsby=agegroupnum sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.age);

					*Sex AgeGroup Year Quarter;
					%demomlevel(levelvars=agegroupnum quarter sex year, indataset=_denom_lvl_SAGYQ, outdataset=_denom_lvl_SAGYQ, where=%str(year ne . and quarter ne .),
								createsumsby=agegroupnum sex, createsumsdimout=sex, createsumsinfile=worktemp._denom&Group.age);

					*AgeGroup Year;
					%demomlevel(levelvars=agegroupnum year, indataset=_denom_lvl_AGYM, outdataset=_denom_lvl_AGY, where=%str(year ne . and month=.),
								createsumsby=agegroupnum, createsumsdimout=agegroupnum, createsumsinfile=worktemp._denom&Group.age);

					*AgeGroup Year Month;
					%demomlevel(levelvars=agegroupnum month year, indataset=_denom_lvl_AGYM, outdataset=_denom_lvl_AGYM, where=%str(year ne . and month ne .),
								createsumsby=agegroupnum, createsumsdimout=agegroupnum, createsumsinfile=worktemp._denom&Group.age);

					*AgeGroup Year Quarter;
					%demomlevel(levelvars=agegroupnum quarter year, indataset=_denom_lvl_AGYQ, outdataset=_denom_lvl_AGYQ, where=%str(year ne . and quarter ne .),
								createsumsby=agegroupnum, createsumsdimout=agegroupnum, createsumsinfile=worktemp._denom&Group.age);

					*Race AgeGroup;
					%demomlevel(levelvars=agegroupnum race, indataset=worktemp._denom&Group.age, outdataset=_denom_lvl_RAG);

				 	*Race AgeGroup Year;
					%demomlevel(levelvars=agegroupnum race year, indataset=_denom_lvl_RAGYM, outdataset=_denom_lvl_RAGY, where=%str(year ne . and month=.),
								createsumsby=agegroupnum race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.age);

					*Race AgeGroup Year Month;
					%demomlevel(levelvars=agegroupnum month race year, indataset=_denom_lvl_RAGYM, outdataset=_denom_lvl_RAGYM, where=%str(year ne . and month ne .),
								createsumsby=agegroupnum race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.age);

					*Race AgeGroup Year Quarter;
					%demomlevel(levelvars=agegroupnum quarter race year, indataset=_denom_lvl_RAGYQ, outdataset=_denom_lvl_RAGYQ, where=%str(year ne . and quarter ne .),
								createsumsby=agegroupnum race, createsumsdimout=race, createsumsinfile=worktemp._denom&Group.age);
				 	*Hispanic AgeGroup;
					%demomlevel(levelvars=agegroupnum hispanic, indataset=worktemp._denom&Group.age, outdataset=_denom_lvl_HAG);

					*Hispanic AgeGroup Year;
					%demomlevel(levelvars=agegroupnum hispanic year, indataset=_denom_lvl_HAGYM, outdataset=_denom_lvl_HAGY, where=%str(year ne . and month=.),
								createsumsby=agegroupnum hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.age);

					*Hispanic AgeGroup Year Month;
					%demomlevel(levelvars=agegroupnum hispanic month year, indataset=_denom_lvl_HAGYM, outdataset=_denom_lvl_HAGYM, where=%str(year ne . and month ne .),
								createsumsby=agegroupnum hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.age);

					*Hispanic AgeGroup Year Quarter;
					%demomlevel(levelvars=agegroupnum hispanic quarter year, indataset=_denom_lvl_HAGYQ, outdataset=_denom_lvl_HAGYQ, where=%str(year ne . and quarter ne .),
								createsumsby=agegroupnum hispanic, createsumsdimout=hispanic, createsumsinfile=worktemp._denom&Group.age);
				%end; /* agegroup requested */

				%if "&geog." = "Y" %then %do;

					/*-------------------------------------------------*/
				    /* Geographic categories                           */
				    /*-------------------------------------------------*/

					/*Produce Uncertain level stratifications, regardless of Geographic region specified*/
					/*Need to adjust denominator for zip_date*/
					data worktemp._denom&Group._un(rename=ZipDenomEnrEndDt=DenomEnrEndDt rename=ZipDenomEnrStartDt=DenomEnrStartDt);
				    set worktemp._denom&Group.;
				    format ZipDenomEnrStartDt ZipDenomEnrEndDt date9.;
					length ZipDenomEnrStartDt ZipDenomEnrEndDt 4;
				    ZipDenomEnrStartDt=DenomEnrStartDt;
				    ZipDenomEnrEndDt=DenomEnrEndDt;

				    /*1. if zip_date < DenomEnrStartDt, full enrollment is certain*/
				    if . < zip_date < DenomEnrStartDt then do;                                
						%if "&outputdenom" = "Y" %then %do;	MemberDays=DenomEnrEndDt-DenomEnrStartDt+1;	%end;								
				        zip_uncertain = 'N';
				        output;
				    end;
				    /*2. if zip_date is missing, full enrollment is uncertain*/
				    else if zip_date = . then do;                                
						%if "&outputdenom" = "Y" %then %do; MemberDays=DenomEnrEndDt-DenomEnrStartDt+1;	%end;								
				        zip_uncertain = 'Y';
				        output;
				    end;
				    /*3. if zip_date > ZipEnrEnd, then no uncertain time*/ 
				    else if zip_date >= DenomEnrEndDt then do;                                
						%if "&outputdenom" = "Y" %then %do; MemberDays=DenomEnrEndDt-DenomEnrStartDt+1;	%end;								
				        zip_uncertain = 'Y';
				        output;
				    end;
				    /*4. if DenomEnrStartDt <= zip_date <= DenomEnrEndDt, then calculate uncertain time*/ 
				    else do;
				        zip_uncertain = 'Y';
				        /*Adjust Denom enrollment*/
				        ZipDenomEnrEndDt=zip_date;                                
						%if "&outputdenom" = "Y" %then %do; MemberDays=zip_date-DenomEnrStartDt+1; %end;								
				        output;

				        zip_uncertain = 'N';
				        ZipDenomEnrStartDt=zip_date+1;
				        ZipDenomEnrEndDt = DenomEnrEndDt;
				        %if "&outputdenom" = "Y" %then %do; MemberDays=ZipDenomEnrEndDt-ZipDenomEnrStartDt+1; %end; /*member is certain on zip_date*/
				        output;
				    end;       					
				    keep PatId ZipDenomEnrEndDt ZipDenomEnrStartDt sex Patient %if "&outputdenom" = "Y" %then %do; MemberDays %end; zip_uncertain zip_date birth_date race hispanic zip3 state hhs_reg cb_reg; 
					run;

					*Zip_Uncertain;
					%demomlevel(levelvars=zip_uncertain, indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_U);

					*Zip_Uncertain Sex;
					%demomlevel(levelvars=sex zip_uncertain, indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_U_S);

					*Zip_Uncertain Year;
					%demomlevel(levelvars=year zip_uncertain, indataset=_denom_lvl_U_Y, outdataset=_denom_lvl_U_Y, where=%str(year ne . and month eq .),
								createsumsby=zip_uncertain, createsumsdimout=zip_uncertain, createsumsinfile=worktemp._denom&Group._un); 

					%if "&agegroup_out" = "Y" %then %do;
						data worktemp._denom&Group.age_un(rename=AgeDenomEnrEndDt=DenomEnrEndDt rename=AgeDenomEnrStartDt=DenomEnrStartDt);
				        length agegroupnum 3;
				        set worktemp._denom&Group._un;
				        format AgeGroup $9.; 
				        format threshdate AgeDenomEnrStartDt AgeDenomEnrEndDt date9.;
						length threshdate AgeDenomEnrStartDt AgeDenomEnrEndDt 4;
				        AgeDenomEnrStartDt=DenomEnrStartDt;
				        AgeDenomEnrEndDt=DenomEnrEndDt;                                
				        do i=&NBSTRAT. to 1 by -1;       
				            threshdate=intnx(scan("&AGETYP.",i),birth_date,input(scan("&AGETHRESH.",i),best.),'sameday'); 
				            if threshdate <= AgeDenomEnrEndDt then do;
				               AgeGroup = scan("&AGESTRAT.",i,' ');
				               agegroupnum = i;
				               AgeDenomEnrStartDt=max(threshdate,DenomEnrStartDt);                                   
							   %if "&outputdenom" = "Y" %then %do; MemberDays=AgeDenomEnrEndDt-AgeDenomEnrStartDt+1; %end;									   
				               output;
				               AgeDenomEnrEndDt=threshdate-1;
				               if AgeDenomEnrEndDt < DenomEnrStartDt then leave;
				            end;
				            end;         
				        keep PatId AgeDenomEnrEndDt AgeDenomEnrStartDt sex AgeGroup agegroupnum Patient %if "&outputdenom" = "Y" %then %do; MemberDays %end; threshdate zip_uncertain zip3 state hhs_reg cb_reg; 
				    	run;
						
						*Zip_Uncertain AgeGroup;
						%demomlevel(levelvars=agegroupnum zip_uncertain, indataset=worktemp._denom&Group.age_un, outdataset=_denom_lvl_U_AG);
					%end; 

					%macro geogdenoms(strata=);     
						%let curlevelvars=;
						%macro getsortedlevelvars(levelvars=);
							data _denomcurlevelvars(rename=curlevelvars_out=curlevelvars);
							curlevelvars="&levelvars.";
							%alphabetizevarutil(array=a, in=curlevelvars, out=curlevelvars_out);
							run;

							proc sql noprint;
							select curlevelvars into :curlevelvars trimmed from _denomcurlevelvars;
							quit;
						%mend getsortedlevelvars;

						*&Strata.(State/Zip/Census/HHS);
						%demomlevel(levelvars=&strata., indataset=worktemp._denom&Group, outdataset=_denom_lvl_&strata.);

				        *&Strata. Sex;
						%getsortedlevelvars(levelvars=%str(&strata. sex));
						%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group, outdataset=_denom_lvl_&strata._S);    
				              
							*&Strata. Race;  
							%getsortedlevelvars(levelvars=%str(&strata. race));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group, outdataset=_denom_lvl_&strata._R);

							*&Strata. Zip_Uncertain Race;        
							%getsortedlevelvars(levelvars=%str(&strata. zip_uncertain race));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_&strata._U_R);

							*&Strata. Hispanic;  
							%getsortedlevelvars(levelvars=%str(&strata. hispanic));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group, outdataset=_denom_lvl_&strata._H);

							*&Strata. Zip_Uncertain Hispanic;        
							%getsortedlevelvars(levelvars=%str(&strata. zip_uncertain hispanic));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_&strata._U_H);
				            
						%if "&agegroup_out" = "Y" %then %do;
							*&Strata. AgeGroup;          
					        %getsortedlevelvars(levelvars=%str(&strata. agegroupnum));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group.age, outdataset=_denom_lvl_&strata._AG);   

							*&Strata. Zip_Uncertain AgeGroup;          
					        %getsortedlevelvars(levelvars=%str(&strata. agegroupnum zip_uncertain));
							%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group.age_un, outdataset=_denom_lvl_&strata._U_AG);    
				        %end;

						*&Strata. Year;
						%getsortedlevelvars(levelvars=%str(&strata. year));
						%demomlevel(levelvars=&curlevelvars., indataset=_denom_lvl_&strata._Y, outdataset=_denom_lvl_&strata._Y, where=%str(year ne . and month=.),
									createsumsby=&strata., createsumsdimout=&strata., createsumsinfile=worktemp._denom&Group.);
				                 
				    	*&Strata. Zip_Uncertain;
						%getsortedlevelvars(levelvars=%str(&strata. zip_uncertain));
						%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_&strata._U);    
				    
				        *&Strata. Zip_Uncertain Sex;        
						%getsortedlevelvars(levelvars=%str(&strata. zip_uncertain sex));
						%demomlevel(levelvars=&curlevelvars., indataset=worktemp._denom&Group._un, outdataset=_denom_lvl_&strata._U_S);
				 
				        *&Strata. Zip_Uncertain Year;
						%getsortedlevelvars(levelvars=%str(&strata. year zip_uncertain));
						%demomlevel(levelvars=&curlevelvars., indataset=_denom_lvl_&strata._U_Y, outdataset=_denom_lvl_&strata._U_Y, where=%str(year ne . and month=.),
									createsumsby=&strata. zip_uncertain, createsumsdimout=zip_uncertain, createsumsinfile=worktemp._denom&Group._un);                                           
				        %mend;
				            
				        %if %index(%lowcase(&allstrat.), zip3) > 0 %then %do;
				            %geogdenoms(strata=zip3);
				        %end;
				        %if %index(%lowcase(&allstrat.), state) > 0 %then %do;
				            %geogdenoms(strata=state);
				        %end;
				        %if %index(%lowcase(&allstrat.), hhs_reg) > 0 %then %do;
				            %geogdenoms(strata=hhs_reg);
				        %end;
				        %if %index(%lowcase(&allstrat.), cb_reg) > 0 %then %do;
				            %geogdenoms(strata=cb_reg);
				        %end;

				%end; /* geog requested */
	
				data _denomsquare;
    				format sex zip_uncertain race hispanic $1. zip3 state hhs_reg cb_reg $7.; 
    				length agegroupnum month year quarter 3;
    				call missing (of _all_);
				run;

				*Verify if at least one stratum was computed;
				%let denomlvlcount=0;
				proc sql noprint;
					select count(*) into :denomlvlcount from userstrata_denom
    				where %if &ds_suffix. eq _prev %then %do;
							tableid in ("t2cidaprev", "t2itsprev")
						  %end;
						  %else %do;
							tableid not in ("t2cidaprev", "t2itsprev")
						  %end;
					;
				quit;

				data _DenomCounts;
    				format group $40. race hispanic $1. zip3 state hhs_reg cb_reg $7.;              
    				set _denomsquare(obs=0)
    					%if %eval(&denomlvlcount. > 0) %then %do; _denom_lvl: %end;;
    				group="&itgroup.";

					* Some datasets where created to allow them to be reused for efficiency,
				  	  but their strata might not have been requested. Need to delete rows with empty level;
                    if missing(level) then delete;

    				if missing(DenNumPts) then DenNumPts = 0;

    				%if "&outputdenom" = "M" %then %do; DenNumMemDays=.; %end;
    				%else %do; if missing(DenNumMemDays) then DenNumMemDays = 0; %end;
				run;
				                                         
                *Store patient denoms to DPLocal;
                %IF %EVAL(&group.=1) | (%str(&ds_suffix.) ne %str() and %eval(&group. = &firstprevgroup.)) %then %DO;
                    data DPLocal.&RUNID._DenomCounts&ds_suffix.;
                    retain Level group sex agegroupnum year month quarter race hispanic zip3 state hhs_reg cb_reg zip_uncertain DenNumPts DenNumMemDays;
                    set _DenomCounts;
                    run;
                %END;
                %ELSE %DO;
                    proc append base=DPLocal.&RUNID._DenomCounts&ds_suffix. 
                                data=_DenomCounts force;
                    run;
                %END;
            
                *delete datasets;
                proc datasets nowarn noprint lib=work; 
                    delete _unelig: _denom_lvl:;
                quit;
                
            %mend cidadenom_prev_inc;

        /* Create Prevalent and Incident dataset for Type2 when requested */
        %if %eval(&type=2) and %eval(&numprevgroups. > 0) %then %do;
          %cidadenom_prev_inc (l = 1, ds_suffix =, where_clause = %str((where = (PrevInc = "Inc"))));
          %if %str("&autoprev.") = %str("Y") %then %do;
            %cidadenom_prev_inc (l = 2, ds_suffix =_prev, where_clause = %str((where = (PrevInc_def in ("Both","Prev")))));
          %end;
        %end;
        %else %do;
          %cidadenom_prev_inc(l=0, ds_suffix = , where_clause = );
        %end;
    
        /* Delete denom datasets from worktemp */
        proc datasets nowarn noprint lib=worktemp;
            delete _denom:;
        quit;
        proc datasets nowarn noprint lib=work;
            delete _denom: _zip:;
        quit;

        *Output run time;
        %ms_stoptimer(timestart=denomruntime);
        %ms_outputruntimes(timevar=denomruntime, step=%str(CIDA denominators), group=&itgroup., monitoringperiod=);

    %end; /*outputdenom ne N and userstrata contains relevant tables*/
    %else %do;
        /*Create empty dataset*/
        %macro createemptydenom(ds_suffix=);

            data _denomcounts;
                format group $40. level $3. race hispanic sex zip_uncertain $1. zip3 state hhs_reg cb_reg $7.;
                length year month quarter agegroupnum 3;
                call missing(group, level, race, hispanic, sex, zip_uncertain, zip3, state, hhs_reg, cb_reg, year, month, quarter, agegroupnum, DenNumPts, DenNumMemDays);
                stop;
            run;

            %if %eval(&group.=1) | (%str(&ds_suffix.) ne %str() and %eval(&group. = &firstprevgroup.)) %then %do;
                data dplocal.&RUNID._DenomCounts&ds_suffix.;
                    set _DenomCounts;
                run;
            %end;
            %else %do;
                proc append base=DPLocal.&RUNID._DenomCounts&ds_suffix. 
                            data=_DenomCounts force;
                run;
            %end;
        %mend;

        %if %eval(&type=2) and %eval(&numprevgroups. > 0) %then %do;
            %createemptydenom(ds_suffix =);
            %if %str("&autoprev.") = %str("Y") %then %do;
                %createemptydenom(ds_suffix =_prev);
            %end;
        %end;
        %else %do;
            %createemptydenom(ds_suffix=);
        %end;     

		%if "&outputdenom" ne "N" %then %do;
			%put "WARNING: (Sentinel) Denominators are requested (OUTPUTDENOM is not set to N) but USERSTRATA does not contain any related levelid.";
		%end;
    %end; /*outputdenom = N or userstrata contains relevant tables*/
            
    %put NOTE: ******** END OF MACRO: ms_cidadenom ********;

%mend ms_cidadenom;