Efficient method for obtaining mosaic the most recent cloud-free Sentinel 2 observation

My goal is to create a multi-band Sentinel 2 mosaic that includes the most recent cloud/shadow-free observation for each pixel from a date range (e.g. 2021-04-01 thru 2021-06-15). I am going to be running this process for a very large area and for five time windows using the batch processing API so I am looking for any advice on ways to make this query more efficient with respect to processing units.

I have tested it out using the Requests Builder and I believe it is doing what I want based on date-wise viewing in the EO Browser but I am not exactly sure if my valid pixel indexing approach is truly taking the most recent observation so any guidance would be appreciated!

Here is the evalscript that I have so far:

//VERSION=3
function setup() {
  return {
    input: [{
      bands: [
        "B04", // red
        "B03", // green
        "B02", // blue
        "B08", // nir
        "B11", // swir
        "B12",
        "SCL", // pixel classification
        "CLM" // sen2cloudless mask
      ],
      units: "DN"
    }],
    output: [
      {
        id: "default",
        bands: 6,
        sampleType: SampleType.UINT16
      }
    ],
    mosaicking: "ORBIT"
  };
}
function filterScenes (scenes, inputMetadata) {
    return scenes.filter(function (scene) {
       return scene.date.getTime()>=(inputMetadata.to.getTime()-12*31*24*3600*1000);
    });
}
// marks pixels marked as clouds/shadows as invalid
function validate(samples) {
  var scl = samples.SCL;
  var clm = samples.CLM;
  if (scl === 3) { // SC_CLOUD_SHADOW
    return false;
  } else if (clm === 1) { // CLM = cloud
    return false;
  } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
    return false;
  } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
    return false;
  } else if (scl === 7) { // SC_CLOUD_LOW_PROBA
    // return false;
  } else if (scl === 10) { // SC_THIN_CIRRUS
    return false;
  } else if (scl === 11) { // SC_SNOW_ICE
    return false;
  } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
    return false;
  } else if (scl === 2) { // SC_DARK_FEATURE_SHADOW
    // return false;
  }
  return true;
}
function evaluatePixel(samples, scenes) {
  var clo_b02 = [];
  var clo_b03 = [];
  var clo_b04 = [];
  var clo_b08 = [];
  var clo_b11 = [];
var clo_b12 = [];
  var a = 0;
  for (var i = 0; i < samples.length; i++) {
    var sample = samples[i];
    if (sample.B02 > 0 && sample.B03 > 0 && sample.B04 > 0 && sample.B08 > 0 && sample.B11 > 0, sample.B12 > 0) {
      var isValid = validate(sample);
      if (isValid) {
        clo_b02[a] = sample.B02;
        clo_b03[a] = sample.B03;
        clo_b04[a] = sample.B04;
        clo_b08[a] = sample.B08;
        clo_b11[a] = sample.B11;
        clo_b12[a] = sample.B12;
        a = a + 1;
      }
    }
  }
  rValue = clo_b04[1]; // take first in sequence of valid pixels (most recent?)
  gValue = clo_b03[1];
  bValue = clo_b02[1];
  nValue = clo_b08[1];
  sValue = clo_b11[1];
  s2Value = clo_b12[1];
  return {
    default: [rValue, gValue, bValue, nValue, sValue, s2Value]
  };
}

Hmmm. If you want the first valid pixel, you should probably go for

clo_b04[0]

right? (and similar for others).

If you want to debug this process, you can create another output band and output the “Day of the year” in it.

E.g. this script is returning (only) the day of the year of the selected pixel:

//VERSION=3
function setup() {
  return {
    input: [{
      bands: [
        "B04", // red
        "B03", // green
        "B02", // blue
        "SCL", // pixel classification
        "CLM" // sen2cloudless mask
      ],
      units: "DN"
    }],
    output: [
      {
        id: "default",
        bands: 3,
        sampleType: SampleType.UINT16
      }
    ],
    mosaicking: "ORBIT"
  };
}
function filterScenes (scenes, inputMetadata) {
    return scenes.filter(function (scene) {
       return scene.date.getTime()>=(inputMetadata.to.getTime()-12*31*24*3600*1000);
    });
}
// marks pixels marked as clouds/shadows as invalid
function validate(samples) {
  var scl = samples.SCL;
  var clm = samples.CLM;
  if (scl === 3) { // SC_CLOUD_SHADOW
    return false;
  } else if (clm === 1) { // CLM = cloud
    return false;
  } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
    return false;
  } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
    return false;
  } else if (scl === 7) { // SC_CLOUD_LOW_PROBA
    // return false;
  } else if (scl === 10) { // SC_THIN_CIRRUS
    return false;
  } else if (scl === 11) { // SC_SNOW_ICE
    return false;
  } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
    return false;
  } else if (scl === 2) { // SC_DARK_FEATURE_SHADOW
    // return false;
  }
  return true;
}
function evaluatePixel(samples, scenes) {
  var clo_b02 = [];
  var clo_b03 = [];
  var clo_b04 = [];
  var dayOfYear = [];
  var a = 0;
  for (var i = 0; i < samples.length; i++) {
    var sample = samples[i];

    var sceneDate = scenes[i].date;
	var start = new Date(sceneDate.getFullYear(), 0, 0);	
	var diff = sceneDate - start;
	var oneDay = 1000 * 60 * 60 * 24;
	var day = Math.floor(diff / oneDay);
    
    if (sample.B02 > 0 && sample.B03 > 0 && sample.B04 > 0 ) {
      var isValid = validate(sample);
      if (isValid) {
        clo_b02[a] = sample.B02;
        clo_b03[a] = sample.B03;
        clo_b04[a] = sample.B04;
        dayOfYear[a] = day;   
        a = a + 1;
      }
    }
  }
  rValue = clo_b04[0]; // take first in sequence of valid pixels (most recent?)
  gValue = clo_b03[0];
  bValue = clo_b02[0];
  dayValue = dayOfYear[0];

  return {
    default: [dayValue, dayValue, dayValue]
  };
}

Instead of “day of the year” you could also output some other values, e.g. getTime(), but have to somehow convert it to UINT16 range of values.

Thanks for the suggestions and for reminding me of the zero index in javascript!