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.
| # | Participant | F1 | F2 | F3 | F4 | C1 | C2 | C3 | 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.
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.