} 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'); }