x.trim());
const lower=header.map(x=>x.toLowerCase());
let k=idIndex, t=labelIndex;
if(k===null||Number.isNaN(k)) k=lower.findIndex(x=>['property','relation','rid','id','pid','qid','eid'].includes(x));
if(t===null||Number.isNaN(t)) t=lower.findIndex(x=>['title','label','name'].includes(x));
if(k<0||t<0) return new Map();
const out=new Map();
for(const line of lines.slice(1)){
const cells=parseLine(line); const id=(cells[k]||'').trim(); const label=(cells[t]||'').trim(); if(id&&label) out.set(id,label);
}
return out;
}
async function loadLabels(){
const kind=sourceSelected('graph-source');
const getCSV=getCachedMetadata;
if(kind==='custom'){
const file=$('relations-file').files[0];
if(file){
const text=await file.text();
const idIndex=Number($('relation-id-column').value); const labelIndex=Number($('relation-label-column').value);
labels=parseCSVMappings(text,idIndex,labelIndex);
}
// Entity metadata supplies readable labels independently from graph membership.
// The worker includes extra metadata-only entities only on explicit opt-in.
const entities=$('entities-file').files[0];
if(entities && !$('node-column-wrap').classList.contains('hidden')){
const text=await entities.text();
const idIndex=Number($('node-id-column').value), labelIndex=Number($('node-label-column').value);
if(idIndex!==labelIndex) entityLabels=parseCSVMappings(text,idIndex,labelIndex);
}
} else if(kind==='mquake' && providerSelected()==='hf'){
const base=`https://huggingface.co/datasets/${GRAPH_DATASETS[kind].repository}/resolve/main/metadata/`;
const [rels,nodes]=await Promise.allSettled([getCSV(base+'relation_data.csv'),getCSV(base+'node_data.csv')]);
if(rels.status==='fulfilled')labels=parseCSVMappings(rels.value);
if(nodes.status==='fulfilled')entityLabels=parseCSVMappings(nodes.value);
}
if(latest) renderRelations();
}
const GLOSSARY={
'Unique triples':'Distinct directed (head, relation, tail) triples. Duplicate rows are counted separately.',
'Entities':'Distinct node IDs in the active graph. Metadata-only nodes are excluded unless explicitly included.',
'Relations':'Number of distinct relation types appearing in graph triples.',
'Largest WCC':'Share of graph entities in the largest weakly connected component, ignoring edge directions.',
'WCC':'Weakly connected component, a connected group when edge directions are ignored.',
'QA rows':'Number of selected QA records after applying the benchmark split filter.',
'Question families':'Number of distinct structural question families. Related wording variants may belong to the same family.',
'Instance-micro':'Each evaluation instance has equal weight, including each separately represented question variant.',
'Family-macro':'Average the result within each question family, then weight each family equally.',
'Answer (released)':'Evaluate the released reference answer entities (Answer-Entity).',
'Graph-Answer (graph-consistent)':'Evaluate graph-consistent endpoints (Graph-Answer-Entity) supplied in the QA collection. These are not independent gold answers.',
'Mean answer count':'Average number of target answer entities in the selected QA population.',
'Mean paraphrases':'Mean number of available question wordings per selected QA record.',
'RW-Ans@p-actions':'Probability that a uniform random walk over labeled graph actions reaches any target answer at exactly the selected step horizon.',
'RW-Ans@p-neighbors':'Probability that a uniform random walk over unique next-node neighbors reaches any target answer at exactly the selected step horizon.',
'RW-RefPath@p-actions':'Probability of executing a released reference path, including final self-loop padding when enabled and needed.',
'Reachable N count':'Number of distinct entities with nonzero probability after exactly N random-walk steps.',
'Reference paths':'Number of evaluated QA instances with at least one released or reconstructed reference path.',
'Mean reference paths':'Average number of released or reconstructed reference paths per evaluated instance or family.',
'Answer-Entity':'The released valid-answer entity set used as the primary target.',
'Graph-Answer-Entity':'Graph-consistent endpoints derived from released relation chains in the active graph. Not independently verified gold answers.',
'RW-Ans@p-actions::Answer-Entity':'Probability of reaching a released valid answer using uniformly sampled labeled actions at a fixed step horizon.',
'RW-Ans@p-neighbors::Answer-Entity':'Probability of reaching a released valid answer using uniformly sampled unique next-node neighbors.',
'RW-Ans@p-actions::Graph-Answer-Entity':'Probability of reaching a graph-consistent answer using uniformly sampled labeled actions.',
'RW-Ans@p-neighbors::Graph-Answer-Entity':'Probability of reaching a graph-consistent answer using uniformly sampled unique next-node neighbors.',
'Reachable-N-Count':'Number of distinct destinations reachable after exactly N random-walk actions.',
'Path-Count':'Number of distinct released annotated reference paths.',
'Graph-Path-Count':'Number of graph-relative paths consistent with the released relation chains.',
'Graph-Answer-Expansion':'Additional graph-consistent answer entities compared with released answer entities.',
'Graph-Path-Expansion':'Additional graph-relative paths compared with released annotated paths.',
'Clustering coefficient':'Fraction of possible neighbor-neighbor links that exist, averaged across entities in an undirected graph.',
'Eigenvector centrality':'Connectivity score that also gives higher weight to connections with other well-connected entities.',
'Out-degree':'Number of outgoing directed triples from an entity.',
'P95':'95th percentile: 95% of observations are at or below this value.',
'PED':'Path edit distance, measuring differences between predicted and reference navigation trajectories.',
'RED':'Relation edit distance, measuring differences between ordered relation sequences.',
'SELF_LOOP':'An optional action that leaves the walker at its current entity while consuming one step.'
};
function explanationFor(label){
const t=String(label??'').trim();
if(GLOSSARY[t])return GLOSSARY[t];
if(t.startsWith('RW-Ans@p-actions::'))return GLOSSARY['RW-Ans@p-actions'];
if(t.startsWith('RW-Ans@p-neighbors::'))return GLOSSARY['RW-Ans@p-neighbors'];
if(t==='family-macro'||t==='instance-micro')return GLOSSARY[t==='family-macro'?'Family-macro':'Instance-micro'];
return '';
}
function termHTML(label){const info=explanationFor(label);return info?`${escaped(label)}`:escaped(label);}
const metric=(label,value,help='')=>`${termHTML(label)}
${escaped(value)}
${escaped(help)}
`;
function table(rows,cols){
return `${cols.map(c=>`| ${termHTML(c.label)} | `).join('')}
${rows.map(row=>''+cols.map(c=>`| ${termHTML(c.format?c.format(row[c.key]):row[c.key])} | `).join('')+'
').join('')}
`;
}
function pairs(rows){return table(rows.map(([label,value])=>({label,value})),[{label:'Measure',key:'label'},{label:'Value',key:'value',right:true}]);}
function section(title,intro,content){return `${termHTML(title)}
${intro?`${intro}
`:''}${content}`;}
function chart(title,items,key='count',nameKey='name'){
if(!items.length)return 'No data available.
';
const max=Math.max(1,...items.map(x=>x[key]||0));
return `${escaped(title)}
${items.map(x=>{
const val=Number(x[key])||0, label=String(x[nameKey]); const width=Math.max(0,val/max*100);
return `
${escaped(label)}${number(val)} `;
}).join('')}
`;
}
function hintPanel(title,text){return `${escaped(title)}: ${escaped(text)}
`;}
function aggregateMetric(metricName, aggregation){
const table=(latest?.joint?.metric_tables||[]).find(x=>x.metric===metricName);
return table?.rows?.find(r=>r.subset==='All'&&r.aggregation===aggregation)||null;
}
function updateDashboardControls(){
const q=latest?.qa, j=latest?.joint;
const hasFamily=Boolean(q?.dashboard?.has_family_ids) && (
!j || !j.summary.random_walk_enabled || Boolean(aggregateMetric('RW-Ans@p-actions::Answer-Entity','family-macro')));
const hasGraphAnswer=Boolean(q?.summary?.rows_with_graph_answers>0) && (
!j || !j.summary.random_walk_enabled || Boolean(aggregateMetric('RW-Ans@p-actions::Graph-Answer-Entity','instance-micro')));
if(!hasFamily)dashboardAggregation='instance-micro';
if(!hasGraphAnswer)dashboardTarget='Answer-Entity';
$('dashboard-modes').classList.toggle('hidden',!hasFamily&&!hasGraphAnswer);
$('aggregation-mode-wrap').classList.toggle('hidden',!hasFamily);
$('answer-mode-wrap').classList.toggle('hidden',!hasGraphAnswer);
$('dashboard-aggregation').value=dashboardAggregation;
$('dashboard-answer').value=dashboardTarget;
$('dashboard-mode-caption').textContent=hasFamily||hasGraphAnswer?'Top summary cards only. Detailed report tables and exported statistics retain both views.':'';
}
function renderTopMetrics(){
if(!latest)return;
const cards=[];
if(latest.kg){const s=latest.kg.summary;
cards.push(metric('Unique triples',number(s.unique_triples),`${number(s.duplicate_triplet_rows)} duplicates found`),
metric('Entities',number(s.entities),'Active graph'),metric('Relations',number(s.relations),'Unique relation types'),
metric('Largest WCC',pct(s.largest_wcc_fraction),`${number(s.largest_wcc_nodes)} entities`));}
if(latest.qa){
const s=latest.qa.summary, m=latest.qa.dashboard?.family_macro||{}, macro=dashboardAggregation==='family-macro';
const targetGraph=dashboardTarget==='Graph-Answer-Entity';
const answerMean=targetGraph?(macro?m.graph_answer_count_mean:s.graph_answer_count_mean):(macro?m.answer_count_mean:s.answer_count_mean);
const wordMean=macro?m.paraphrase_count_mean:s.paraphrase_count_mean;
cards.push(metric('QA rows',number(s.qa_rows),'Selected benchmark rows'),
metric('Question families',number(s.families),'Structural family identifiers'),
metric('Mean answer count',answerMean===null||answerMean===undefined?'—':fixed(answerMean,3),
`${targetGraph?'Graph-consistent':'Released'} answers · ${dashboardAggregation}`),
metric('Mean paraphrases',wordMean===null||wordMean===undefined?'—':fixed(wordMean,3),dashboardAggregation));
}
if(latest.joint){
const s=latest.joint.summary,target=dashboardTarget, aggregation=dashboardAggregation;
const a=aggregateMetric(`RW-Ans@p-actions::${target}`,aggregation),
n=aggregateMetric(`RW-Ans@p-neighbors::${target}`,aggregation),
reach=aggregateMetric('Reachable-N-Count',aggregation),
ref=aggregateMetric('Reference-Path-Count',aggregation);
const targetLabel=target==='Answer-Entity'?'Released':'Graph-consistent';
if(s.random_walk_enabled){
cards.push(metric('RW-Ans@p-actions',a?sci(a.mean):'—',`${targetLabel} · ${aggregation} · n=${number(a?.n)}`),
metric('RW-Ans@p-neighbors',n?sci(n.mean):'—',`${targetLabel} · ${aggregation} · n=${number(n?.n)}`),
metric('Reachable N count',reach?number(reach.mean):'—',`Mean endpoints · ${aggregation}`));
}
cards.push(metric('Mean reference paths',ref?fixed(ref.mean,3):'—',`${aggregation} · n=${number(ref?.n)}`));
}
$('metric-cards').innerHTML=cards.join('');
}
function render(){
if(!latest)return;
const parts=[];if(latest.kg)parts.push(latest.kg.source.name);if(latest.qa)parts.push(latest.qa.source.name);
$('report-title').textContent=parts.length?parts.join(' + '):'Statistics summary';
const subtitles=[];
const sourceCaption=(src,kind)=>src.mode==='archive'?`${src.name} · GCS JSON API (ZIP)`:
src.mode==='url'?`${src.repo} · Hugging Face`:`Local ${kind} · ${src.filename||''}`;
if(latest.kg)subtitles.push(sourceCaption(latest.kg.source,'graph'));
if(latest.qa)subtitles.push(sourceCaption(latest.qa.source,'QA'));
subtitles.push(`${fixed(latest.runtime_seconds,2)} s`);
$('report-subtitle').textContent=subtitles.join(' · ');
updateDashboardControls();renderTopMetrics();
renderOverview();renderNavigation();renderQA();renderJoint();renderDistributions();renderRelations();
decorateStaticDefinitions();
}
function decorateStaticDefinitions(){
document.querySelectorAll('.check-row strong, .mode-label, .tab, .plot-panel h3').forEach(el=>{
const info=explanationFor(el.textContent.trim());
if(info){
el.removeAttribute('title');
el.dataset.definition=info;
el.classList.add('defined-term');
if(!el.matches('button, a, input, select'))el.tabIndex=0;
}
});
const aggregationLabel=document.querySelector('label[for="dashboard-aggregation"]');
const answerLabel=document.querySelector('label[for="dashboard-answer"]');
const explanations=[
[aggregationLabel,GLOSSARY['Instance-micro']+' '+GLOSSARY['Family-macro']],
[answerLabel,GLOSSARY['Answer (released)']+' '+GLOSSARY['Graph-Answer (graph-consistent)']]
];
for(const [label,info] of explanations){
if(label){label.removeAttribute('title');label.dataset.definition=info;label.classList.add('defined-term');label.tabIndex=0;}
}
$('dashboard-aggregation').removeAttribute('title');
$('dashboard-answer').removeAttribute('title');
}
function renderOverview(){
if(!latest.kg){$('tab-overview').innerHTML=hintPanel('KG overview unavailable','No graph source was selected for this run.'); return;}
const r=latest.kg,s=r.summary;
const metadataNote=r.source.entity_filename ?
`Entity metadata: ${escaped(r.source.entity_filename)} is ${r.source.include_metadata_only_entities?'included as an explicit graph entity list. Metadata-only IDs count as isolated nodes.':'used for display labels only. Graph entities are defined by the triplets.'}
` : '';
const issues=[]; if(r.parse.ignored_malformed_rows) issues.push(`${number(r.parse.ignored_malformed_rows)} malformed input rows were ignored.`); if(r.parse.bad_rows) issues.push(`${number(r.parse.bad_rows)} empty-field rows were ignored.`);
const warnings=Object.entries(r.optional).filter(([k,v])=>typeof v==='string').map(([k,v])=>`${escaped(k.replaceAll('_',' '))}: ${escaped(v)}
`).join('');
const optional=[]; if(s.clustering_coefficient!==undefined) optional.push(['Average clustering coefficient',sci(s.clustering_coefficient)]); if(s.legacy_max_non_cyclic_path!==undefined) optional.push(['Legacy max non-cyclic path',number(s.legacy_max_non_cyclic_path)]); if(s.legacy_krackhardt_mean!==undefined) optional.push(['Legacy Krackhardt mean',fixed(s.legacy_krackhardt_mean,5)]); if(r.optional.eigenvector_top) optional.push(['Eigenvector centrality','Computed. See Relations & entities.']);
$('tab-overview').innerHTML=metadataNote+`Navigation metrics use distinct (head, relation, tail) triples. Density and components collapse relation labels and edge directions.
`+
(issues.length?`${issues.map(escaped).join(' ')}
`:'')+warnings+
section('Graph overview','Counts and connectivity for the active graph.',pairs([
['Triplet rows',number(s.triplet_rows)],['Unique directed triples',number(s.unique_triples)],['Duplicate rows',number(s.duplicate_triplet_rows)],['Unique self-loop triples',number(s.self_loop_triples)],['Entities',number(s.entities)],['Relation types',number(s.relations)],['Isolated nodes',number(s.isolated_nodes)],['Zero-out-degree nodes',number(s.zero_out_degree_nodes)],['Weakly connected components',number(s.wcc_count)],['Largest WCC (nodes)',number(s.largest_wcc_nodes)],['Largest WCC fraction',pct(s.largest_wcc_fraction)],['Median WCC size',number(s.median_wcc_nodes)],['Singleton components',number(s.singleton_wcc_count)],['Graph density (undirected)',sci(s.graph_density)],['Legacy raw out-degree mean',fixed(s.out_degree_mean_legacy,4)],['Legacy raw in-degree mean',fixed(s.in_degree_mean_legacy,4)]
]))+
(optional.length?section('Optional metrics','Requested calculations that completed within the browser safeguards.',pairs(optional)):'')+
section('Largest components','Number of entities in each weakly connected component (top 12).',chart('Component size',r.components.map((count,i)=>({name:`WCC ${i+1}`,count})).slice(0,12)));
}
function renderNavigation(){
if(!latest.kg){$('tab-navigation').innerHTML=hintPanel('KG navigation unavailable','No graph source was selected for this run.'); return;}
const s=latest.kg.summary;
const groups=[{name:'Out-degree (unique triples)',prefix:'out_degree'},{name:'In-degree (unique triples)',prefix:'in_degree'},{name:'Unique outgoing neighbors',prefix:'unique_out_neighbors'},{name:'Distinct outgoing relations',prefix:'distinct_out_relations'}];
const rows=groups.map(g=>({measure:g.name,mean:fixed(s[g.prefix+'_mean'],4),median:number(s[g.prefix+'_median']),p90:number(s[g.prefix+'_p90']),p95:number(s[g.prefix+'_p95']),p99:number(s[g.prefix+'_p99']),maximum:number(s[g.prefix+'_max'])}));
$('tab-navigation').innerHTML=section('Navigation-oriented structural calibration','All percentiles include tail-only nodes and explicitly listed isolated nodes, whose outgoing values are zero.',
table(rows,[{label:'Metric',key:'measure'},{label:'Mean',key:'mean',right:true},{label:'Median',key:'median',right:true},{label:'P90',key:'p90',right:true},{label:'P95',key:'p95',right:true},{label:'P99',key:'p99',right:true},{label:'Max',key:'maximum',right:true}]))+
section('Branching thresholds','Fraction of all entities with unique-triple out-degree exceeding each threshold.',pairs([
['Out-degree > 10',pct(s.nodes_out_degree_gt_10_fraction)],['Out-degree > 50',pct(s.nodes_out_degree_gt_50_fraction)],['Out-degree > 100',pct(s.nodes_out_degree_gt_100_fraction)],['Zero out-degree entities',`${number(s.zero_out_degree_nodes)} / ${number(s.entities)}`]
]))+
`Out-degree counts distinct directed triples. If a single head has several relations pointing to the same tail, its out-degree can be larger than its unique outgoing-neighbor count.
`;
}
function renderQA(){
if(!latest.qa){$('tab-qa').innerHTML=hintPanel('QA analysis unavailable','No QA source was selected for this run.'); return;}
const q=latest.qa;
const summary=q.summary;
const hopItems=Object.entries(q.hops||{}).sort((a,b)=>Number(a[0])-Number(b[0])).map(([name,count])=>({name:`${name} hop`,count}));
const splitItems=Object.entries(q.splits||{}).map(([name,count])=>({name,count}));
const wordingItems=Object.entries(q.wording_modes||{}).map(([name,count])=>({name,count}));
const notes=[]; if(q.parse?.ignored_malformed_rows) notes.push(`${number(q.parse.ignored_malformed_rows)} malformed CSV rows were ignored.`);
$('tab-qa').innerHTML=(notes.length?`${notes.map(escaped).join(' ')}
`:'')+
section('QA summary','Row-level statistics for the selected QA collection after applying the split filter. PQ/PQL released and graph-consistent annotations are reported separately.',pairs([
['QA rows',number(summary.qa_rows)],['Question families',number(summary.families)],['Available splits',number(Object.keys(q.splits||{}).length)],['Maximum hop count',number(summary.max_hops)],['Mean answer count',fixed(summary.answer_count_mean,3)],['Median answer count',fixed(summary.answer_count_median,3)],['Mean paraphrase count',fixed(summary.paraphrase_count_mean,3)],['Rows with explicit path materialization',number(summary.rows_with_materialized_paths)],['Rows with relation-chain keys',number(summary.rows_with_path_key)],['Rows with split labels',number(summary.rows_with_split_label)]
]))+
section('Answer and wording composition','How many answers and paraphrases each QA row contains.',table([
{measure:'Answer count',mean:fixed(summary.answer_count_mean,3),median:fixed(summary.answer_count_median,3),p95:fixed(summary.answer_count_p95,3),max:number(summary.answer_count_max)},
{measure:'Paraphrase count',mean:fixed(summary.paraphrase_count_mean,3),median:fixed(summary.paraphrase_count_median,3),p95:fixed(summary.paraphrase_count_p95,3),max:number(summary.paraphrase_count_max)},
{measure:'Released reference path count',mean:fixed(summary.reference_path_count_mean,3),median:fixed(summary.reference_path_count_median,3),p95:fixed(summary.reference_path_count_p95,3),max:number(summary.reference_path_count_max)},
...(summary.graph_answer_count_mean!==null?[{measure:'Graph-consistent answer count',mean:fixed(summary.graph_answer_count_mean,3),median:fixed(summary.graph_answer_count_median,3),p95:fixed(summary.graph_answer_count_p95,3),max:number(summary.graph_answer_count_max)}]:[]),
...(summary.graph_path_count_mean!==null?[{measure:'Graph-relative path count',mean:fixed(summary.graph_path_count_mean,3),median:fixed(summary.graph_path_count_median,3),p95:fixed(summary.graph_path_count_p95,3),max:number(summary.graph_path_count_max)}]:[])
],[{label:'Measure',key:'measure'},{label:'Mean',key:'mean',right:true},{label:'Median',key:'median',right:true},{label:'P95',key:'p95',right:true},{label:'Max',key:'max',right:true}]))+
''+chart('Hop distribution',hopItems)+chart('Benchmark split distribution',splitItems)+'
'+
''+chart('Question wording availability',wordingItems)+chart('Answer multiplicity (most common)',(q.answer_distribution||[]).slice(0,12).map(x=>({name:String(x.value),count:x.count})))+'
'+
section('Released and graph-relative annotations','Released reference paths are obtained from Multi-Paths or Paths. Graph-Multi-Paths and Graph-Answer-Entity come from the uploaded QA CSV and are not recomputed for the selected KG. They do not redefine released gold answers.',pairs([
['Rows with released relation-chain keys',number(summary.rows_with_path_key)],
['Mean relation chains per row',fixed(summary.relation_chain_count_mean,3)],
['Maximum relation chains per row',number(summary.relation_chain_count_max)],
['Rows with released materialized paths',number(summary.rows_with_materialized_paths)],
['Rows with released Path-Count',number(summary.rows_with_path_count_field)],
['Rows whose topic is a released answer',number(summary.topic_is_answer_rows)],
['Rows with graph-consistent answers',number(summary.rows_with_graph_answers)],
['Rows whose topic is a graph-consistent answer',number(summary.topic_is_graph_answer_rows)],
['Rows with Graph-Multi-Paths',number(summary.rows_with_graph_paths)],
['Rows with Graph-Path-Count',number(summary.rows_with_graph_path_count_field)],
['Rows with cycles in released materialized paths',number(summary.rows_with_cyclic_materialized_paths)],
['Rows with immediate backtracking in released materialized paths',number(summary.rows_with_backtrack_materialized_paths)]
]));
}
function metricTableRows(metricTables, metricName){
const found=(metricTables||[]).find(x=>x.metric===metricName);
return found?.rows||[];
}
function renderJoint(){
if(!latest.joint){$('tab-joint').innerHTML=hintPanel('Joint KGQA analysis unavailable','Joint analysis requires both a graph and a QA collection.');return;}
const j=latest.joint,s=j.summary,withRW=s.random_walk_enabled!==false;
const entries=[
['Evaluated QA instances',number(s.qa_rows_evaluated)],['Released QA rows',number(s.canonical_qa_rows)],
['Paraphrase expansion',s.paraphrases_expanded?'Enabled':'Disabled'],
['Evaluated families',number(s.families_evaluated)],['Skipped rows (missing source entity)',number(s.skipped_missing_source)],
['Missing released/reconstructed reference paths',number(s.missing_reference_paths)],
['Rows with released/reconstructed reference paths',number(s.reference_paths_rows)],
['Random-walk probabilities',withRW?'Calculated':'Skipped (disabled)']
];
if(withRW)entries.push(
['Action space',s.action_space==='undirected'?'Undirected (reverse actions)':'Directed (outgoing only)'],
['Self-loop actions enabled',s.use_self_loops?'Yes':'No'],
['Random-walk horizon',number(s.num_rollout_steps)],
['Mean reachable endpoints after N steps',fixed(s.reachable_n_mean,3)],
['Mean RW-Ans@p-actions',sci(s.rw_actions_all_mean)],
['Mean RW-Ans@p-neighbors',sci(s.rw_neighbors_all_mean)]
);
const metricRows=name=>(j.metric_tables||[]).find(t=>t.metric===name)?.rows||[];
const summaryRows=(names,formatter=sci)=>names.flatMap(name=>metricRows(name)).map(r=>({metric:r.metric||'',subset:r.subset,aggregation:r.aggregation,n:r.n,mean:formatter(r.mean),median:formatter(r.median),std:formatter(r.std)}));
const summaryCols=[{label:'Metric',key:'metric'},{label:'Subset',key:'subset'},{label:'Aggregation',key:'aggregation'},{label:'N',key:'n',right:true},{label:'Mean',key:'mean',right:true},{label:'Median',key:'median',right:true},{label:'Std',key:'std',right:true}];
let out=section('Joint KGQA summary',withRW?'The active QA rows include exact fixed-horizon random-walk probabilities and reference structure.':'Random-walk probabilities were disabled. Reference-path and answer statistics are still calculated.',pairs(entries));
if(withRW){
out+=section('Random-walk answer calibration','Instance-micro and family-macro summaries follow the selected QA rows and their hop subsets.',
table(summaryRows(['RW-Ans@p-actions::Answer-Entity','RW-Ans@p-neighbors::Answer-Entity']),summaryCols));
const graphRows=summaryRows(['RW-Ans@p-actions::Graph-Answer-Entity','RW-Ans@p-neighbors::Graph-Answer-Entity']);
if(graphRows.length)out+=section('Secondary graph-consistent answer calibration','These probabilities target Graph-Answer-Entity. The QA CSV supplies the targets.',table(graphRows,summaryCols));
}
const expansion=summaryRows(['Graph-Answer-Expansion','Graph-Path-Expansion'],x=>fixed(x,4));
if(expansion.length)out+=section('Graph-relative expansion','Compares graph-answer and graph-path counts with released counts.',table(expansion,summaryCols));
const structural=summaryRows(['Answer-Count::Answer-Entity','Answer-Count::Graph-Answer-Entity','Released-Answer-Count','Graph-Answer-Count','Topic-Is-Released-Answer','Topic-Is-Graph-Answer','Path-Count','Graph-Path-Count','Reference-Path-Count','Released-Reference-Has-Cycle','Released-Reference-Has-Immediate-Backtrack'],x=>fixed(x,4));
if(!withRW&&structural.length)out+=section('Reference and answer structure','These metrics do not require a random-walk simulation.',table(structural,summaryCols));
if(withRW){
const reach=metricRows('Reachable-N-Count');
out+=section('Reachability and reference structure','Reachability depends on the source entity and fixed horizon. Reference metrics use released or reconstructable paths.',
table(reach.map(r=>({subset:r.subset,aggregation:r.aggregation,n:r.n,mean:fixed(r.mean,3),median:fixed(r.median,3),std:fixed(r.std,3)})),
[{label:'Subset',key:'subset'},{label:'Aggregation',key:'aggregation'},{label:'N',key:'n',right:true},{label:'Mean reachable endpoints',key:'mean',right:true},{label:'Median',key:'median',right:true},{label:'Std',key:'std',right:true}]));
const refProb=metricRows('RW-RefPath@p-actions');
if(refProb.length)out+=section('Released reference-path probability','Probability of executing an annotated path and padding to the fixed horizon with SELF_LOOP actions.',
table(refProb.map(r=>({subset:r.subset,aggregation:r.aggregation,n:r.n,mean:sci(r.mean),median:sci(r.median),std:sci(r.std)})),
[{label:'Subset',key:'subset'},{label:'Aggregation',key:'aggregation'},{label:'N',key:'n',right:true},{label:'Mean',key:'mean',right:true},{label:'Median',key:'median',right:true},{label:'Std',key:'std',right:true}]));
}
if(j.reference_state_actions){const ref=j.reference_state_actions;
out+=section('Reference-state action space','Distribution of actions at unique (hop, entity) states along reference paths.',pairs([
['States counted',number(ref.n)],['Mean actions',fixed(ref.mean,3)],['Median actions',fixed(ref.median,3)],
['P95 actions',fixed(ref.p95,3)],['Max actions',number(ref.max)],
['Fraction with > 10 actions',pct(ref.fraction_gt10)],['Fraction with > 50 actions',pct(ref.fraction_gt50)],['Fraction with > 100 actions',pct(ref.fraction_gt100)]
]));
}
out+=withRW?'RW-Ans@p-actions samples labeled actions. RW-Ans@p-neighbors samples unique neighbors. Dead ends remain absorbing.
':'Random-walk evaluation was skipped as requested. Enable the option and rerun to calculate RW-Ans, RW-RefPath, and fixed-horizon reachability.
';
$('tab-joint').innerHTML=out;
}
function renderDistributions(){
if(!latest.kg){$('tab-distribution').innerHTML=hintPanel('Distribution view unavailable','No graph source was selected for this run.'); return;}
const d=latest.kg.distributions, names={out_degree:'Out-degree',in_degree:'In-degree',unique_out_neighbors:'Unique outgoing neighbors',distinct_out_relations:'Distinct outgoing relations'};
$('tab-distribution').innerHTML='Entity-frequency distributions for navigation metrics, including zero-valued nodes. The graphs display the 18 most common values.
'+
Object.entries(names).map(([key,name])=>{
const dist=[...(d[key]||[])].sort((a,b)=>b.count-a.count||a.degree-b.degree).slice(0,18).map(x=>({name:String(x.degree),count:x.count}));
return chart(name,dist);
}).join('')+'
';
}
function renderRelations(){
if(!latest.kg){$('tab-relations').innerHTML=hintPanel('Relation and entity view unavailable','No graph source was selected for this run.'); return;}
const r=latest.kg, ren=x=>labels.get(x)||x, en=x=>entityLabels.has(x)?`${entityLabels.get(x)} (${x})`:x;
const top=(r.relations||[]).slice(0,20).map(x=>({...x,pretty:ren(x.name)}));
const relTable=table(top.map(x=>({relation:x.pretty+(x.pretty!==x.name?` (${x.name})`:''),rows:x.count,unique:x.unique_triples})),[{label:'Relation',key:'relation'},{label:'Rows',key:'rows',right:true},{label:'Unique triples',key:'unique',right:true}]);
let extra=''; if(r.optional.eigenvector_top) extra=section('Top eigenvector centrality','Undirected projection, optional iterative computation.',table(r.optional.eigenvector_top.map(v=>({...v,name:en(v.name),score:fixed(v.value,6)})),[{label:'Entity',key:'name'},{label:'Score',key:'score',right:true}]));
if(r.optional.legacy_path_per_relation) extra+=section('Legacy path and hierarchy by relation','Per-relation maximum cycle-pruned path length and Krackhardt reachability score.',table([...r.optional.legacy_path_per_relation].sort((a,b)=>b.maximum-a.maximum).slice(0,30).map(v=>({name:ren(v.relation),maximum:v.maximum,hierarchy:v.krackhardt===null?'Skipped':fixed(v.krackhardt,4)})),[{label:'Relation',key:'name'},{label:'Max path',key:'maximum',right:true},{label:'Hierarchy',key:'hierarchy',right:true}]));
$('tab-relations').innerHTML=section('Most frequent relations','Raw input-row frequency and unique-triple frequency.',chart('Top relation frequencies',top.slice(0,12).map(x=>({name:x.pretty,count:x.count})))+relTable)+
''+chart('Most frequent entities',(r.topNodes||[]).slice(0,12).map(x=>({name:en(x.name),count:x.total_degree_rows})))+chart('Highest incident relation diversity',(r.diverseNodes||[]).slice(0,12).map(x=>({name:en(x.name),count:x.distinct_incident_relations})))+'
'+
section('Entities by frequency','Number of input triple rows involving each entity as head or tail.',table((r.topNodes||[]).slice(0,20).map(v=>({name:en(v.name),head:v.out_degree_rows,tail:v.in_degree_rows,total:v.total_degree_rows})),[{label:'Entity',key:'name'},{label:'Head',key:'head',right:true},{label:'Tail',key:'tail',right:true},{label:'Total',key:'total',right:true}]))+extra;
}
function switchTab(target){document.querySelectorAll('[role=tab]').forEach(el=>{const on=el.dataset.tab===target;el.classList.toggle('active',on);el.setAttribute('aria-selected',String(on));}); document.querySelectorAll('.tab-pane').forEach(el=>el.classList.toggle('hidden',el.id!==`tab-${target}`));}
function saveBlob(data,name,type){const blob=new Blob([data],{type}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),2500);}
function basename(){return 'kgqa_statistics';}
function exportJSON(){if(!latest)return; const data={...latest,metadata:{tool:'THESEUS KGQA Statistics Lab',created_utc:new Date().toISOString()}}; saveBlob(JSON.stringify(data,null,2),`${basename()}_report.json`,'application/json');}
function csvCell(x){const s=String(x??''); return /[,\n"\r]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s;}
function exportCSV(){if(!latest?.kg){reportError('Node CSV export is only available when a graph source is selected.'); return;} const fields=['name','out_degree','in_degree','out_degree_rows','in_degree_rows','unique_out_neighbors','distinct_out_relations','distinct_incident_relations','total_degree_rows']; const lines=[['name','title',...fields.slice(1)].join(','),...(latest.kg.nodes||[]).map(n=>[csvCell(n.name),csvCell(entityLabels.get(n.name)||''),...fields.slice(1).map(f=>csvCell(n[f]))].join(','))]; saveBlob(lines.join('\n'),`${basename()}_node_statistics.csv`,'text/csv;charset=utf-8');}
document.querySelectorAll('input[name=graph-source], input[name=qa-source]').forEach(el=>el.addEventListener('change',updateSourceHints));
$('dataset-provider').addEventListener('change',updateSourceHints);
$('relations-file').addEventListener('change',()=>onColumnFileChange('relation','relations-file'));
$('entities-file').addEventListener('change',()=>onEntityFileChange());
for(const id of ['relation-id-column','relation-label-column','node-id-column','node-label-column'])
$(id).addEventListener('change',()=>{
if(latest&&!running){
loadLabels().catch(()=>{});
if(id==='node-id-column' && $('opt-include-metadata-entities').checked)status('Entity ID column changed. Rerun the analyses to update explicitly included metadata-only nodes.');
}
});
$('analyze').addEventListener('click',run); $('cancel').addEventListener('click',cancel);
$('clear-download-cache').addEventListener('click',clearDatasetCache);
$('opt-run-random-walk').addEventListener('change',updateRandomWalkControls); $('json-export').addEventListener('click',exportJSON); $('csv-export').addEventListener('click',exportCSV); document.querySelectorAll('[role=tab]').forEach(el=>el.addEventListener('click',()=>switchTab(el.dataset.tab)));
$('dashboard-aggregation').addEventListener('change',event=>{dashboardAggregation=event.target.value;renderTopMetrics();});
$('dashboard-answer').addEventListener('change',event=>{dashboardTarget=event.target.value;renderTopMetrics();});
updateSourceHints();
updateDatasetCacheStatus();
updateRandomWalkControls();