Hugdrigo's picture
Upload 3 files
84c5af3 verified
Raw History Blame Contribute Delete
56.3 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#080f19"><meta name="description" content="LexiBench, an offline bilingual response-evaluation laboratory with transparent metrics and paired uncertainty.">
<title>LexiBench | Multilingual Evaluation Studio</title>
<style>
:root{--bg:#080f19;--side:#0b1520;--panel:#111f2b;--panel2:#152533;--stroke:#263b4b;--text:#edf5f6;--dim:#8fa7b2;--mint:#69e8c2;--aqua:#58b9ed;--gold:#f3ca82;--red:#ff8c96;--font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color-scheme:dark}*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);font-family:var(--font)}body{font-size:14px}button,input,select{font:inherit}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--mint);outline-offset:2px}a{color:var(--mint)}.layout{display:grid;grid-template-columns:250px minmax(0,1fr);min-height:100vh}.sidebar{background:linear-gradient(180deg,#0e1d29,#0a141f);border-right:1px solid #223646;padding:28px 16px 20px;display:flex;flex-direction:column;gap:24px;position:sticky;top:0;height:100vh}.brand{display:flex;align-items:center;gap:13px;padding:5px 9px}.logo{width:42px;height:42px;display:grid;place-items:center;background:#153f3f;border:1px solid #387a70;border-radius:14px;font-size:23px;box-shadow:0 0 25px #15463f44}.brand b{font-size:19px;letter-spacing:-.4px}.brand small{display:block;color:var(--dim);font-size:10px;letter-spacing:1.7px;text-transform:uppercase;margin-top:4px}.nav-cap{font-size:10px;text-transform:uppercase;letter-spacing:2.1px;color:#71909e;padding:0 13px;margin-bottom:10px}.nav{display:grid;gap:4px}.navbtn{display:flex;align-items:center;gap:13px;text-align:left;border:1px solid transparent;border-radius:10px;background:transparent;color:#b0c2ca;font-size:13px;padding:12px 14px;width:100%}.navbtn svg{opacity:.65}.navbtn:hover{background:#172c38;color:#fff}.navbtn.active{color:var(--mint);background:#14352f;border-color:#21584c;font-weight:700}.navbtn.active svg{opacity:1}.nav .num{font-family:monospace;color:#6f8a9c;font-size:11px;min-width:24px}.sidebar-foot{margin-top:auto;border-top:1px solid var(--stroke);padding:18px 11px;color:var(--dim);font-size:11px;line-height:1.7}.dot{width:7px;height:7px;border-radius:50%;display:inline-block;background:var(--mint);margin-right:7px}.main{min-width:0;padding:0 34px 50px;max-width:1670px;width:100%;margin:auto}.topbar{height:84px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #1c3340;gap:16px}.eyebrow{color:var(--mint);letter-spacing:2.2px;font-weight:700;font-size:10px;text-transform:uppercase}.top-right{display:flex;gap:12px;align-items:center;color:#a9bdc5;font-size:11px}.pill{border:1px solid #365548;border-radius:20px;background:#143326;padding:7px 10px;color:#98ebc6;font-size:11px;white-space:nowrap}.header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:29px 0 22px}.header h1{font-size:clamp(25px,2.45vw,38px);letter-spacing:-1.5px;line-height:1.12;margin:7px 0 9px}.header p{color:var(--dim);font-size:13px;line-height:1.55;max-width:760px;margin:0}.btns{display:flex;gap:9px;flex-wrap:wrap;justify-content:flex-end}.btn{border:1px solid #35505f;border-radius:9px;padding:11px 14px;color:#d6e6ed;background:#1a2d3b;font-size:12px;font-weight:650;display:inline-flex;align-items:center;justify-content:center;gap:8px}.btn:hover{filter:brightness(1.15)}.btn.primary{color:#052d24;background:var(--mint);border-color:var(--mint)}.btn.subtle{background:transparent}.controls{display:grid;grid-template-columns:minmax(180px,1.4fr) repeat(3,minmax(105px,1fr));gap:11px;margin:1px 0 20px;padding:15px;border:1px solid var(--stroke);background:#101d29;border-radius:14px}.control label{color:#839eaa;text-transform:uppercase;letter-spacing:1px;font-weight:700;font-size:9px;display:block;margin-bottom:8px}.control select,.control input{width:100%;background:#0c1924;color:#e5eff3;border:1px solid #2d4654;border-radius:8px;padding:10px 11px;font-size:12px;min-height:39px}.control select:hover{border-color:#658394}.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:13px;margin-bottom:18px}.card{background:linear-gradient(145deg,#132431,#0e1c28);border:1px solid #28404f;border-radius:14px;padding:20px;min-width:0}.metric-label{color:#8faab4;letter-spacing:1px;font-size:10px;font-weight:750;text-transform:uppercase}.metric-val{font-weight:750;font-size:clamp(25px,2.5vw,37px);letter-spacing:-1.8px;margin:14px 0 7px;font-variant-numeric:tabular-nums;white-space:nowrap}.metric-sub{color:#809ba7;font-size:11px;line-height:1.6}.mint{color:var(--mint)}.blue{color:var(--aqua)}.gold{color:var(--gold)}.red{color:var(--red)}.columns{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(310px,1fr);gap:16px;margin-bottom:17px}.panel{background:#101e2b;border:1px solid #28404e;border-radius:15px;padding:21px;min-width:0}.panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:22px}.panel h2{font-size:16px;letter-spacing:-.25px;margin:0 0 5px}.panel .desc{font-size:11px;color:var(--dim);margin:0;line-height:1.6}.legend{display:flex;gap:14px;align-items:center;flex-wrap:wrap;font-size:11px;color:#b9cbd2}.legend i{width:9px;height:9px;display:inline-block;border-radius:3px;margin-right:6px}.small-stat{font-family:ui-monospace,SFMono-Regular,monospace;color:#c7d9e0;font-size:12px}.table-wrap{overflow:auto;max-width:100%}table{border-collapse:collapse;width:100%;font-size:12px;min-width:510px}th{text-align:left;text-transform:uppercase;letter-spacing:1px;font-size:9px;color:#829eaa;font-weight:750;border-bottom:1px solid #2a4351;padding:10px 9px}td{padding:13px 9px;border-bottom:1px solid #243846;color:#cfdee4;vertical-align:middle}tbody tr:hover{background:#1a2d3a}th.num,td.num{text-align:right;font-variant-numeric:tabular-nums}.bar-track{height:9px;width:100%;background:#203440;border-radius:5px;overflow:hidden}.bar-inner{height:100%;border-radius:5px;background:var(--mint)}.bar-inner.a{background:var(--aqua)}.bar-row{display:grid;grid-template-columns:136px minmax(80px,1fr) 65px;gap:12px;align-items:center;margin:17px 0}.bar-row strong{font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bar-row span{font-size:11px;color:#acc2cb;font-variant-numeric:tabular-nums;text-align:right}.bar-pair{display:grid;gap:4px}.empty{padding:40px 15px;color:#94acb6;text-align:center}.insight{border-left:3px solid var(--mint);background:#142a30;border-radius:0 10px 10px 0;padding:14px 15px;margin-top:18px;font-size:12px;color:#c8d9de;line-height:1.7}.insight.warning{border-color:var(--gold);background:#2c291d}.tag{border:1px solid #38505a;padding:5px 8px;border-radius:7px;color:#acc8d0;background:#192b36;font-size:10px;display:inline-block}.tag.es{border-color:#4c6351;color:#a9dfad}.tag.en{border-color:#375b74;color:#9bd1ee}.scope{color:#8fb0bd;font-size:11px;margin-top:5px}.note{border:1px solid #385058;background:#142734;border-radius:12px;padding:16px 17px;color:#b1c8d1;font-size:11px;line-height:1.75;margin:16px 0}.section{margin-bottom:18px}.no-show{display:none!important}.stack{display:grid;gap:14px}.formline{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.formline input,.formline select{background:#0c1924;color:white;border:1px solid var(--stroke);padding:10px 11px;border-radius:8px;max-width:100%}.formline input{flex:1;min-width:160px}.chart{width:100%;min-height:160px;display:block}.case{border:1px solid var(--stroke);padding:14px;background:#122530;border-radius:11px;margin-bottom:10px}.case:hover{border-color:#4c7177}.case header{display:flex;gap:10px;align-items:center;justify-content:space-between}.case h3{font-size:12px;line-height:1.5;margin:0 0 7px}.case p{margin:7px 0;color:#adc2cb;font-size:11px;line-height:1.65}.case-columns{display:grid;grid-template-columns:1fr 1fr;gap:12px}.answer{background:#0b1a23;border-radius:8px;padding:12px;font-size:11px;line-height:1.65;color:#c7dbe1;word-break:break-word}.answer strong{display:block;text-transform:uppercase;letter-spacing:1px;font-size:9px;margin-bottom:8px;color:#91abb4}.method-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.method{background:#132632;border:1px solid #304956;padding:15px;border-radius:11px}.method b{color:#d8eff0;font-size:12px}.method p{color:#9fb9c3;font-size:11px;line-height:1.65;margin:8px 0 0}.footer{margin-top:24px;display:flex;justify-content:space-between;align-items:center;color:#6c8997;font-size:10px;line-height:1.6;gap:10px}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.toast{position:fixed;bottom:23px;right:24px;background:#173b31;border:1px solid #429783;border-radius:11px;color:#d5fdef;box-shadow:0 17px 60px #0006;padding:14px 20px;z-index:50;max-width:450px}.toast.err{background:#45242b;border-color:#8f494d}.mobile-menu{display:none}.upload-box{display:flex;flex-direction:column;gap:10px;padding:23px;border:1px dashed #4b7180;border-radius:13px;background:#102835}.upload-box input{font-size:12px}.hint{font-size:11px;color:var(--dim);line-height:1.7}.datasummary{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.datasummary>div{padding:16px;background:#142937;border:1px solid #294758;border-radius:11px}.datasummary b{font-size:24px;display:block;margin-bottom:6px}.datasummary span{font-size:10px;color:var(--dim)}.footlabel{font-size:10px;color:#7f9aa5}
@media(max-width:1250px){.layout{grid-template-columns:210px minmax(0,1fr)}.main{padding:0 22px 34px}.columns{grid-template-columns:1fr 1fr}.card{padding:16px}.metric-val{font-size:27px}}@media(max-width:930px){.sidebar{position:fixed;z-index:20;left:0;top:0;width:246px;transform:translateX(-110%);transition:transform .2s}.sidebar.open{transform:translateX(0);box-shadow:0 0 0 100vw #0009}.layout{display:block}.mobile-menu{display:flex}.main{padding:0 17px 34px}.topbar{height:69px}.top-right{font-size:10px}.header{padding:24px 0 18px;flex-direction:column}.btns{justify-content:flex-start}.controls{grid-template-columns:repeat(2,minmax(0,1fr))}.kpis{grid-template-columns:repeat(2,1fr)}.columns{grid-template-columns:1fr}.method-grid{grid-template-columns:1fr 1fr}}@media(max-width:540px){.main{padding:0 12px 24px}.topbar{height:61px}.top-right .hide-mobile{display:none}.header h1{font-size:27px}.header p{font-size:12px}.controls{grid-template-columns:1fr 1fr;padding:11px;gap:9px}.control label{font-size:9px}.control select{padding:9px 6px}.kpis{gap:9px}.card{padding:13px}.metric-val{font-size:25px;letter-spacing:-1px}.metric-label{font-size:9px}.metric-sub{font-size:10px}.panel{padding:14px}.panel-head{flex-direction:column;gap:8px}.method-grid{grid-template-columns:1fr}.case-columns{grid-template-columns:1fr}.footer{flex-direction:column;align-items:flex-start}.bar-row{grid-template-columns:82px minmax(50px,1fr) 48px;gap:7px}.bar-row strong,.bar-row span{font-size:10px}.datasummary b{font-size:19px}.btn{padding:10px 11px;font-size:11px}}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" id="sidebar" aria-label="Primary navigation">
<div class="brand"><div class="logo" aria-hidden="true">◈</div><div><b>LexiBench</b><small>Evaluation intelligence</small></div></div>
<div><div class="nav-cap">Workspace</div><nav class="nav" aria-label="Analysis modules">
<button class="navbtn active" data-nav="overview"><span class="num">01</span> ◫ &nbsp; Evaluation overview</button>
<button class="navbtn" data-nav="metrics"><span class="num">02</span> ▥ &nbsp; Slice diagnostics</button>
<button class="navbtn" data-nav="uncertainty"><span class="num">03</span> ◈ &nbsp; Statistical uncertainty</button>
<button class="navbtn" data-nav="cases"><span class="num">04</span> ≡ &nbsp; Case explorer</button>
<button class="navbtn" data-nav="data"><span class="num">05</span> ⇩ &nbsp; Dataset workbench</button>
<button class="navbtn" data-nav="methodology"><span class="num">06</span> ⓘ &nbsp; Methods & limitations</button>
</nav></div>
<div class="sidebar-foot"><span class="dot"></span> OFFLINE · PRIVATE BY DESIGN<br>No API key · No inference service<br><br>DEMO DATA: SYNTHETIC<br>All calculations run in the browser.</div>
</aside>
<main class="main">
<div class="topbar"><div style="display:flex;align-items:center;gap:12px"><button id="menuToggle" class="btn mobile-menu" aria-label="Toggle menu">☰</button><span class="eyebrow">AI QUALITY / BENCHMARK RESEARCH</span></div><div class="top-right"><span class="hide-mobile">NO CLOUD COMPUTE</span><span class="pill"><span class="dot"></span> PRIVATE ANALYSIS</span></div></div>
<div id="content" aria-live="polite"></div>
</main>
</div>
<div id="toast" role="status" class="toast no-show"></div>
<script>
'use strict';
// LexiBench core. All analysis runs locally; no external services or libraries.
const Core = (() => {
const MAX_ROWS = 1200;
function normalize(s) {return String(s ?? '').normalize('NFKC').toLowerCase().replace(/[\p{P}\p{S}]+/gu, ' ').replace(/\s+/g, ' ').trim();}
function tokens(s) {return normalize(s).match(/[\p{L}\p{N}]+/gu) || [];}
function clamp(x,a,b){return Math.max(a, Math.min(b,x));}
function f1(a,b) {
if (!a.length && !b.length) return 1;
if (!a.length || !b.length) return 0;
const counts=new Map(); for(const w of a) counts.set(w,(counts.get(w)||0)+1);
let matches=0;for(const w of b){const k=counts.get(w)||0;if(k){counts.set(w,k-1);matches++;}}
return matches ? 2*matches/(a.length+b.length) : 0;
}
function lcs(a,b) {
if (!a.length||!b.length)return 0;
// Bound cost for uploaded very long strings.
a=a.slice(0,240);b=b.slice(0,240);
let prev=new Uint16Array(b.length+1);
for(let i=1;i<=a.length;i++){
const next=new Uint16Array(b.length+1);
for(let j=1;j<=b.length;j++)next[j]=a[i-1]===b[j-1] ? prev[j-1]+1 : Math.max(prev[j],next[j-1]);
prev=next;
}return prev[b.length];
}
function rougeL(a,b){if(!a.length&&!b.length)return 1;if(!a.length||!b.length)return 0;const k=lcs(a,b);return 2*k/(Math.min(a.length,240)+Math.min(b.length,240));}
function grams(s,n) {const m=new Map();for(let i=0;i+n<=s.length;i++){const g=s.slice(i,i+n);m.set(g,(m.get(g)||0)+1);}return m;}
function chrF(ref,hyp) {
ref=normalize(ref).replace(/\s+/g,' ');hyp=normalize(hyp).replace(/\s+/g,' ');
if(!ref&&!hyp)return 1;
if(!ref||!hyp)return 0;
let pr=0,re=0,cnt=0;
for(let n=1;n<=3;n++){
const a=grams(ref,n),b=grams(hyp,n);
let na=0,nb=0,match=0;
for(const v of a.values())na+=v;
for(const [g,v] of b){nb+=v;match+=Math.min(v,a.get(g)||0);}
if(na&&nb){pr+=match/nb;re+=match/na;cnt++;}
}
if(!cnt)return 0;
const p=pr/cnt,r=re/cnt;
return p+r ? 5*p*r/(4*p+r) : 0;
}
function score(reference, candidate){
const a=tokens(reference),b=tokens(candidate);
const tokenF1=f1(a,b), rouge=rougeL(a,b), chrf=chrF(reference,candidate);
return {exact:normalize(reference)===normalize(candidate)?1:0,tokenF1,rougeL:rouge,chrf,combined:.5*tokenF1+.3*rouge+.2*chrf, lengthRatio:a.length ? b.length/a.length : (b.length?0:1)};
}
const mean = a => a.length ? a.reduce((s,v)=>s+v,0)/a.length:0;
function metrics(rows,metric='combined'){
if(!rows.length)return {a:0,b:0,delta:0,n:0,winA:0,winB:0,ties:0};
const av=rows.map(x=>x.scoresA[metric]),bv=rows.map(x=>x.scoresB[metric]);
let wa=0,wb=0,t=0;
for(let i=0;i<rows.length;i++) {if(av[i]>bv[i]+1e-9)wa++;else if(bv[i]>av[i]+1e-9)wb++;else t++;}
return {a:mean(av),b:mean(bv),delta:mean(bv)-mean(av),n:rows.length,winA:wa,winB:wb,ties:t};
}
function hash(s){let h=2166136261;for(let i=0;i<s.length;i++){h=Math.imul(h^s.charCodeAt(i),16777619);}return h>>>0;}
function random(seed){let x=(seed>>>0)||1;return ()=>{x^=x<<13;x^=x>>>17;x^=x<<5;return (x>>>0)/4294967296;};}
function bootstrap(rows,metric='combined',reps=600,seed=1842) {
const diffs=rows.map(r=>r.scoresB[metric]-r.scoresA[metric]);
if(!diffs.length)return {estimate:0,lo:0,hi:0,distribution:[],pApprox:null};
const rnd=random(seed);const distribution=[];let extremes=0;
const estimate=mean(diffs);
for(let rep=0;rep<reps;rep++){
let total=0;
for(let i=0;i<diffs.length;i++)total+=diffs[Math.floor(rnd()*diffs.length)];
distribution.push(total/diffs.length);
}
distribution.sort((a,b)=>a-b);
// Paired sign-flip randomization p-value for sharp null of exchangeability, not universal causal inference.
for(let rep=0;rep<reps;rep++){
let total=0;
for(const d of diffs)total+=(rnd()<.5?-d:d);
if(Math.abs(total/diffs.length)+1e-12>=Math.abs(estimate))extremes++;
}
return {estimate,lo:distribution[Math.floor(.025*(reps-1))],hi:distribution[Math.ceil(.975*(reps-1))],distribution,pApprox:(extremes+1)/(reps+1)};
}
function csvParse(text){
// Strict-enough RFC4180 parser with embedded quotes, newlines, commas.
text=String(text).replace(/^\uFEFF/,'');
if(text.length>2500000)throw Error('CSV supera el límite de 2.5 MB.');
const rows=[];let row=[],val='',quoted=false,afterQuote=false;
for(let i=0;i<text.length;i++){
const c=text[i];
if(quoted){if(c==='"'){if(text[i+1]==='"'){val+='"';i++;}else{quoted=false;afterQuote=true;}}else{val+=c;}continue;}
if(afterQuote){if(c===','||c==='\n'||c==='\r')afterQuote=false;else if(c===' '||c==='\t')continue;else throw Error('CSV: contenido inesperado después de comillas.');}
if(c==='"'){if(val.trim())throw Error('CSV: comillas inesperadas.');quoted=true;}
else if(c===','){row.push(val);val='';}
else if(c==='\n'||c==='\r'){if(c==='\r'&&text[i+1]==='\n')i++;row.push(val);if(row.some(v=>v.trim()))rows.push(row);row=[];val='';}
else val+=c;
}
if(quoted)throw Error('CSV: comillas sin cerrar.');
row.push(val);if(row.some(v=>v.trim()))rows.push(row);
return rows;
}
function validateData(raw){
if(!Array.isArray(raw)||raw.length<2)throw Error('Se requieren al menos 2 registros.');
if(raw.length>MAX_ROWS)throw Error('Máximo '+MAX_ROWS+' filas.');
const ids=new Set();
const rows=raw.map((r,i)=>{
const obj={}; for(const k of ['id','language','domain','difficulty','prompt','reference','model_a','model_b'])obj[k]=String(r[k]??'').trim();
if(!obj.id)obj.id=String(i+1);
if(ids.has(obj.id))throw Error('ID repetido: '+obj.id);ids.add(obj.id);
for(const col of ['language','domain','difficulty','prompt','reference','model_a','model_b']){
if(!obj[col])throw Error(`Fila ${i+2}: falta ${col}.`);
if(obj[col].length>1400)throw Error(`Fila ${i+2}: ${col} supera 1400 caracteres.`);
}
if(!['en','es'].includes(obj.language.toLowerCase()))throw Error(`Fila ${i+2}: language debe ser en o es.`);
obj.language=obj.language.toLowerCase();
obj.scoresA=score(obj.reference,obj.model_a);obj.scoresB=score(obj.reference,obj.model_b);
return obj;
});return rows;
}
const columns=['id','language','domain','difficulty','prompt','reference','model_a','model_b'];
function fromCSV(text){
const all=csvParse(text);if(all.length<3)throw Error('CSV requiere cabecera y al menos 2 registros.');
const header=all.shift().map(x=>x.trim().toLowerCase());
const missing=columns.filter(c=>!header.includes(c));if(missing.length)throw Error('Columnas faltantes: '+missing.join(', '));
const rows=all.map((data,i)=>{if(data.length!==header.length)throw Error(`Fila ${i+2}: número incorrecto de columnas.`);return Object.fromEntries(header.map((k,j)=>[k,data[j]]));});
return validateData(rows);
}
function csvCell(v){let s=String(v??'');if(/^[\s]*[=+@\-]/.test(s))s="'"+s;return '"'+s.replace(/"/g,'""')+'"';}
function toCSV(rows,extended=false){
const cols=extended?[...columns,'combined_a','combined_b','delta_b_minus_a']:columns;
return [cols.join(','),...rows.map(r=>cols.map(k=>csvCell(k==='combined_a'?r.scoresA.combined.toFixed(5):k==='combined_b'?r.scoresB.combined.toFixed(5):k==='delta_b_minus_a'?(r.scoresB.combined-r.scoresA.combined).toFixed(5):r[k])).join(','))].join('\r\n');
}
return {normalize,tokens,score,f1,rougeL,chrF,metrics,bootstrap,csvParse,fromCSV,validateData,toCSV,columns,hash,random,mean};
})();
if(typeof module!=='undefined')module.exports=Core;
'use strict';
const Samples=(()=>{
const source=[
['en','Operations','Basic','What is a service-level agreement?','A service-level agreement defines measurable service commitments between a provider and a customer.'],
['en','Operations','Advanced','How should a team interpret a growing backlog?','A growing backlog can indicate arrivals exceed processing capacity; verify demand, staffing, and case complexity.'],
['en','Operations','Basic','Define cycle time in operations.','Cycle time is the elapsed time required to complete one unit of work.'],
['en','Operations','Advanced','Why can higher utilization increase waiting time?','As utilization approaches capacity, random arrivals and service variability can cause waiting times to rise sharply.'],
['en','Research','Basic','What does a confidence interval describe?','A confidence interval describes a procedure that covers the population parameter at its stated long-run rate.'],
['en','Research','Advanced','Why does correlation not establish causation?','Correlation can reflect confounding, reverse causality, or coincidence rather than a direct causal effect.'],
['en','Research','Advanced','What is data leakage in model validation?','Data leakage occurs when training or selection uses information that would not be available at prediction time.'],
['en','Research','Basic','Why report a dataset sample size?','Sample size helps readers assess precision, statistical power, and the scope of the evidence.'],
['en','Support','Basic','How should a support team record an incident?','Record the incident time, impact, affected systems, owner, status, and actions taken.'],
['en','Support','Advanced','Why prioritize incidents by impact?','Impact-based prioritization helps teams address disruptions affecting the most users or critical services first.'],
['en','Support','Basic','What is first response time?','First response time is the interval between a customer request and the first meaningful support reply.'],
['en','Support','Advanced','How can teams reduce repeated support requests?','Identify recurring root causes, improve help content, and fix process or product issues.'],
['en','Governance','Basic','What is a conflict of interest?','A conflict of interest occurs when secondary interests may improperly influence professional judgment.'],
['en','Governance','Advanced','Why document a decision rationale?','A documented rationale makes assumptions, tradeoffs, and accountability easier to inspect and challenge.'],
['en','Governance','Basic','Define an audit trail.','An audit trail records relevant actions, changes, timestamps, and responsible parties.'],
['en','Governance','Advanced','Why separate monitoring from evaluation?','Monitoring tracks ongoing activities while evaluation examines outcomes, effectiveness, and attribution.'],
['es','Operaciones','Básico','¿Qué es un indicador de nivel de servicio?','Un indicador de nivel de servicio mide el cumplimiento de un compromiso de atención previamente definido.'],
['es','Operaciones','Avanzado','¿Por qué aumenta la espera cuando el sistema se satura?','Cuando la utilización se acerca a la capacidad, pequeñas variaciones de demanda pueden aumentar mucho la espera.'],
['es','Operaciones','Básico','¿Qué significa capacidad operativa?','La capacidad operativa es la cantidad de trabajo que un sistema puede procesar en un período determinado.'],
['es','Operaciones','Avanzado','¿Cómo identificar un cuello de botella?','Compara tasas de entrada y salida, acumulación de trabajo y utilización de cada etapa del proceso.'],
['es','Investigación','Básico','¿Qué es una hipótesis?','Una hipótesis es una afirmación comprobable que anticipa una relación entre variables.'],
['es','Investigación','Avanzado','¿Qué implica la validez externa?','La validez externa evalúa en qué medida los resultados pueden generalizarse a otros contextos o poblaciones.'],
['es','Investigación','Básico','¿Para qué sirve una muestra representativa?','Una muestra representativa busca reflejar características relevantes de la población estudiada.'],
['es','Investigación','Avanzado','¿Qué problema genera seleccionar casos por el resultado?','Seleccionar casos según el resultado puede introducir sesgo y distorsionar las asociaciones observadas.'],
['es','Atención','Básico','¿Qué es una solicitud de soporte?','Una solicitud de soporte es un pedido de ayuda o resolución registrado por un usuario.'],
['es','Atención','Avanzado','¿Qué hacer ante una falla recurrente?','Se debe analizar la causa raíz, priorizar una corrección permanente y comunicar los avances.'],
['es','Atención','Básico','¿Cómo definir el tiempo de resolución?','El tiempo de resolución mide el intervalo desde la apertura hasta el cierre efectivo de un caso.'],
['es','Atención','Avanzado','¿Por qué segmentar las solicitudes de atención?','La segmentación permite identificar problemas frecuentes y adaptar recursos según complejidad y urgencia.'],
['es','Gobernanza','Básico','¿Qué es una política pública?','Una política pública es un conjunto de decisiones y acciones estatales para atender un problema colectivo.'],
['es','Gobernanza','Avanzado','¿Por qué evaluar efectos no previstos?','Los efectos no previstos pueden producir costos o beneficios que no aparecen en los objetivos iniciales.'],
['es','Gobernanza','Básico','¿Qué es la rendición de cuentas?','La rendición de cuentas exige explicar decisiones, informar resultados y asumir responsabilidades.'],
['es','Gobernanza','Avanzado','¿Qué aporta la participación ciudadana?','La participación ciudadana incorpora perspectivas afectadas, mejora la legitimidad y puede detectar fallas de diseño.']
];
const presets=[
{id:'bilingual',title:'Multilingual Research Benchmark',description:'32 synthetic QA cases, balanced between Spanish and English.',shift:0},
{id:'operations',title:'Operational Excellence Benchmark',description:'32 synthetic cases with controlled variation in wording and difficulty.',shift:2},
{id:'governance',title:'Responsible Decision Benchmark',description:'32 synthetic cases testing changes in response completeness.',shift:4}
];
const words=s=>s.split(/\s+/);
const shorten=(s,n)=>words(s).slice(0,n).join(' ').replace(/[,;:]$/,'')+'.';
function generated(label){
const config=presets.find(p=>p.id===label)||presets[0];
const rows=source.map(([language,domain,difficulty,prompt,reference],i)=>{
const seed=(i+config.shift)%8;
// Intentionally imperfect synthetic outputs. Responses do NOT come from actual LLMs.
const tok=words(reference);
let model_a=reference,model_b=reference;
if(language==='en'){
if(seed===0)model_a=reference;
if(seed===1)model_a=shorten(reference,Math.max(6,Math.floor(tok.length*.73)));
if(seed===2)model_a='A useful definition depends on the context and the available information.';
if(seed===3)model_a=shorten(reference,Math.max(5,Math.floor(tok.length*.64)));
if(seed===4)model_a=reference;
if(seed===5)model_a=reference.replace(/, /g,' ');
if(seed===6)model_a=shorten(reference,Math.max(6,Math.floor(tok.length*.8)));
if(seed===7)model_a=reference;
if(seed%5===0)model_b=shorten(reference,Math.max(6,Math.floor(tok.length*.72)));
else if(seed%4===0)model_b='The answer depends on context, available evidence, and clear definitions.';
else model_b=reference;
} else {
if(seed%4===0)model_a='Se requiere información adicional para responder adecuadamente.';
else if(seed%3===0)model_a=shorten(reference,Math.max(5,Math.floor(tok.length*.65)));
else model_a=reference;
if(seed===1)model_b=shorten(reference,Math.max(7,Math.floor(tok.length*.84)));
else if(seed===6)model_b=reference.replace(/, /g,' ');
else model_b=reference;
}
// Third preset intentionally stresses hard cases for candidate B.
if(label==='governance'&&difficulty==='Advanced' || label==='governance'&&difficulty==='Avanzado') {
if(i%3===0)model_b=language==='es'?'Esta respuesta necesita verificación adicional.':'This answer requires additional verification.';
}
return {id:label.slice(0,3).toUpperCase()+'-'+String(i+1).padStart(3,'0'),language,domain,difficulty,prompt,reference,model_a,model_b};
});return Core.validateData(rows);
}
return {presets,generated};
})();
if(typeof module!=='undefined')module.exports=Samples;
'use strict';
const App=(()=>{
const LABEL={combined:'Composite score',tokenF1:'Token F1',rougeL:'ROUGE-L F1',chrf:'chrF (1–3)',exact:'Exact match'};
const state={view:'overview',preset:'bilingual',dataset:Samples.generated('bilingual'),metric:'combined',language:'all',difficulty:'all',slice:'language',search:'',errorsOnly:false,source:'Synthetic demonstration data',name:'Multilingual Research Benchmark'};
const $=sel=>document.querySelector(sel);
const escapeHTML=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const pct=v=>(v*100).toFixed(1)+'%';
const signed=v=>(v>=0?'+':'')+(v*100).toFixed(1)+' pp';
const fixed=v=>Number(v).toFixed(3);
const filtered=()=>state.dataset.filter(r=>(state.language==='all'||r.language===state.language)&&(state.difficulty==='all'||r.difficulty===state.difficulty));
const sectionTitle={overview:['Evaluation overview','A reproducible, pairwise comparison of two response systems against curated reference answers.'],metrics:['Slice diagnostics','Explore performance differences across languages, domains, and difficulty bands.'],uncertainty:['Statistical uncertainty','Paired bootstrap confidence intervals and a sign-flip randomization diagnostic.'],cases:['Case explorer','Inspect references, system responses, and individual disagreement patterns.'],data:['Dataset workbench','Import your own anonymized CSV data, export results, or use the synthetic fixtures.'],methodology:['Methods and limitations','Transparent lexical metrics, quality controls, and methodological caveats.']};
function titleBar(){const [name,description]=sectionTitle[state.view];return `<div class="header"><div><div class="eyebrow">Project 37 / Research workspace</div><h1>${name}</h1><p>${description}</p></div><div class="btns"><button class="btn subtle" data-action="export-report">↗ Export report</button><button class="btn primary" data-action="export-results">↓ Download CSV</button></div></div>`;}
function controls(){const dims=[['all','All languages'],['en','English'],['es','Español']];const diffs=['all',...new Set(state.dataset.map(x=>x.difficulty))]; const diffOptions=diffs.map(v=>`<option value="${escapeHTML(v)}" ${v===state.difficulty?'selected':''}>${v==='all'?'All difficulties':escapeHTML(v)}</option>`).join('');return `<div class="controls"><div class="control"><label for="preset">Dataset</label><select id="preset"><option value="uploaded" ${state.preset==='uploaded'?'selected':''} ${state.preset!=='uploaded'?'disabled':''}>${escapeHTML(state.name)}</option>${Samples.presets.map(p=>`<option value="${p.id}" ${state.preset===p.id?'selected':''}>${escapeHTML(p.title)}</option>`).join('')}</select></div><div class="control"><label for="metric">Metric</label><select id="metric">${Object.entries(LABEL).map(([k,v])=>`<option value="${k}" ${state.metric===k?'selected':''}>${escapeHTML(v)}</option>`).join('')}</select></div><div class="control"><label for="language">Language</label><select id="language">${dims.map(([k,v])=>`<option value="${k}" ${state.language===k?'selected':''}>${v}</option>`).join('')}</select></div><div class="control"><label for="difficulty">Difficulty</label><select id="difficulty">${diffOptions}</select></div></div>`;}
function kpis(rows){const m=Core.metrics(rows,state.metric), b=Core.bootstrap(rows,state.metric,500,2701); const quality=m.delta>=0?'B leads on this sample':'A leads on this sample';return `<div class="kpis"><div class="card"><div class="metric-label">System A · baseline</div><div class="metric-val blue">${pct(m.a)}</div><div class="metric-sub">Mean ${escapeHTML(LABEL[state.metric])}</div></div><div class="card"><div class="metric-label">System B · candidate</div><div class="metric-val mint">${pct(m.b)}</div><div class="metric-sub">Mean ${escapeHTML(LABEL[state.metric])}</div></div><div class="card"><div class="metric-label">B − A difference</div><div class="metric-val ${m.delta>=0?'mint':'red'}">${signed(m.delta)}</div><div class="metric-sub">${quality}</div></div><div class="card"><div class="metric-label">Paired examples</div><div class="metric-val">${m.n}</div><div class="metric-sub">${m.winB} B wins · ${m.winA} A wins · ${m.ties} ties</div></div></div>`;}
function byGroup(rows,dim){const grouped=new Map();for(const r of rows){const key=r[dim];if(!grouped.has(key))grouped.set(key,[]);grouped.get(key).push(r);}return [...grouped.entries()].map(([label,group])=>({label,...Core.metrics(group,state.metric)})).sort((a,b)=>b.n-a.n||a.label.localeCompare(b.label));}
function groupBars(groups){if(!groups.length)return '<div class="empty">No records in selected filters.</div>';return groups.map(g=>`<div style="margin-bottom:20px"><div style="display:flex;justify-content:space-between;align-items:center;gap:12px;font-size:11px;margin-bottom:8px"><strong>${escapeHTML(g.label)}</strong><span style="color:var(--dim)">n=${g.n} · ${signed(g.delta)}</span></div><div class="bar-row" style="margin:7px 0"><strong style="color:var(--aqua)">System A</strong><div class="bar-track"><div class="bar-inner a" style="width:${g.a*100}%"></div></div><span>${pct(g.a)}</span></div><div class="bar-row" style="margin:7px 0"><strong style="color:var(--mint)">System B</strong><div class="bar-track"><div class="bar-inner" style="width:${g.b*100}%"></div></div><span>${pct(g.b)}</span></div></div>`).join('');}
function outcome(rows){const m=Core.metrics(rows,state.metric);const winner=m.delta>0?'System B':m.delta<0?'System A':'Neither system';const h=m.n<20?'This slice has fewer than 20 pairs; uncertainty may be substantial.':'Interpret lexical gains alongside manual review of correctness and factuality.';return `<div class="legend"><span><i style="background:var(--aqua)"></i>System A</span><span><i style="background:var(--mint)"></i>System B</span></div><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:22px"><div class="card" style="padding:13px"><div class="metric-label">A wins</div><div class="metric-val blue" style="font-size:25px">${m.winA}</div></div><div class="card" style="padding:13px"><div class="metric-label">B wins</div><div class="metric-val mint" style="font-size:25px">${m.winB}</div></div><div class="card" style="padding:13px"><div class="metric-label">Tied</div><div class="metric-val" style="font-size:25px">${m.ties}</div></div></div><div class="insight"><b>${winner} leads in average ${escapeHTML(LABEL[state.metric])}.</b><br>${h}</div>`;}
function basicTable(rows,dim='language'){const g=byGroup(rows,dim);return `<div class="table-wrap"><table><thead><tr><th>${escapeHTML(dim)}</th><th class="num">N</th><th class="num">System A</th><th class="num">System B</th><th class="num">B − A</th></tr></thead><tbody>${g.map(r=>`<tr><td>${escapeHTML(r.label)}</td><td class="num">${r.n}</td><td class="num">${pct(r.a)}</td><td class="num">${pct(r.b)}</td><td class="num ${r.delta>=0?'mint':'red'}">${signed(r.delta)}</td></tr>`).join('')}</tbody></table></div>`;}
function overview(rows){const m=Core.metrics(rows,state.metric);return `${kpis(rows)}<div class="columns"><section class="panel"><div class="panel-head"><div><h2>Quality by language</h2><p class="desc">Matched examples, same selected metric and filters</p></div><span class="tag">${rows.length} pairs</span></div>${groupBars(byGroup(rows,'language'))}</section><section class="panel"><div class="panel-head"><div><h2>Paired outcomes</h2><p class="desc">Case-by-case wins and ties</p></div></div>${outcome(rows)}</section></div><div class="columns"><section class="panel"><div class="panel-head"><div><h2>Domain breakdown</h2><p class="desc">Inspect performance before relying on overall averages</p></div></div>${basicTable(rows,'domain')}</section><section class="panel"><div class="panel-head"><div><h2>Evaluation principles</h2><p class="desc">Designed for transparent offline auditing</p></div></div><div class="stack"><div class="note"><strong style="color:var(--mint)">01 · Same references</strong><br>Both candidate responses are scored against the same reference answer for every example.</div><div class="note"><strong style="color:var(--mint)">02 · No remote inference</strong><br>All calculations run on this device. Uploaded CSVs stay in browser memory.</div><div class="note"><strong style="color:var(--gold)">03 · Lexical metrics ≠ truth</strong><br>Similarity does not verify factual accuracy, safety, fluency, or cultural suitability.</div></div></section></div>`;}
function metricsView(rows){const gs=byGroup(rows,state.slice);return `${kpis(rows)}<section class="panel section"><div class="panel-head"><div><h2>Performance by segment</h2><p class="desc">Use the same evaluation formula for both candidate systems.</p></div><div class="control" style="min-width:150px"><label for="slice">Segment dimension</label><select id="slice"><option value="language" ${state.slice==='language'?'selected':''}>Language</option><option value="domain" ${state.slice==='domain'?'selected':''}>Domain</option><option value="difficulty" ${state.slice==='difficulty'?'selected':''}>Difficulty</option></select></div></div>${groupBars(gs)}</section><section class="panel section"><div class="panel-head"><div><h2>Segment scorecard</h2><p class="desc">Differences represent percentage points, not relative percentages.</p></div></div>${basicTable(rows,state.slice)}</section><div class="note">A segment can reverse the overall ranking. With small sample sizes, differences may be unstable. Inspect individual cases and report the selected metric and filter settings.</div>`;}
function svgHistogram(values){if(!values.length)return ''; const w=820,h=215,pad=30, bins=24,lo=Math.min(...values),hi=Math.max(...values);const span=hi-lo||1;const hist=Array(bins).fill(0);values.forEach(v=>hist[Math.min(bins-1,Math.floor(((v-lo)/span)*bins))]++);const max=Math.max(...hist)||1;const bw=(w-2*pad)/bins;const bars=hist.map((c,i)=>`<rect x="${pad+i*bw+1}" y="${h-pad-c/max*(h-2*pad)}" width="${Math.max(1,bw-2)}" height="${c/max*(h-2*pad)}" rx="3" fill="#69e8c2" opacity="${.65+c/max*.35}"/>`).join('');return `<svg class="chart" viewBox="0 0 ${w} ${h}" aria-label="Histogram of paired bootstrap differences" role="img" xmlns="http://www.w3.org/2000/svg"><line x1="${pad}" y1="${h-pad}" x2="${w-pad}" y2="${h-pad}" stroke="#557080"/>${bars}<text x="${pad}" y="${h-8}" fill="#9bb7c3" font-size="14">${signed(lo)}</text><text text-anchor="middle" x="${w/2}" y="${h-8}" fill="#9bb7c3" font-size="14">B − A bootstrap distribution</text><text text-anchor="end" x="${w-pad}" y="${h-8}" fill="#9bb7c3" font-size="14">${signed(hi)}</text></svg>`;}
function uncertainty(rows){const b=Core.bootstrap(rows,state.metric,1000,4326), m=Core.metrics(rows,state.metric);const inconclusive=b.lo<=0&&b.hi>=0;return `${kpis(rows)}<div class="columns"><section class="panel"><div class="panel-head"><div><h2>Paired bootstrap distribution</h2><p class="desc">1,000 seeded resamples of matched example pairs</p></div><span class="tag">95% percentile interval</span></div>${svgHistogram(b.distribution)}<div class="datasummary"><div><b>${signed(b.estimate)}</b><span>Mean improvement</span></div><div><b>${signed(b.lo)}</b><span>Lower bound</span></div><div><b>${signed(b.hi)}</b><span>Upper bound</span></div></div></section><section class="panel"><div class="panel-head"><div><h2>Inference diagnostics</h2><p class="desc">Exploratory statistics for paired comparisons</p></div></div><div class="stack"><div class="note"><strong style="color:var(--mint)">Paired resampling</strong><br>Resamples whole examples so A and B remain matched. Interval estimates depend on sample representativeness.</div><div class="note"><strong style="color:var(--aqua)">Randomization check</strong><br>Approximate two-sided sign-flip p-value: <strong style="color:white">${b.pApprox===null?"N/A":b.pApprox.toFixed(3)}</strong>. This requires an appropriate exchangeability assumption.</div><div class="insight ${inconclusive?'warning':''}"><b>${inconclusive?'Interval includes zero':'Interval excludes zero'}</b><br>${inconclusive?'The observed gap is not clearly separated from zero under this bootstrap procedure.':'A difference is visible in this exploratory resampling; inspect dataset construction and validity before concluding.'}</div></div></section></div><div class="note">This is not a randomized human study or a causal estimate. The 95% interval and p-value are exploratory diagnostics. Repeated use of filters changes the interpretation. Do not claim statistically validated superiority from a synthetic sample.</div>`;}
function cases(rows){let list=rows.filter(r=>!state.search||[r.id,r.prompt,r.reference,r.domain].join(' ').toLowerCase().includes(state.search.toLowerCase()));if(state.errorsOnly)list=list.filter(r=>r.scoresA[state.metric]<.75||r.scoresB[state.metric]<.75);list=list.sort((a,b)=>Math.abs(b.scoresB[state.metric]-b.scoresA[state.metric])-Math.abs(a.scoresB[state.metric]-a.scoresA[state.metric]));return `<section class="panel"><div class="panel-head"><div><h2>Paired response inspector</h2><p class="desc">Sorted by absolute difference on the selected metric</p></div><span class="tag">${list.length} cases</span></div><div class="formline" style="margin-bottom:17px"><input type="search" id="search" placeholder="Search question, ID, domain..." value="${escapeHTML(state.search)}"><label style="font-size:11px;color:var(--dim);white-space:nowrap"><input type="checkbox" id="errorsOnly" ${state.errorsOnly?'checked':''}> Lower scores only</label></div>${list.slice(0,80).map(r=>{const a=r.scoresA[state.metric],b=r.scoresB[state.metric];return `<article class="case"><header><span class="scope">${escapeHTML(r.id)} · <span class="tag ${r.language}">${r.language.toUpperCase()}</span> <span class="tag">${escapeHTML(r.domain)}</span> <span class="tag">${escapeHTML(r.difficulty)}</span></span><strong class="${b-a>=0?'mint':'red'}" style="font-size:11px;white-space:nowrap">${signed(b-a)}</strong></header><h3>${escapeHTML(r.prompt)}</h3><p><strong>Reference:</strong> ${escapeHTML(r.reference)}</p><div class="case-columns"><div class="answer"><strong>System A · ${pct(a)}</strong>${escapeHTML(r.model_a)}</div><div class="answer"><strong>System B · ${pct(b)}</strong>${escapeHTML(r.model_b)}</div></div></article>`;}).join('')||'<div class="empty">No cases match these filters.</div>'}${list.length>80?'<div class="hint">Showing first 80 matches. Narrow filters to inspect more.</div>':''}</section>`;}
function dataView(rows){const langEn=state.dataset.filter(r=>r.language==='en').length;return `<div class="columns"><section class="panel"><div class="panel-head"><div><h2>CSV import and validation</h2><p class="desc">No account, network requests, or server upload required</p></div><span class="tag">Local only</span></div><div class="upload-box"><strong>Import your own paired evaluation dataset</strong><span class="hint">Maximum 1,200 records / 2.5 MB. Supported language codes: en, es.</span><input id="fileInput" type="file" accept=".csv,text/csv" aria-label="Upload paired evaluation CSV"><div class="formline"><button class="btn" data-action="template">↓ Download CSV template</button><button class="btn subtle" data-action="reset">↻ Restore demo</button></div></div><p class="hint">Required fields: <code>id,language,domain,difficulty,prompt,reference,model_a,model_b</code>. Quoted commas and line breaks are supported. Nonempty answers are required.</p><div class="note"><b style="color:var(--gold)">Privacy note:</b> Avoid uploading identifiable personal or confidential material to a public demo. The app does not transmit CSV content, but exported files or screenshots could expose it.</div></section><section class="panel"><div class="panel-head"><div><h2>Active dataset</h2><p class="desc">${escapeHTML(state.name)}</p></div></div><div class="datasummary"><div><b>${state.dataset.length}</b><span>Total pairs</span></div><div><b>${langEn}</b><span>English</span></div><div><b>${state.dataset.length-langEn}</b><span>Spanish</span></div></div><div class="note">Source: ${escapeHTML(state.source)}<br>Selected subset: ${rows.length} examples.</div><div class="stack"><button class="btn" data-action="export-results">↓ Download scored results CSV</button><button class="btn" data-action="export-json">↓ Download reproducible JSON</button><button class="btn" data-action="export-report">↗ Download HTML report</button></div></section></div><section class="panel"><div class="panel-head"><div><h2>Field dictionary</h2><p class="desc">Structure needed for reproducible evaluations</p></div></div><div class="table-wrap"><table><thead><tr><th>Field</th><th>Meaning</th><th>Example</th></tr></thead><tbody>${[['id','Unique example identifier','CASE-001'],['language','Language code','en / es'],['domain','Task category','Operations'],['difficulty','Difficulty label','Advanced'],['prompt','Question or user request','What is cycle time?'],['reference','Expected reference answer','Cycle time is...'],['model_a','Response from baseline system','...'],['model_b','Response from candidate system','...']].map(x=>`<tr><td><code>${x[0]}</code></td><td>${x[1]}</td><td>${escapeHTML(x[2])}</td></tr>`).join('')}</tbody></table></div></section>`;}
function methodology(rows){return `<section class="panel section"><div class="panel-head"><div><h2>Evaluation protocol</h2><p class="desc">Metrics implemented in native JavaScript, no external packages</p></div><span class="tag">Deterministic</span></div><div class="method-grid"><div class="method"><b>Exact match</b><p>Normalized output string must match the normalized reference. Case and punctuation are removed. Strict and easy to interpret.</p></div><div class="method"><b>Token F1</b><p>Harmonic mean of precision and recall for overlapping normalized tokens, accounting for word counts and repetitions.</p></div><div class="method"><b>ROUGE-L F1</b><p>Longest common subsequence overlap between reference and response tokens. Long outputs are capped at 240 tokens for runtime safety.</p></div><div class="method"><b>chrF (1–3)</b><p>Character n-gram overlap averaged across lengths 1, 2 and 3. Recall-weighted F-score with β=2. This is a lightweight variant, not sacreBLEU chrF.</p></div><div class="method"><b>Composite score</b><p>0.50 Token F1 + 0.30 ROUGE-L F1 + 0.20 chrF. An illustrative, user-transparent score, not an established quality standard.</p></div><div class="method"><b>Uncertainty estimates</b><p>Paired bootstrap 95% percentile confidence interval using a seeded pseudorandom generator. Approximate sign-flip p-value from 1,000 randomizations.</p></div></div></section><div class="columns"><section class="panel"><div class="panel-head"><div><h2>Study limitations</h2><p class="desc">Necessary before interpreting any leaderboard</p></div></div><div class="stack"><div class="note">Lexical similarity <strong>cannot verify truth, groundedness, reasoning, safety, bias, empathy, or usefulness</strong>. Correct paraphrases may score poorly; incorrect repetitions may score highly.</div><div class="note">All included demonstration responses and reference answers are synthetic. They do not represent real inference by named commercial models.</div><div class="note">Group comparisons are descriptive and sensitive to sample size, domain composition, and reference quality. The synthetic sample is not representative of any deployment population.</div></div></section><section class="panel"><div class="panel-head"><div><h2>Reproducibility checklist</h2><p class="desc">What this application exports</p></div></div><div class="stack"><div class="note">✓ Original paired examples and exact reference text<br>✓ Selected metric and active filters<br>✓ Per-case A/B scores and aggregate deltas<br>✓ Bootstrap seed and 1,000 replicate setting<br>✓ Documentation and version identifier</div><div class="insight">Recommended next step: have qualified bilingual reviewers manually score a stratified sample for factual correctness, clarity, safety and task success.</div></div></section></div>`;}
function view(){const rows=filtered();const body=state.view==='overview'?overview(rows):state.view==='metrics'?metricsView(rows):state.view==='uncertainty'?uncertainty(rows):state.view==='cases'?cases(rows):state.view==='data'?dataView(rows):methodology(rows);$('#content').innerHTML=titleBar()+controls()+body+`<footer class="footer"><span>LexiBench v1.0 · Synthetic demonstration · No API keys · Browser-only</span><span>Open educational software · MIT License · Built for reproducible evaluations</span></footer>`;
document.querySelectorAll('[data-nav]').forEach(b=>{b.classList.toggle('active',b.dataset.nav===state.view);b.setAttribute('aria-current',b.dataset.nav===state.view?'page':'false');});
bind();
}
function bind(){
$('#preset').onchange=e=>{if(e.target.value==='uploaded')return;state.preset=e.target.value;const preset=Samples.presets.find(p=>p.id===state.preset);state.name=preset.title;state.source='Synthetic demonstration data';state.dataset=Samples.generated(state.preset);state.difficulty='all';state.language='all';state.search='';view();};
$('#metric').onchange=e=>{state.metric=e.target.value;view();};
$('#language').onchange=e=>{state.language=e.target.value;view();};
$('#difficulty').onchange=e=>{state.difficulty=e.target.value;view();};
$('#slice')&&($('#slice').onchange=e=>{state.slice=e.target.value;view();});
$('#search')&&($('#search').addEventListener('change',e=>{state.search=e.target.value;view();}));
$('#errorsOnly')&&($('#errorsOnly').onchange=e=>{state.errorsOnly=e.target.checked;view();});
$('#fileInput')&&($('#fileInput').onchange=async e=>{const file=e.target.files?.[0];if(!file)return;try{if(file.size>2500000)throw Error('CSV supera el límite de 2.5 MB.');const raw=await file.text();const dataset=Core.fromCSV(raw);state.dataset=dataset;state.preset='uploaded';state.name=file.name.slice(0,70);state.source='Imported CSV, processed entirely in this browser';state.language='all';state.difficulty='all';state.search='';toast('CSV validado: '+dataset.length+' pares importados.');view();}catch(err){e.target.value='';toast(err.message,true);}});
}
function payload(){const rows=filtered(),m=Core.metrics(rows,state.metric),b=Core.bootstrap(rows,state.metric,1000,4326);return {project:'LexiBench v1.0',dataset:state.name,source:state.source,metric:state.metric,filters:{language:state.language,difficulty:state.difficulty},assumptions:'Reference-overlap metrics; not factual accuracy or causal evidence',seed:4326,bootstrapReplicates:1000,summary:{...m,interval95:[b.lo,b.hi],signFlipPApprox:b.pApprox},records:rows.map(r=>({id:r.id,language:r.language,domain:r.domain,difficulty:r.difficulty,prompt:r.prompt,reference:r.reference,model_a:r.model_a,model_b:r.model_b,scoresA:r.scoresA,scoresB:r.scoresB}))};}
function save(name,type,content){const b=new Blob([content],{type});const url=URL.createObjectURL(b);const a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),2000);toast('Descarga preparada: '+name);}
function report(){const p=payload();const m=p.summary;const values=Object.entries(LABEL).map(([k,v])=>{const s=Core.metrics(filtered(),k);return `<tr><td>${escapeHTML(v)}</td><td>${pct(s.a)}</td><td>${pct(s.b)}</td><td>${signed(s.delta)}</td></tr>`;}).join('');return `<!doctype html><html lang="en"><meta charset="utf-8"><title>LexiBench evaluation report</title><style>body{font:15px system-ui,sans-serif;margin:40px auto;max-width:840px;color:#172d36;line-height:1.6;padding:0 16px}h1{color:#115144}table{width:100%;border-collapse:collapse}td,th{border-bottom:1px solid #cdd9dc;padding:12px;text-align:left}.note{background:#edf3f1;padding:16px;border-left:4px solid #246e59}footer{margin-top:38px;color:#667;font-size:12px}</style><h1>LexiBench · Evaluation report</h1><p><b>Dataset:</b> ${escapeHTML(p.dataset)}<br><b>Source:</b> ${escapeHTML(p.source)}<br><b>Metric:</b> ${escapeHTML(LABEL[p.metric])}<br><b>Filters:</b> ${escapeHTML(JSON.stringify(p.filters))}<br><b>Paired cases:</b> ${p.records.length}</p><h2>Pairwise performance</h2><table><tr><th>Metric</th><th>Baseline A</th><th>Candidate B</th><th>B − A</th></tr>${values}</table><h2>Uncertainty</h2><p>95% paired bootstrap interval: ${signed(m.interval95[0])} to ${signed(m.interval95[1])}; approximate sign-flip p-value: ${m.signFlipPApprox===null?"N/A":m.signFlipPApprox.toFixed(3)}.</p><div class="note"><b>Interpretation:</b> These are reference-overlap metrics. They do not assess factual accuracy, safety, or downstream task success. Bootstrap statistics are exploratory. Synthetic demonstration responses do not measure real products.</div><footer>LexiBench v1.0 · Generated locally in browser · MIT</footer></html>`;}
function toast(msg,isError=false){let el=$('#toast');el.textContent=msg;el.classList.toggle('err',isError);el.classList.remove('no-show');clearTimeout(toast.t);toast.t=setTimeout(()=>el.classList.add('no-show'),4100);}
function init(){
document.querySelectorAll('[data-nav]').forEach(b=>b.onclick=()=>{state.view=b.dataset.nav;$('#sidebar').classList.remove('open');view();window.scrollTo(0,0);});
$('#menuToggle').onclick=()=>$('#sidebar').classList.toggle('open');
document.addEventListener('click',e=>{const btn=e.target.closest('[data-action]');if(!btn)return;const act=btn.dataset.action;
try{
if(act==='export-results')save('lexibench_evaluation.csv','text/csv;charset=utf-8',Core.toCSV(filtered(),true));
if(act==='export-json')save('lexibench_reproducible_report.json','application/json',JSON.stringify(payload(),null,2));
if(act==='export-report')save('lexibench_evaluation_report.html','text/html;charset=utf-8',report());
if(act==='template'){const sample=[{id:'EXAMPLE-1',language:'en',domain:'Operations',difficulty:'Basic',prompt:'What is an SLA?',reference:'An SLA defines measurable service commitments.',model_a:'An SLA describes service commitments.',model_b:'An SLA defines measurable service commitments.'},{id:'EJEMPLO-2',language:'es',domain:'Investigación',difficulty:'Básico',prompt:'¿Qué es una muestra?',reference:'Una muestra es un subconjunto de una población.',model_a:'Una muestra es una parte de una población.',model_b:'Una muestra es un subconjunto de una población.'}];save('lexibench_import_template.csv','text/csv;charset=utf-8',Core.toCSV(sample));}
if(act==='reset'){state.preset='bilingual';state.dataset=Samples.generated('bilingual');state.name=Samples.presets[0].title;state.source='Synthetic demonstration data';state.language='all';state.difficulty='all';state.search='';toast('Datos de demostración restaurados.');view();}
}catch(err){toast('No se pudo completar la acción: '+err.message,true);}
});view();
}
return {init,state,filtered,payload,report};
})();
if(typeof window!=='undefined')window.addEventListener('DOMContentLoaded',App.init);
if(typeof module!=='undefined')module.exports=App;
</script>
</body>
</html>