Home · Beneficiaries Fund · Scoreboard

Live · Beneficiaries Fund selection

The Bootcamps Scoreboard

Cumulative test scores, live ranking, and the Beneficiaries Fund cut-off line. This is exactly where every participant stands — and where the funded seats end.

Demo mode — sample data

# Participant F1F2F3F4 C1C2C3 Taken Score Status

Ranking is by cumulative test score (sum of every bootcamp test taken). Ties break on average test %, then bootcamps taken, then who registered first. Rows above the dashed line are inside the current Fund; move Scholarships available to see the line shift.

Coordinator — enter scores & connect the Sheet

Scores live in a shared Google Sheet so every coordinator and all participants see the same board. Connect the Sheet to go live; until then this works in demo mode (saved to this browser). Turn on editing to enter scores.

In editing mode, type each participant's test score (0–100) per bootcamp; leave blank for bootcamps they didn't take. The cumulative and rank update live. Save writes to the Sheet.

Apps Script — paste into the Sheet's editor (Code.gs)
const SHEET_NAME = 'Scores';
const BOOTCAMPS = ['F1','F2','F3','F4','C1','C2','C3'];

function getSheet_() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sh = ss.getSheetByName(SHEET_NAME);
  if (!sh) {
    sh = ss.insertSheet(SHEET_NAME);
    sh.appendRow(['Name','Country','Craft'].concat(BOOTCAMPS).concat(['UpdatedBy','UpdatedAt']));
  }
  return sh;
}
function clampOrBlank_(v){ if (v === '' || v === null || v === undefined) return ''; v = Number(v); if (isNaN(v)) return ''; return Math.max(0, Math.min(100, v)); }
function json_(o){ return ContentService.createTextOutput(JSON.stringify(o)).setMimeType(ContentService.MimeType.JSON); }

function doGet() {
  const sh = getSheet_(); const data = sh.getDataRange().getValues(); data.shift();
  const rows = data.filter(r => r[0]).map(function(r){
    const scores = {}; BOOTCAMPS.forEach(function(b,i){ const v = r[3+i]; scores[b] = (v===''||v===null) ? '' : Number(v); });
    return { name:r[0], country:r[1], craft:r[2], scores:scores,
             updatedBy:r[3+BOOTCAMPS.length], updatedAt:r[4+BOOTCAMPS.length] ? new Date(r[4+BOOTCAMPS.length]).toISOString() : '' };
  });
  return json_({ ok:true, rows:rows });
}
function doPost(e) {
  try {
    const p = JSON.parse(e.postData.contents);
    const items = Array.isArray(p) ? p : [p];
    const sh = getSheet_(); const data = sh.getDataRange().getValues();
    items.forEach(function(it){
      let row = -1;
      for (let i = 1; i < data.length; i++) { if (String(data[i][0]).toLowerCase() === String(it.name).toLowerCase()) { row = i + 1; break; } }
      const vals = [it.name, it.country || '', it.craft || ''].concat(
        BOOTCAMPS.map(function(b){ return clampOrBlank_(it.scores ? it.scores[b] : ''); })
      ).concat([it.updatedBy || '', new Date()]);
      if (row === -1) { sh.appendRow(vals); data.push(vals); }
      else { sh.getRange(row, 1, 1, vals.length).setValues([vals]); }
    });
    return json_({ ok:true });
  } catch (err) { return json_({ ok:false, error:String(err) }); }
}

Setup: in the Sheet, Extensions → Apps Script, paste the code, Deploy → New deployment → Web app (Execute as: Me · Access: Anyone), copy the /exec URL into the field above, and Connect. POSTs are sent as text/plain so no CORS pre-flight is needed.

← Back to the Bootcamps