****************************************************************************************************
*                                           PROGRAM OVERVIEW
****************************************************************************************************
*
* PROGRAM: ms_createpov3.sas  
*
* Created (mm/dd/yyyy): 02/02/2017
* Last modified: 02/01/2018
* Version: 1.2
*
*--------------------------------------------------------------------------------------------------
* PURPOSE:
*	This program will identify all index dates meeting the inclusion/exclusion criteria    
*
*  Program inputs:                                                                                   
*	-Dataset with potential index dates (POV1)  
*	-Dataset with inclusion/exclusion codes
* 
*  Program outputs:                                                                                                                                       
* 	-Dataset with index dates meeting the inclusion/exclusion criteria (POV3)
* 
* PARAMETERS:
*  	
*
*  Programming Notes:                                                                                
*                                                                           
*
*
*--------------------------------------------------------------------------------------------------
* CONTACT INFO: 
*  Mini-Sentinel Coordinating Center
*  info@mini-sentinel.org
*
*--------------------------------------------------------------------------------------------------
*  CHANGE LOG: 
*
*   Version   Date       Initials	   Comment (reference external documentation when available)
*   -------   --------   --------   ---------------------------------------------------------------
*   1.1       12/21/17	  AP		Added CODEDAYS parameter to only consider multiple codes during
*									lookback window
*
*   1.2       02/01/18	  AP		Removed post-index enrollment requirement for exclusions (QRP-496)
*
***************************************************************************************************;

%macro ms_createpov3();

%put =====> MACRO CALLED: ms_createpov3 v1.2;

	********************************************************;
	* For Inclusions/Exclusions, the same algorithm 		;
	* applies to all types of analyses						;
	********************************************************;

	*Initialize for the case where No inclusion/exclusion criteria => keeping all patients;
    data _POV3;
    set _POV1;
    keep Patid Adate; 
    run;
    
	%let INCL=0;
    %let EXCL=0; 

	%ISDATA(dataset=&INCLUSIONCODES.);
    %IF %EVAL(&NOBS.>=1) %THEN %DO;

        *Are there any incl/excl conds to verify for this iteration?;
        data _IT&INCLUSIONCODES.;
        set &INCLUSIONCODES.;
        where group="&ITGROUP.";
        run;

        %ISDATA(dataset=_IT&INCLUSIONCODES.);
        %IF %EVAL(&NOBS.>=1) %THEN %DO;

            *Initialize list with all potential index dates;
            data _POV3;
            set _POV1;
            MustKickOutHard=0;  *cumulative;
            MustKickOutElig=0;  *cumulative;
            keep PatId Adate Enr_Start Enr_end MustKickOut:;
            run;

            *Uniqueness of _InclExcl;
            Proc Sort nodupkey data=_InclExcl out=_InclExcl;
            by Patid Adate Expiredt CondInclusion cond subcond SubCondInclusion CondFrom CondTo;
            run;

            *Get all in INCL/EXCL period claims (both incl and Excl) 
             relative to each potential index dates;
            proc sql noprint;
            create table _POV3Claims(where=(InPeriod=1)) as
            select distinct index.Patid,
                   index.Adate,
                   Inc.CondInclusion,
                   Inc.cond,
                   Inc.Subcond,
                   Inc.SubCondinclusion,
				   Inc.codedays,
				   Inc.adate as incdate,
                   %MS_PeriodsOverlap(period1=COALESCE(index.Adate+CondFrom,-999999) COALESCE(index.Adate+CondTo,999999),                                             
                                      period2=Inc.ADate Inc.Expiredt) as InPeriod
            from _POV1 as index,
                 _InclExcl as Inc
            where index.Patid = Inc.Patid
            order by index.Patid, 
                     index.Adate;
            quit;

            data _null_;
            set _IT&INCLUSIONCODES.;
            if CondInclusion=0 then call symputx("EXCL",1);
            if CondInclusion=1 then call symputx("INCL",1);
            run;
            %put EXCL = &EXCL.;
            %put INCL = &INCL.; 

			*Determine how many conditions;
			%let MAXCOND=;
			proc sql;
			select max(cond) into: maxcond
			from _IT&INCLUSIONCODES.;
			quit;

			data _NULL_;
			call symputx("MAXCOND",&MAXCOND.);*For sum issue;
			run;
			%put MAXCOND = &MAXCOND.;

			%DO COND=1 %TO &MAXCOND.;

				%let MAXSUBCOND=0;
				*Determine how many levels of subconditions needed;
				proc sql;
				select max(subcond) into: maxsubcond
				from _IT&INCLUSIONCODES. (where=(cond=&COND.));
				quit;

				data _NULL_;
				call symputx("MAXSUBCOND",&MAXSUBCOND.);*For sum issue;
				run;
				%put MAXSUBCOND = &MAXSUBCOND.;

				*Determine whether this COND is an exclusion or inclusion;
				proc sql noprint;
					select max(CondInclusion) into: inclusion
					from _IT&INCLUSIONCODES. (where=(cond=&COND.)); 

					select min(CondFrom) into: MinimumCondFrom
					from _IT&INCLUSIONCODES. (where=(cond=&COND.)); 
				quit;
				%put &INCLUSION. &MinimumCondFrom.;

				*Set data for this cond;
				data _POV3_&cond.;
				set _POV3(keep=Patid ADate ENR_START ENR_END);
                ExclCond&COND.=0;
				run;

				%DO SUBCOND=1 %TO &MAXSUBCOND.;

					%let subinclusion=0;
					*Determine if SUBCOND is an exclusion or inclusion;
					proc sql noprint;
						select max(SubCondInclusion) into: SUBCONDINCLUSION
						from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.)); 

						select min(CondFrom) into: CondFrom
						from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.)); 

						select max(codedays) into: codedays
						from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.)); 
					quit;
					%put &SUBCONDINCLUSION. &CondFrom. &codedays.;

					*Only keep if codedays satisfied (number of ADates >= codedays);
					proc means data=_POV3Claims noprint nway missing;
						class patid Adate CondInclusion cond Subcond SubCondinclusion codedays / missing;
						output out=_POV3Incl_tmp(drop=_: where=(freq>=codedays)) n=freq;
						where cond=&COND. and subcond=&SUBCOND.;
					run;

		            proc sort nodupkey data=_POV3Incl_tmp out=_POV3Incl_tmp(keep=PatId Adate);
			            by PatId Adate;  *Here Adate is of the episode, not the INCL;
			            where cond=&COND. and subcond=&SUBCOND.;
		            run;

					*Square;						
					data _POV3_&cond.;
					merge _POV3_&cond.(in=a)
					      _POV3Incl_tmp(in=b);
					by PatId Adate;

					if a and b then SubCond_&COND._&SUBCOND.=1;
					else SubCond_&COND._&SUBCOND.=0;

					if &SUBCONDINCLUSION.=1 then do;
						if a and b then SubCond_&COND._&SUBCOND.=1;
						else SubCond_&COND._&SUBCOND.=0;
					end;
					*If the subcondition is met but it is a subexclusion, then means that condition not satisfied;
					else do;
						if a and b then SubCond_&COND._&SUBCOND.=0;
						else SubCond_&COND._&SUBCOND.=1;
					end;
					run;

  				    *Check elig for the subconditions exclusions;
          			*If the condition is a CondExclusion, then it will be checked again later with different assumptions;
					%if &SUBCONDINCLUSION.=0 and &inclusion.=1 %then %do;
						data _POV3_&cond.;
						set _POV3_&cond.;
						*check that the patient meets also meets elig for this subcondition;
		            	if not(Enr_Start <= Adate + COALESCE(&CondFrom.,0)) then do;*Open ended means -infty/infty;
		     				SubCond_&COND._&SUBCOND.=0;  
                 			ExclCond&COND.=1;
						end;
						run;
					%end;
				%END; *loop subcond;
				
				*If all subconditions are satisfied, then condition is satisfied (at this point);
				data _POV3_&cond.;
				set _POV3_&cond.;
				Cond&cond.=sum(of SubCond_&COND._1-SubCond_&COND._&MAXSUBCOND.)=&MAXSUBCOND.;
				run;
			
				%let condFrom2=.;
				data _null_;
				set _IT&INCLUSIONCODES. (where=(cond=&COND.));
				if CondInclusion=0 then do;
					call symputx('CondFrom2',"&MinimumCondFrom.");
				end;
				run;
				%put &CondFrom. &CondFrom2.;

        		*Manage if an exclusion - if you have one hard exclusion 
         		or not enough elig, you are out;
				data _POV3_&cond.;
				set _POV3_&cond.;
				if &INCLUSION.=0 then do;
	          	*if the patient had cond&cond.=1 then can never be included;
	          		if Cond&cond.=1 then MustKickOutHard_&cond.=1;
	          		else MustKickOutHard_&cond.=0;
	          		*flip the dummy;
	          		Cond&cond.=Cond&cond.=0;
	  				*Because we can have CondInclusion=0 and SubInclusion=1, we still need to consider if enough elig in this case;
	          		MustKickOutElig_&cond.=0;
	          		if not(Enr_Start <= Adate + COALESCE(&CondFrom2.,0)) then do;
	            		MustKickOutElig_&cond.=1;
	            		ExclCond&COND.=1;  *Open ended means -infty/infty;
	            		Cond&cond.=0;
	          		end;
				end;

				*Create a dummy to remember if this condition was an inclusion or and exlusion;
				InclCond&COND.=&INCLUSION.;
				run;

				*Put all together;
				data _POV3;
				merge _POV3(in=a)
				      _POV3_&cond.(in=b keep=PatId Adate Cond: SubCond: Excl: InclCond: MustKickOut:);
				by PatId Adate;
        		if MustKickOutHard_&cond.=1 then MustKickOutHard=1;
        		if MustKickOutElig_&cond.=1 then MustKickOutElig=1;
				if a;
				run;
			%END;*loop cond;

			*Clean up;
			data _POV3;															
			set _POV3;
      		array _cond(*) Cond1-cond&maxcond.;
      		array _InclCond(*) InclCond1-InclCond&maxcond.;

        	*At least one inclusion criterion;
	        %if &maxcond. >1 %then %do;
				inclusion=max(of InclCond1-InclCond&maxcond.);
			%end;
			%else %do;
	        	inclusion=InclCond1;
			%end;

	        *Initialize AnyCond;
	        if inclusion ne 1 then AnyCond=1;
	        else do;
	          AnyCond=0;
	          *Find those with at least on inclusion met;
	    			do i=1 to &maxcond.;
	            if _cond(i)=1 and _InclCond(i)=1 then AnyCond=1;
	    			end;	
	        end;
 
	        *Kick out any index not meeting the elig criteria for at least one exlcusion
	         or that has at least one hard exclusion (claim).;
	        if MustKickOutElig or MustKickOuthard then AnyCond=0;
	        drop inclusion;
		  	run;

			data _POV3Excl1;	*for attrition;												
			set _POV3;
			if MustKickOutHard then output _POV3Excl1;
			run;

			data _POV3Excl2;	*for attrition;	
			set _POV3;
			if MustKickOutElig then output _POV3Excl2;
			run;

			data _POV3Incl;		*for attrition;
			set _POV3;
			If AnyCond then output _POV3Incl;
			run;

			data _POV3;
			set _POV3Incl;
			run;

        %END; *_ITINCLUSIONCODES;
    %END; *INCLUSIONCODES;

%put NOTE: ********END OF MACRO: ms_createpov3 v1.2********;

%mend ms_createpov3;