}
function buildFullStudentDocDefinition(row){
const score=Number.isFinite(row.score)?row.score:'—';
const gops=sortedGopsForReport(row);
const counts=studentReportCounts(gops);
const content=[];
content.push({text:'Индивидуальный грант-отчёт Newton',style:'title'});
content.push({canvas:[{type:'line',x1:0,y1:0,x2:515,y2:0,lineWidth:1.5,lineColor:'#185CFF'}],margin:[0,0,0,8]});
content.push(pdfInfoRow([{label:'Ученик',value:row.name||'—',w:'*'},{label:'Балл ЕНТ',value:score,w:70,big:true},{label:'Проф.предметы',value:row.subject||'—',w:'*'}]));
content.push(pdfStatsRow([{label:'общее ГОП',value:counts.total},{label:'высокий',value:counts.high},{label:'средний',value:counts.mid},{label:'не поступит',value:counts.risk}]));
content.push({text:'1. Калькулятор гранта: ГОПы',style:'heading'});
if(gops.length) content.push(...pdfGrantTablesForReport(gops));
else content.push({text:'По выбранным профильным предметам ГОП не найдены.',style:'muted'});
const gopsWithUniversities=gops.filter(g=>getUniversityOptionsForGop(g,row.score).length>0);
content.push({text:'2. ВУЗы по баллу',style:'heading',pageBreak:gops.length?'before':undefined});
if(!gopsWithUniversities.length){
content.push({text:'По выбранным ГОП ВУЗы в базе не найдены.',style:'muted'});
}else{
gopsWithUniversities.forEach((g,i)=>content.push(...pdfGopUniversityBlock(g,row.score,i)));
}
content.push(pdfQuoteBlock());
return {
pageSize:'A4', pageMargins:[38,34,38,34],
content,
defaultStyle:{font:'Roboto',fontSize:9,lineHeight:1.12,color:'#101828'},
styles:{
title:{fontSize:19,bold:true,margin:[0,0,0,7],color:'#101828'},
heading:{fontSize:13,bold:true,margin:[0,8,0,5],color:'#101828'},
metaLabel:{fontSize:6.8,bold:true,color:'#667085',characterSpacing:.2},
metaValue:{fontSize:10,bold:true,color:'#101828'},
metaValueBig:{fontSize:14,bold:true,color:'#101828'},
th:{fontSize:7,bold:true,color:'#667085'},
td:{fontSize:8.2,color:'#101828'},
tdNoData:{fontSize:7.2,color:'#98A2B3'},
tdBold:{fontSize:8.2,bold:true},
codeBlue:{fontSize:9,bold:true,color:'#185CFF'},
smallMuted:{fontSize:7.5,color:'#667085'},
muted:{fontSize:9,color:'#667085'},
gopHeader:{fontSize:10.5,bold:true,color:'#185CFF'},
gopMeta:{fontSize:7.7,color:'#667085'},
subheadingGreen:{fontSize:9.5,bold:true,color:'#067647'},
subheadingRed:{fontSize:9.5,bold:true,color:'#B42318'},
quote:{fontSize:11,bold:true,color:'#FFFFFF',fillColor:'#101828',margin:[0,10,0,0]}
}
};
}
function buildBulkGrantDocDefinition(){
const scores=bulkRows.map(r=>r.score).filter(Number.isFinite);
const avg=scores.length?Math.round((scores.reduce((a,b)=>a+b,0)/scores.length)*10)/10:'—';
const high=bulkRows.filter(r=>bestGop(r)?.chance?.level==='high').length;
const mid=bulkRows.filter(r=>bestGop(r)?.chance?.level==='mid').length;
const risk=bulkRows.filter(r=>bestGop(r)?.chance?.level==='risk').length;
const summaryRows=buildSummaryRows().map(r=>[
{text:pdfText(r.subject),style:'td'},
{text:String(r.students),style:'td'},
{text:pdfText(r.scoreCount?Math.round((r.scoreSum/r.scoreCount)*10)/10:'—'),style:'td'},
{text:String(r.high),style:'td'},
{text:String(r.mid),style:'td'},
{text:String(r.risk),style:'td'}
]);
const studentRows=bulkRows.map((row,i)=>{
const g=bestGop(row);
return [
{text:String(i+1),style:'td'},
{text:pdfText(row.name),style:'td'},
{text:pdfText(row.subject),style:'td'},
{text:pdfText(Number.isFinite(row.score)?row.score:'—'),style:'td'},
{text:g?`${g.code} — ${g.name}`:'—',style:'td'},
{text:g?g.chance.label:'—',style:'tdBold',color:g?pdfStatusColor(g.chance.level):'#667085'}
];
});
const base=buildFullStudentDocDefinition({name:'Группа',score:avg,subject:'Excel-группа',gops:[]});
base.content=[
{text:'Групповой расчёт гранта Newton',style:'title'},
{canvas:[{type:'line',x1:0,y1:0,x2:515,y2:0,lineWidth:1.5,lineColor:'#185CFF'}],margin:[0,0,0,8]},
pdfStatsRow([{label:'учеников',value:bulkRows.length},{label:'средний балл',value:avg},{label:'высокий',value:high},{label:'средний',value:mid},{label:'не поступит',value:risk}]),
{text:'Сводная таблица по группе',style:'heading'},
pdfTable(['Проф.предметы','Учеников','Средний','Высокий','Средний','Не поступит'], summaryRows, ['*',46,52,50,50,58]),
{text:'Ученики',style:'heading'},
pdfTable(['#','ФИО','Проф.предметы','Балл','Лучший ГОП','Статус'], studentRows, [22,'*','*',38,'*',62]),
pdfQuoteBlock()
];
return base;
}
function downloadBulkPDF(){
if(!bulkRows.length)return;
downloadDocDefinitionAsPdf(buildBulkGrantDocDefinition(),'Newton_grant_group.pdf');
}
function buildUniversityOnlyDocDefinition(student, rows){
const content=[];
const pass=rows.filter(r=>r.status.level==='pass').length;
const fail=rows.filter(r=>r.status.level==='fail').length;
content.push({text:'ВУЗы по баллу Newton',style:'title'});
content.push({canvas:[{type:'line',x1:0,y1:0,x2:515,y2:0,lineWidth:1.5,lineColor:'#185CFF'}],margin:[0,0,0,8]});
content.push(pdfInfoRow([{label:'Ученик',value:student.name||'—',w:'*'},{label:'Балл ЕНТ',value:Number.isFinite(student.score)?student.score:'—',w:70,big:true},{label:'Проф.предметы',value:student.subject||'—',w:'*'}]));
const statItems=[{label:'ГОП',value:student.gop||'Все ГОП'}];
if(pass>0) statItems.push({label:'можно подать',value:pass,big:true});
if(fail>0) statItems.push({label:'нельзя подать',value:fail,big:true});
content.push(pdfInfoRow(statItems));
const groups=groupUniByGop(rows).filter(g=>g.rows.length);
groups.forEach((g,idx)=>{
const split=splitUniversityRows(g.rows);
content.push({text:`${g.code} — ${g.name}`,style:'gopHeader',margin:[0,idx===0?8:10,0,1]});
const metaParts=[];
if(split.pass.length) metaParts.push({text:`Можно подать: ${split.pass.length}`,color:'#067647',bold:true});
if(split.fail.length) metaParts.push({text:(metaParts.length?' ':'')+`Нельзя подать: ${split.fail.length}`,color:'#B42318',bold:true});
if(metaParts.length) content.push({text:metaParts,style:'gopMeta',margin:[0,0,0,4]});
const p=pdfUniversityRowsTable('Можно подать',split.pass); if(p) content.push(...p);
const f=pdfUniversityRowsTable('Нельзя подать',split.fail); if(f) content.push(...f);
});
content.push(pdfQuoteBlock());
const doc=buildFullStudentDocDefinition({name:student.name,score:student.score,subject:student.subject,gops:[]});
doc.content=content;
return doc;
}
function buildUniBulkDocDefinition(){
const scores=uniBulkRows.map(r=>r.score).filter(Number.isFinite);
const avg=scores.length?Math.round((scores.reduce((a,b)=>a+b,0)/scores.length)*10)/10:'—';
const studentRows=uniBulkRows.map((row,i)=>{
const c=uniBulkCounts(row);
return [
{text:String(i+1),style:'td'},
{text:pdfText(row.name),style:'td'},
{text:pdfText(row.subject),style:'td'},
{text:pdfText(Number.isFinite(row.score)?row.score:'—'),style:'td'},
{text:pdfText(row.gop||'Все ГОП'),style:'td'},
{text:String(c.pass),style:'td',color:'#067647'},
{text:String(c.fail),style:'td',color:'#B42318'}
];
});
const doc=buildFullStudentDocDefinition({name:'Группа',score:avg,subject:'Excel-группа',gops:[]});
doc.content=[
{text:'Групповой подбор ВУЗов Newton',style:'title'},
{canvas:[{type:'line',x1:0,y1:0,x2:515,y2:0,lineWidth:1.5,lineColor:'#185CFF'}],margin:[0,0,0,8]},
pdfStatsRow([{label:'учеников',value:uniBulkRows.length},{label:'средний балл',value:avg}]),
{text:'Компактная сводка по ученикам',style:'heading'},
pdfTable(['#','ФИО','Проф.предметы','Балл','ГОП','Можно','Нельзя'], studentRows, [22,'*','*',38,58,44,44]),
pdfQuoteBlock()
];
return doc;
}
function downloadUniBulkPDF(){
if(!uniBulkRows.length)return;
downloadDocDefinitionAsPdf(buildUniBulkDocDefinition(),'Newton_universities_group.pdf');
}