Spaces:
Sleeping
Sleeping
deploy v2 study (40-runs baselines) at /v2/: dual-study backend + static/v2 rater UI; v1 study untouched
Browse files- static/v2/mobile.html +35 -11
static/v2/mobile.html
CHANGED
|
@@ -841,11 +841,16 @@ function rowSlotOrder(caseId, qKey, slotCount){
|
|
| 841 |
return Array.from({length:slotCount},(_,i)=>i);
|
| 842 |
}
|
| 843 |
|
| 844 |
-
/* ----- Ratings -----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 845 |
function getCaseR(cid){ if(!RATINGS[cid]) RATINGS[cid]={}; return RATINGS[cid]; }
|
| 846 |
-
function
|
| 847 |
-
function setRating(cid,
|
| 848 |
-
|
| 849 |
saveState();
|
| 850 |
updateProgress();
|
| 851 |
if (RATER.submitted_at){
|
|
@@ -861,7 +866,7 @@ function studyCompleteness(){
|
|
| 861 |
for (const c of CASES){
|
| 862 |
const slots = SHUFFLE[c.id] || c.videos.map((_,i)=>i);
|
| 863 |
for (let s=0; s<slots.length; s++){
|
| 864 |
-
const r = (RATINGS[c.id]||{})[s] || {};
|
| 865 |
for (const qk of qkeys){
|
| 866 |
expected++;
|
| 867 |
if (typeof r[qk]==="number" && r[qk]>=1 && r[qk]<=5) done++;
|
|
@@ -876,7 +881,7 @@ function caseProgress(c){
|
|
| 876 |
const expected = slots.length * TOTAL_QUESTIONS;
|
| 877 |
let done=0;
|
| 878 |
for (let s=0;s<slots.length;s++){
|
| 879 |
-
const r = (RATINGS[c.id]||{})[s] || {};
|
| 880 |
done += Object.keys(r).filter(k=>typeof r[k]==="number"&&r[k]>=1&&r[k]<=5).length;
|
| 881 |
}
|
| 882 |
return { done, expected };
|
|
@@ -920,7 +925,7 @@ function buildProgressPayload(){
|
|
| 920 |
slot: String.fromCharCode(65+slotIdx), slot_index: slotIdx,
|
| 921 |
original_index: origIdx, model: c.videos[origIdx].model,
|
| 922 |
path: c.videos[origIdx].path,
|
| 923 |
-
ratings: (RATINGS[c.id]||{})[
|
| 924 |
})),
|
| 925 |
};
|
| 926 |
}),
|
|
@@ -1007,7 +1012,7 @@ function exportJSON(){
|
|
| 1007 |
videos: slots.map((oi,si)=>({
|
| 1008 |
slot:String.fromCharCode(65+si), slot_index:si, original_index:oi,
|
| 1009 |
model:c.videos[oi].model, path:c.videos[oi].path,
|
| 1010 |
-
ratings:(RATINGS[c.id]||{})[
|
| 1011 |
})),
|
| 1012 |
};
|
| 1013 |
}),
|
|
@@ -1216,14 +1221,14 @@ function renderQuestionCard(c, q){
|
|
| 1216 |
row.appendChild(chip);
|
| 1217 |
const group = document.createElement("div");
|
| 1218 |
group.className = "likert";
|
| 1219 |
-
const cur =
|
| 1220 |
for (let s=1; s<=5; s++){
|
| 1221 |
const b = document.createElement("button");
|
| 1222 |
b.dataset.score = String(s);
|
| 1223 |
b.textContent = String(s);
|
| 1224 |
if (cur===s) b.classList.add("selected");
|
| 1225 |
b.onclick = () => {
|
| 1226 |
-
setRating(c.id,
|
| 1227 |
Array.from(group.children).forEach((bb,i)=>bb.classList.toggle("selected", (i+1)===s));
|
| 1228 |
};
|
| 1229 |
group.appendChild(b);
|
|
@@ -1454,7 +1459,26 @@ function hideSubmittedOverlay(){ document.getElementById("submittedOverlay").cla
|
|
| 1454 |
if (!isAdmin()) anonymize = true;
|
| 1455 |
// Admins see all cases; everyone else gets a deterministic 10-case subset.
|
| 1456 |
CASES = selectCasesForRater(ALL_CASES);
|
| 1457 |
-
buildShuffle(CASES);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1458 |
|
| 1459 |
// wire DOM events
|
| 1460 |
document.getElementById("drawerBtn").onclick = openDrawer;
|
|
|
|
| 841 |
return Array.from({length:slotCount},(_,i)=>i);
|
| 842 |
}
|
| 843 |
|
| 844 |
+
/* ----- Ratings -----
|
| 845 |
+
* RATINGS is keyed RATINGS[case_id][model][q_key] — by MODEL NAME, exactly
|
| 846 |
+
* like desktop index.html, so the shared localStorage blob and the
|
| 847 |
+
* server-recovered state read identically on both pages. (Older mobile code
|
| 848 |
+
* keyed by slot index; see migrateSlotKeyedRatings for the one-shot fix-up.)
|
| 849 |
+
*/
|
| 850 |
function getCaseR(cid){ if(!RATINGS[cid]) RATINGS[cid]={}; return RATINGS[cid]; }
|
| 851 |
+
function getModelR(cid, model){ const m = getCaseR(cid); if(!m[model]) m[model]={}; return m[model]; }
|
| 852 |
+
function setRating(cid, model, qk, score){
|
| 853 |
+
getModelR(cid, model)[qk] = score;
|
| 854 |
saveState();
|
| 855 |
updateProgress();
|
| 856 |
if (RATER.submitted_at){
|
|
|
|
| 866 |
for (const c of CASES){
|
| 867 |
const slots = SHUFFLE[c.id] || c.videos.map((_,i)=>i);
|
| 868 |
for (let s=0; s<slots.length; s++){
|
| 869 |
+
const r = (RATINGS[c.id]||{})[c.videos[slots[s]].model] || {};
|
| 870 |
for (const qk of qkeys){
|
| 871 |
expected++;
|
| 872 |
if (typeof r[qk]==="number" && r[qk]>=1 && r[qk]<=5) done++;
|
|
|
|
| 881 |
const expected = slots.length * TOTAL_QUESTIONS;
|
| 882 |
let done=0;
|
| 883 |
for (let s=0;s<slots.length;s++){
|
| 884 |
+
const r = (RATINGS[c.id]||{})[c.videos[slots[s]].model] || {};
|
| 885 |
done += Object.keys(r).filter(k=>typeof r[k]==="number"&&r[k]>=1&&r[k]<=5).length;
|
| 886 |
}
|
| 887 |
return { done, expected };
|
|
|
|
| 925 |
slot: String.fromCharCode(65+slotIdx), slot_index: slotIdx,
|
| 926 |
original_index: origIdx, model: c.videos[origIdx].model,
|
| 927 |
path: c.videos[origIdx].path,
|
| 928 |
+
ratings: (RATINGS[c.id]||{})[c.videos[origIdx].model] || {},
|
| 929 |
})),
|
| 930 |
};
|
| 931 |
}),
|
|
|
|
| 1012 |
videos: slots.map((oi,si)=>({
|
| 1013 |
slot:String.fromCharCode(65+si), slot_index:si, original_index:oi,
|
| 1014 |
model:c.videos[oi].model, path:c.videos[oi].path,
|
| 1015 |
+
ratings:(RATINGS[c.id]||{})[c.videos[oi].model]||{},
|
| 1016 |
})),
|
| 1017 |
};
|
| 1018 |
}),
|
|
|
|
| 1221 |
row.appendChild(chip);
|
| 1222 |
const group = document.createElement("div");
|
| 1223 |
group.className = "likert";
|
| 1224 |
+
const cur = getModelR(c.id, v.model)[q.key];
|
| 1225 |
for (let s=1; s<=5; s++){
|
| 1226 |
const b = document.createElement("button");
|
| 1227 |
b.dataset.score = String(s);
|
| 1228 |
b.textContent = String(s);
|
| 1229 |
if (cur===s) b.classList.add("selected");
|
| 1230 |
b.onclick = () => {
|
| 1231 |
+
setRating(c.id, v.model, q.key, s);
|
| 1232 |
Array.from(group.children).forEach((bb,i)=>bb.classList.toggle("selected", (i+1)===s));
|
| 1233 |
};
|
| 1234 |
group.appendChild(b);
|
|
|
|
| 1459 |
if (!isAdmin()) anonymize = true;
|
| 1460 |
// Admins see all cases; everyone else gets a deterministic 10-case subset.
|
| 1461 |
CASES = selectCasesForRater(ALL_CASES);
|
| 1462 |
+
buildShuffle(CASES);
|
| 1463 |
+
// One-shot migration: older mobile builds keyed RATINGS by slot index
|
| 1464 |
+
// instead of model name. SHUFFLE is deterministic per (rater, case), so a
|
| 1465 |
+
// numeric slot key maps back to the exact model the rater saw and scored.
|
| 1466 |
+
{
|
| 1467 |
+
let moved = 0;
|
| 1468 |
+
for (const c of CASES) {
|
| 1469 |
+
const m = RATINGS[c.id];
|
| 1470 |
+
if (!m) continue;
|
| 1471 |
+
const slots = SHUFFLE[c.id] || c.videos.map((_,i)=>i);
|
| 1472 |
+
for (const k of Object.keys(m)) {
|
| 1473 |
+
if (!/^\d+$/.test(k)) continue;
|
| 1474 |
+
const model = (c.videos[slots[Number(k)]] || {}).model;
|
| 1475 |
+
if (model && !m[model]) { m[model] = m[k]; moved++; }
|
| 1476 |
+
delete m[k];
|
| 1477 |
+
}
|
| 1478 |
+
}
|
| 1479 |
+
if (moved) console.info(`[user-study] migrated ${moved} slot-keyed rating group(s) to model keys`);
|
| 1480 |
+
}
|
| 1481 |
+
saveState();
|
| 1482 |
|
| 1483 |
// wire DOM events
|
| 1484 |
document.getElementById("drawerBtn").onclick = openDrawer;
|