Talent Outreach and Acquisition
AC
Abbey Chen
Director
abbey.chen@dsta.gov.sg
Demo — Switch Role
DSTA
Workstream
Customize Dashboard
Active Widgets
Available P0 Widgets

Action records

${rows.map((row,rowIndex)=>`${row.map(value=>rowIndex === 0 ? `` : ``).join('')}`).join('')}
${escapeCell(value)}${escapeCell(value)}
`; filename = `${baseFilename}.xls`; mimeType = 'application/vnd.ms-excel;charset=utf-8'; } else { const escapeCsv = value=>{ const text = String(value); return /[",\r\n]/.test(text) ? `"${text.replaceAll('"','""')}"` : text; }; content = `\uFEFF${rows.map(row=>row.map(escapeCsv).join(',')).join('\r\n')}`; filename = `${baseFilename}.csv`; mimeType = 'text/csv;charset=utf-8'; } const url = URL.createObjectURL(new Blob([content],{type:mimeType})); const link = document.createElement('a'); link.href = url; link.download = filename; link.style.display = 'none'; document.body.appendChild(link); link.click(); link.remove(); setTimeout(()=>URL.revokeObjectURL(url),0); toast(`Chart data exported as ${format === 'excel' ? 'Excel' : 'CSV'}.`); } catch(error) { toast('Chart data export is not available in this browser.'); } closeChartMenus(false); } function updateP0ChartTypeHints(){ const applicationHint = document.getElementById('p0ApplicationTypeHint'); const withdrawalHint = document.getElementById('p0WithdrawalTypeHint'); if(applicationHint) applicationHint.textContent = p0ApplicationChartType === 'line' ? 'Line → Bar' : 'Bar → Line'; if(withdrawalHint) withdrawalHint.textContent = p0WithdrawalChartType === 'bar' ? 'Bar → Donut' : 'Donut → Bar'; } function toggleP0ApplicationChart(){ changeP0ApplicationChart(p0ApplicationChartType === 'line' ? 'bar' : 'line'); closeChartMenus(false); } function changeP0ApplicationChart(type){ if(type !== 'line' && type !== 'bar') return; p0ApplicationChartType = type; renderP0ApplicationChart(); updateP0ChartTypeHints(); } function renderP0ApplicationChart(){ const gridColor = cssVar('--border-border','#e5e7eb'); const labelColor = cssVar('--text-fg-muted','#475569'); const isBar = p0ApplicationChartType === 'bar'; const isDaily = Number.isInteger(p0ApplicationDrillMonthIndex); const monthIndex = isDaily ? p0ApplicationDrillMonthIndex : null; const values = isDaily ? dailyApplicationSeries(monthIndex) : WORKSHOP_DATA.monthlyApplications; const labels = isDaily ? values.map((_,index)=>String(index + 1)) : APPLICATION_MONTH_LABELS; replaceP0Chart('p0Applications',{ type:isBar ? 'bar' : 'line', data:{ labels, datasets:[isBar ? { label:'Applications', data:values, backgroundColor:hexToRgba(P.blue,.78), borderColor:P.blue, borderWidth:1, borderRadius:3, maxBarThickness:32 } : { label:'Applications', data:values, borderColor:P.blue, backgroundColor:hexToRgba(P.blue,.12), fill:true, tension:.32, pointRadius:3, pointHoverRadius:5, pointBackgroundColor:P.blue }] }, options:{ maintainAspectRatio:false, onClick:openP0ApplicationMonth, onHover:(event,elements)=>setP0ChartDrilldownCursor(event,isDaily ? [] : elements), plugins:{ legend:{display:false}, tooltip:{ callbacks:{ title:items=>isDaily ? `${items[0].label} ${APPLICATION_MONTH_LABELS[monthIndex]} ${dashboardScope.year}` : items[0].label, afterLabel:()=>isDaily ? 'Daily submitted applications' : 'Click to view daily trend' } } }, scales:{ x:{ title:isDaily ? {display:true,text:'Submission day',color:labelColor,font:{size:10}} : {display:false}, grid:{color:gridColor}, ticks:{color:labelColor,font:{size:10},maxTicksLimit:isDaily ? 16 : 12} }, y:{beginAtZero:isBar,grid:{color:gridColor},ticks:{color:labelColor,font:{size:10},precision:0}} } } }); updateP0ApplicationDrillHeader(); } function openP0ApplicationMonth(event,elements,chart){ if(!elements?.length) return; if(event?.native?.stopPropagation) event.native.stopPropagation(); const selectedIndex = elements[0].index; if(!Number.isInteger(selectedIndex)) return; if(Number.isInteger(p0ApplicationDrillMonthIndex)) return; p0ApplicationDrillMonthIndex = selectedIndex; renderP0ApplicationChart(); } function resetP0ApplicationDrilldown(){ p0ApplicationDrillMonthIndex = null; renderP0ApplicationChart(); } function updateP0ApplicationDrillHeader(){ const title = document.getElementById('p0ApplicationsTitle'); const subtitle = document.getElementById('p0ApplicationsSubtitle'); const back = document.getElementById('p0ApplicationsDrillBack'); const isDaily = Number.isInteger(p0ApplicationDrillMonthIndex); if(title) title.textContent = isDaily ? `Applications Over Time · ${APPLICATION_MONTH_NAMES[p0ApplicationDrillMonthIndex]} ${dashboardScope.year}` : 'Applications Over Time'; if(subtitle) subtitle.textContent = isDaily ? 'Daily Programme applications; use Back to monthly to return' : 'Monthly Programme applications; select a month to drill down'; if(back) back.hidden = !isDaily; initIcons(); } function toggleP0WithdrawalChart(){ changeP0WithdrawalChart(p0WithdrawalChartType === 'bar' ? 'doughnut' : 'bar'); closeChartMenus(false); } function changeP0WithdrawalChart(type){ if(type !== 'bar' && type !== 'doughnut') return; p0WithdrawalChartType = type; renderP0WithdrawalChart(); updateP0ChartTypeHints(); } function renderP0WithdrawalChart(){ const gridColor = cssVar('--border-border','#e5e7eb'); const labelColor = cssVar('--text-fg-muted','#475569'); const isDonut = p0WithdrawalChartType === 'doughnut'; const labels = WORKSHOP_DATA.withdrawalStages.map(item=>item.stage); const values = WORKSHOP_DATA.withdrawalStages.map(item=>item.count); replaceP0Chart('p0Withdrawals',{ type:isDonut ? 'doughnut' : 'bar', data:{ labels, datasets:[isDonut ? { label:'Withdrawals', data:values, backgroundColor:[ P.rose, hexToRgba(P.rose,.82), hexToRgba(P.rose,.66), hexToRgba(P.rose,.50), hexToRgba(P.rose,.34) ], borderColor:CHART_SURFACE, borderWidth:2, hoverOffset:4 } : { label:'Withdrawals', data:values, backgroundColor:P.rose, borderRadius:3, maxBarThickness:68 }] }, options:isDonut ? { maintainAspectRatio:false, cutout:'58%', onClick:openP0WithdrawalStage, onHover:setP0ChartDrilldownCursor, plugins:{ legend:{ position:'bottom', labels:{boxWidth:11,boxHeight:11,color:labelColor,font:{size:10},padding:12} } } } : { maintainAspectRatio:false, onClick:openP0WithdrawalStage, onHover:setP0ChartDrilldownCursor, plugins:{legend:{display:false}}, scales:{ x:{grid:{display:false},ticks:{color:labelColor,font:{size:10}}}, y:{beginAtZero:true,grid:{color:gridColor},ticks:{color:labelColor,precision:0}} } } }); } function setP0ChartDrilldownCursor(event,elements){ const target = event?.native?.target; if(target && target.style) target.style.cursor = elements.length ? 'pointer' : 'default'; } function openP0WithdrawalStage(event,elements,chart){ if(!elements?.length) return; if(event?.native?.stopPropagation) event.native.stopPropagation(); const stage = chart?.data?.labels?.[elements[0].index]; if(stage) showP0WithdrawalStageDetail(stage); } document.addEventListener('click',()=>closeChartMenus(false)); document.addEventListener('keydown',event=>{ if(event.key === 'Escape') closeChartMenus(true); }); function metricDefinition(text){ return `
${text}
`; } function detailPageHeader(title, subtitle, status=''){ return `
Analytics / ${title}

${title}

${subtitle}
${status ? `${status.label}` : ''}
`; } function programmeOptions(selected=''){ return `${WORKSHOP_DATA.programmes.map(item=>``).join('')}`; } function intakeTags(intakes){ return `
${intakes.map(item=>`${item.name} · ${item.window}`).join('')}
`; } function applicationRowsHTML(applications){ if(!applications.length){ return `No sample applications match the selected filters.`; } return applications.map(app=>` ${app.id} ${app.candidate}
${app.email} ${app.programme} ${intakeTags(app.intakes)} ${app.submitted} ${app.status} ${app.daysInStage} › `).join(''); } const APPLICATION_MONTH_LABELS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; const APPLICATION_MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December']; function dailyApplicationSeries(monthIndex){ const year = Number(dashboardScope.year || WORKSHOP_DATA.reportingYear); const days = new Date(year,monthIndex + 1,0).getDate(); const total = WORKSHOP_DATA.monthlyApplications[monthIndex] || 0; const weights = Array.from({length:days},(_,index)=>1 + ((index * 7 + monthIndex * 3) % 5)); const weightTotal = weights.reduce((sum,value)=>sum + value,0); const values = weights.map(weight=>Math.floor(total * weight / weightTotal)); let remaining = total - values.reduce((sum,value)=>sum + value,0); for(let index=0; remaining > 0; index=(index + 1) % days){ values[index] += 1; remaining -= 1; } return values; } function showApplicationsDetail(){ destroyCharts(); const m = WORKSHOP_DATA.metrics; const uniqueIntakes = [...new Map(WORKSHOP_DATA.applications.flatMap(app=>app.intakes).map(item=>[item.id,item])).values()]; setMain(` ${detailPageHeader('Applications','Annual portfolio analysis and sample application records')} ${metricDefinition(`Counting rule: ${m.applications.toLocaleString()} Applications = COUNT(DISTINCT applicationId). A Candidate may submit applications to multiple Programmes, and each Programme Application selects one or more Intakes. Therefore ${m.intakeSelections.toLocaleString()} Intake selections are intentionally greater than ${m.applications.toLocaleString()} Applications.`)}
${detailKpi('Programme Applications',m.applications.toLocaleString(),'Each application counted once','blue')} ${detailKpi('Unique Candidates',m.uniqueCandidates.toLocaleString(),'Candidates may apply more than once')} ${detailKpi('Intake Selections',m.intakeSelections.toLocaleString(),'Overlapping; do not add to Applications')} ${detailKpi('Average Intakes / Application',(m.intakeSelections/m.applications).toFixed(2),'Minimum one selected Intake')}
Applications by Intern Category
${barListHTML(WORKSHOP_DATA.programmes.map(item=>({label:item.name,value:item.applications,max:m.applications})),false)}
Application List
Representative mock records from the annual dataset
${applicationRowsHTML(WORKSHOP_DATA.applications)}
Application IDCandidateIntern CategorySelected Intake(s)SubmittedStatusDays in stage
`); applyApplicationFilters(); } function applyApplicationFilters(){ const programme = document.getElementById('applicationProgrammeFilter')?.value || ''; const intake = document.getElementById('applicationIntakeFilter')?.value || ''; const status = document.getElementById('applicationStatusFilter')?.value || ''; const search = (document.getElementById('applicationSearch')?.value || '').trim().toLowerCase(); const filtered = WORKSHOP_DATA.applications.filter(app => (!programme || app.programmeId === programme) && (!intake || app.intakes.some(item=>item.id === intake)) && (!status || app.status === status) && (!search || [app.id,app.candidate,app.email,app.programme].join(' ').toLowerCase().includes(search)) ); const body = document.getElementById('applicationRows'); const result = document.getElementById('applicationFilterResult'); if(body) body.innerHTML = applicationRowsHTML(filtered); if(result) result.textContent = `${filtered.length} of ${WORKSHOP_DATA.applications.length} sample records`; initIcons(); } function showApplicationDetail(applicationId){ destroyCharts(); const app = WORKSHOP_DATA.applications.find(item=>item.id===applicationId); if(!app){ showApplicationsDetail(); return; } const related = WORKSHOP_DATA.applications.filter(item=>item.candidateId===app.candidateId && item.id!==app.id); setMain(` ${detailPageHeader(app.id,`${app.candidate} · ${app.programme}`,{label:app.status,variant:app.variant})} ${metricDefinition(`Record grain: This page represents one Programme Application. Its ${app.intakes.length} selected Intake${app.intakes.length===1?'':'s'} are shown separately so Intake-specific progression is not confused with the overall Application status.`)}
Candidate & Application
Candidate ID
${app.candidateId}
Candidate
${app.candidate}
Email
${app.email}
Institution
${app.institution}
Intern Category
${app.programme}
Submitted
${app.submitted}
Assigned officer
${app.officer}
Days in current stage
${app.daysInStage}
Application Timeline
${app.timeline.map(item=>`
${item[0]}${item[1]}
`).join('')}
Selected Intakes (${app.intakes.length})
${app.intakes.map(intake=>`
${intake.name}
${intake.window}
${intake.status}
${intake.project ? `Project: ${intake.project}` : 'No project placement recorded for this Intake.'}
`).join('')}
Other Programme Applications for this Candidate
${related.length ? `
${related.map(item=>``).join('')}
Application IDIntern CategorySelected Intake(s)Status
${item.id}${item.programme}${intakeTags(item.intakes)}${item.status}›
` : `
No other Programme applications are recorded for this Candidate in the sample dataset.
`}
`); } function barListHTML(items, asPercentage=true){ const maxValue = Math.max(...items.map(item=>asPercentage ? item.value : item.value/item.max*100),1); return `
${items.map(item=>{ const numeric = asPercentage ? item.value : item.value/item.max*100; const width = Math.max(2,numeric/maxValue*100); const label = asPercentage ? `${item.value.toFixed(1)}%` : item.value.toLocaleString(); return `
${item.label}
${label}
`; }).join('')}
`; } function metricConfig(key){ const m = WORKSHOP_DATA.metrics; const configs = { shortlisting:{ title:'Shortlisting Rate', value:percentage(m.shortlisted,m.applications), numerator:m.shortlisted, denominator:m.applications, numeratorLabel:'Shortlisted Applications', denominatorLabel:'Submitted Applications', formula:'COUNT(DISTINCT shortlisted applicationId) ÷ COUNT(DISTINCT submitted applicationId). Each Programme Application is counted once, regardless of how many Intakes were selected.', prior:'29.4% in 2026', action:`${WORKSHOP_DATA.actionCards.newReferral.count} candidates awaiting the next project after Refer` }, acceptance:{ title:'Offer Acceptance Rate', value:percentage(m.offersAccepted,m.offersIssued), numerator:m.offersAccepted, denominator:m.offersIssued, numeratorLabel:'Offers Accepted', denominatorLabel:'Valid Offers Issued', formula:'Accepted valid offers ÷ valid offers issued. Withdrawn or cancelled offers are excluded from the denominator.', prior:'81.9% in 2026', action:`${WORKSHOP_DATA.actionCards.recommendations.count} mentor recommendations awaiting IO action` }, withdrawal:{ title:'Withdrawal Rate', value:percentage(m.withdrawals,m.applications), numerator:m.withdrawals, denominator:m.applications, numeratorLabel:'Withdrawn Applications', denominatorLabel:'Submitted Applications', formula:'Applications with current outcome “Withdrawn” ÷ submitted Programme Applications. The record is attributed to the stage at which withdrawal occurred.', prior:'5.3% in 2026', action:'14 withdrawals occurred during screening' }, capacity:{ title:'Project Capacity Utilisation', value:percentage(m.filledSlots,m.totalSlots), numerator:m.filledSlots, denominator:m.totalSlots, numeratorLabel:'Filled Intake-specific Slots', denominatorLabel:'Total Intake-specific Slots', formula:'Confirmed placements ÷ available project slots for the selected Intake scope. In this sample, projects and capacity are independent for each Intake.', prior:'78.2% in 2026', action:`${m.totalSlots-m.filledSlots} open slots across active Intakes` }, feedback:{ title:'Feedback Response Rate', value:percentage(m.feedbackSubmitted,m.feedbackEligible), numerator:m.feedbackSubmitted, denominator:m.feedbackEligible, numeratorLabel:'Valid Feedback Submitted', denominatorLabel:'Eligible Feedback Requests', formula:'Valid submitted feedback ÷ eligible feedback requests. Intakes whose feedback window is not yet due are shown as N/A and excluded from the denominator.', prior:'79.5% in 2026', action:`${WORKSHOP_DATA.actions.feedbackOutstanding} eligible responses outstanding` } }; return configs[key]; } function metricRateForProgramme(key, programme){ if(key==='shortlisting') return programme.shortlisted/programme.applications*100; if(key==='acceptance') return programme.accepted/programme.offers*100; if(key==='withdrawal') return programme.withdrawn/programme.applications*100; if(key==='capacity') return programme.filled/programme.slots*100; return programme.feedbackSubmitted/programme.feedbackEligible*100; } function metricBreakdownTable(key){ const headings = { shortlisting:['Applications','Shortlisted','Rate'], acceptance:['Offers issued','Accepted','Rate'], withdrawal:['Applications','Withdrawn','Rate'], capacity:['Total slots','Filled slots','Utilisation'], feedback:['Eligible requests','Submitted','Response rate'] }[key]; return `
${WORKSHOP_DATA.programmes.map(programme=>{ const values = key==='shortlisting' ? [programme.applications,programme.shortlisted] : key==='acceptance' ? [programme.offers,programme.accepted] : key==='withdrawal' ? [programme.applications,programme.withdrawn] : key==='capacity' ? [programme.slots,programme.filled] : [programme.feedbackEligible,programme.feedbackSubmitted]; return ``; }).join('')}
Intern Category${headings[0]}${headings[1]}${headings[2]}
${programme.name}${values[0]}${values[1]}${metricRateForProgramme(key,programme).toFixed(1)}%
`; } function inheritedCapacityScopeHTML(){ return `
Inherited dashboard scope
These filters came from the first screen. The controls below only refine this result.
Year${dashboardScope.year} Intern Category${dashboardProgrammeName(dashboardScope.programmeId)} Intake${dashboardIntakeName(dashboardScope.programmeId,dashboardScope.intakeId)}
`; } function capacityProjectsForInheritedScope(){ return WORKSHOP_DATA.projects.filter(project => project.year === dashboardScope.year && (!dashboardScope.programmeId || project.programmeId === dashboardScope.programmeId) && (!dashboardScope.intakeId || project.intakeId === dashboardScope.intakeId) ); } function capacityScopeTotals(){ if(dashboardScope.year !== String(WORKSHOP_DATA.reportingYear)){ return { filled:0, slots:0, source:'No mock portfolio data for the selected year' }; } if(dashboardScope.intakeId){ const projects = capacityProjectsForInheritedScope(); return { filled: projects.reduce((sum,item)=>sum+item.filled,0), slots: projects.reduce((sum,item)=>sum+item.slots,0), source:'Illustrative Intake-specific project records' }; } if(dashboardScope.programmeId){ const programme = WORKSHOP_DATA.programmes.find(item=>item.id===dashboardScope.programmeId); return programme ? { filled:programme.filled, slots:programme.slots, source:'Selected Intern Category portfolio' } : { filled:0, slots:0, source:'No matching portfolio records' }; } return { filled:WORKSHOP_DATA.metrics.filledSlots, slots:WORKSHOP_DATA.metrics.totalSlots, source:'Full annual internship portfolio' }; } function capacityProjectRowsHTML(projects){ if(!projects.length){ return `No sample projects match the inherited dashboard scope and local filters.`; } return projects.map(project=>` ${project.id} ${project.project} ${project.programme} ${project.intake} ${project.demand} ${project.filled} / ${project.slots} ${project.filled===project.slots?'Filled':'Open slots'} `).join(''); } function applyCapacityRecordFilters(){ const status = document.getElementById('capacityStatusFilter')?.value || ''; const search = (document.getElementById('capacityProjectSearch')?.value || '').trim().toLowerCase(); const inherited = capacityProjectsForInheritedScope(); const filtered = inherited.filter(project => (!status || (status==='open' ? project.filled < project.slots : project.filled === project.slots)) && (!search || [project.id,project.project,project.programme,project.intake].join(' ').toLowerCase().includes(search)) ); const body = document.getElementById('capacityProjectRows'); const result = document.getElementById('capacityFilterResult'); if(body) body.innerHTML = capacityProjectRowsHTML(filtered); if(result) result.textContent = `${filtered.length} of ${inherited.length} scoped sample projects`; } function showCapacityDetail(config){ const totals = capacityScopeTotals(); const utilisation = percentage(totals.filled,totals.slots); const openSlots = Math.max(0,totals.slots-totals.filled); const inheritedProjects = capacityProjectsForInheritedScope(); setMain(` ${detailPageHeader(config.title,`Filter inheritance example · Data as of ${WORKSHOP_DATA.asOf}`)} ${metricDefinition(`Business definition: ${config.formula}`)} ${inheritedCapacityScopeHTML()}
${detailKpi('Project Capacity Utilisation',utilisation,totals.source,'blue')} ${detailKpi('Filled Slots',totals.filled.toLocaleString(),'Numerator')} ${detailKpi('Total Slots',totals.slots.toLocaleString(),'Denominator')} ${detailKpi('Open Slots',openSlots.toLocaleString(),'Within the inherited scope','amber')}
Intake-specific Project Records
The local filters below refine, but do not replace, the inherited dashboard scope.
${inheritedProjects.length} scoped sample projects
${capacityProjectRowsHTML(inheritedProjects)}
Project IDProjectIntern CategoryIntakeDemandFilled / SlotsStatus
`); applyCapacityRecordFilters(); } function metricRecordsHTML(key){ if(key==='capacity'){ return `
${WORKSHOP_DATA.projects.map(project=>``).join('')}
Project IDProjectIntern CategoryIntakeDemandFilled / SlotsStatus
${project.id}${project.project}${project.programme}${project.intake}${project.demand}${project.filled} / ${project.slots}${project.filled===project.slots?'Filled':'Open slots'}
`; } if(key==='feedback'){ return `
${WORKSHOP_DATA.feedbackOutstanding.map(item=>``).join('')}
Application IDCandidateIntern CategoryIntakeRespondent groupDue
${item.applicationId}${item.candidate}${item.programme}${item.intake}${item.group}${item.due}›
`; } let apps = []; if(key==='shortlisting') apps = WORKSHOP_DATA.applications.filter(app=>['Pending Review','Incomplete','Shortlisted'].includes(app.status)); if(key==='acceptance') apps = WORKSHOP_DATA.applications.filter(app=>['Offer Accepted','Offer Pending','Placement Confirmed'].includes(app.status)); if(key==='withdrawal') apps = WORKSHOP_DATA.applications.filter(app=>app.status==='Withdrawn'); return `
${apps.map(app=>``).join('')}
Application IDCandidateIntern CategorySelected Intake(s)StatusDays in stage
${app.id}${app.candidate}${app.programme}${intakeTags(app.intakes)}${app.status}${app.daysInStage}›
`; } function withdrawalStageScopeHTML(stage){ return `
Drill-down scope
Dashboard filters are inherited. Withdrawal stage comes from the selected chart bar or segment.
Year${dashboardScope.year} Intern Category${dashboardProgrammeName(dashboardScope.programmeId)} Intake${dashboardIntakeName(dashboardScope.programmeId,dashboardScope.intakeId)} Withdrawal stage${stage}
`; } function withdrawalStageRecords(stage){ return WORKSHOP_DATA.withdrawalRecords.filter(record => record.year === dashboardScope.year && record.stage === stage && (!dashboardScope.programmeId || record.programmeId === dashboardScope.programmeId) && (!dashboardScope.intakeId || record.intakeId === dashboardScope.intakeId) ); } function withdrawalStageRecordsHTML(records){ if(!records.length){ return `
No representative records match the inherited dashboard filters.
`; } return `
${records.map(record=>` `).join('')}
Application IDCandidateIntern CategoryIntakeWithdrawal stageReasonWithdrawn
${record.id} ${record.candidate} ${record.programme} ${record.intake} ${record.stage} ${record.reason} ${record.withdrawn}
`; } function showP0WithdrawalStageDetail(stage){ syncDashboardScope(); const stageInfo = WORKSHOP_DATA.withdrawalStages.find(item=>item.stage===stage); if(!stageInfo){ showP0Detail('withdrawal'); return; } destroyCharts(); const records = withdrawalStageRecords(stage); const reasonItems = stageInfo.reasons.map(([label,value])=>({label,value,max:stageInfo.count})); const topReason = stageInfo.reasons.reduce((top,item)=>item[1] > top[1] ? item : top,stageInfo.reasons[0]); const allWithdrawals = WORKSHOP_DATA.metrics.withdrawals; const allApplications = WORKSHOP_DATA.metrics.applications; setMain(` ${detailPageHeader(`Withdrawals · ${stage}`,`Chart drill-down · ${WORKSHOP_DATA.reportingYear} · As at ${WORKSHOP_DATA.asOf}`)} ${withdrawalStageScopeHTML(stage)} ${metricDefinition(`Drill-down rule: Each withdrawn application is counted once against the last recruitment stage reached before withdrawal. Offer Rejected is not counted as a withdrawal.`)}
${detailKpi('Withdrawals in stage',stageInfo.count.toLocaleString(),stage,'blue')} ${detailKpi('Share of withdrawals',percentage(stageInfo.count,allWithdrawals),`${stageInfo.count} of ${allWithdrawals}`)} ${detailKpi('Share of applications',percentage(stageInfo.count,allApplications),`${stageInfo.count} of ${allApplications.toLocaleString()}`)} ${detailKpi('Most common reason',topReason[0],`${topReason[1]} applications`,'amber','medium')}
Withdrawal Reasons · ${stage}
${barListHTML(reasonItems,false)}
Selected Stage Context
${stageInfo.count} withdrawalsOnly records attributed to ${stage} are included.
${percentage(stageInfo.count,allWithdrawals)} of all withdrawalsThe four valid pre-offer stages reconcile to ${allWithdrawals} withdrawals.
${topReason[0]}Review the representative records below before deciding follow-up.
${stage} Withdrawal Records
${records.length} representative records shown from ${stageInfo.count} records in this stage
${withdrawalStageRecordsHTML(records)}
`); } function showP0Detail(key){ syncDashboardScope(); destroyCharts(); if(key==='applications'){ showApplicationsDetail(); return; } const config = metricConfig(key); if(!config){ showDashboard(); return; } if(key==='capacity'){ showCapacityDetail(config); return; } const barItems = WORKSHOP_DATA.programmes.map(programme=>({label:programme.name,value:metricRateForProgramme(key,programme)})); const recordsTitle = key==='capacity' ? 'Intake-specific Project Records' : key==='feedback' ? 'Outstanding Feedback Records' : key==='withdrawal' ? 'Withdrawn Application Records' : key==='acceptance' ? 'Offer Records Requiring Review' : 'Applications Requiring Review'; setMain(` ${detailPageHeader(config.title,`P0 metric analysis · ${WORKSHOP_DATA.reportingYear} · As at ${WORKSHOP_DATA.asOf}`)} ${metricDefinition(`Business definition: ${config.formula}`)}
${detailKpi(config.title,config.value,'Current reporting scope','blue')} ${detailKpi(config.numeratorLabel,config.numerator.toLocaleString(),'Numerator')} ${detailKpi(config.denominatorLabel,config.denominator.toLocaleString(),'Denominator')}
${config.title} by Intern Category
${barListHTML(barItems)}
Decision Context
Current: ${config.value}Calculated from the shared annual mock dataset.
Previous year: ${config.prior}Use trend as context, not as a separate P0 metric.
${config.action}Open the records below to identify the next action.
Intern Category Breakdown
${metricBreakdownTable(key)}
${recordsTitle}
Representative records supporting the metric
${metricRecordsHTML(key)}
`); } function renderDrawerWidgets(){ const p0Widgets = [ ['Applications','Programme applications, unique candidates and Intake selections'], ['Shortlisting Rate','Shortlisted applications ÷ submitted applications'], ['Offer Acceptance Rate','Accepted offers ÷ valid offers issued'], ['Withdrawal Rate','Withdrawn applications ÷ submitted applications'], ['Project Capacity Utilisation','Filled ÷ total Intake-specific project slots'], ['Feedback Response Rate','Valid responses ÷ eligible feedback requests'] ]; const activeWidgets = currentRole === 'director' ? [p0Widgets[0],p0Widgets[2],p0Widgets[4],p0Widgets[5]] : p0Widgets; const availableWidgets = currentRole === 'director' ? [p0Widgets[1],p0Widgets[3]] : []; document.getElementById('activeWidgets').innerHTML = activeWidgets.map(([title,desc])=>`
${title}
${desc}
`).join(''); document.getElementById('availableWidgets').innerHTML = availableWidgets.length ? availableWidgets.map(([title,desc])=>`
${title}
${desc}
`).join('') : `
All proposed P0 metrics are shown in the Internship Officer view. Backup metrics remain in the workshop inventory for discussion.
`; initIcons(); } /* Init */ renderDrawerWidgets(); applyProfile(ROLE_PROFILES.director); showDashboard(); initIcons();