// evaluation-nist-engine.jsx — Verigo Global: NIST CSF v2.0 readiness evaluation engine // All six functions are assessed regardless of tier; tier shapes recommendations. // Exposes window.EVAL_NIST const { V: GV, MAXW: GMW, FONT: GFT } = window; // Compute subcategory counts per function from NIST_FUNCS global (loaded first by data file) const N_FN_COUNTS = {}; (function(){ NIST_FUNCS.forEach(f=>{ N_FN_COUNTS[f.fn]=f.cats.reduce((a,c)=>a+c.items.length,0); }); })(); /* ── QUESTION BANK — 6 functions × 3 questions, tagged with CSF subcategory IDs ── */ const NIST_Q = [ { fn:'GV', name:'Govern', icon:'briefcase', docs:['Cybersecurity Policy','Organizational Risk Management Policy','Cybersecurity Roles & Responsibilities Policy'], q:[ ['GV.OC-01','The organizational mission is understood and informs how cybersecurity risk is prioritized.'], ['GV.RM-02','Cybersecurity risk appetite and risk tolerance statements are established, communicated, and maintained.'], ['GV.SC-05','Requirements to address cybersecurity risks in supply chains are established and integrated into contracts.'], ]}, { fn:'ID', name:'Identify', icon:'search', docs:['Asset Management Policy','Vulnerability Management Policy'], q:[ ['ID.AM-01','Inventories of hardware and software managed by the organization are established and maintained.'], ['ID.RA-01','Vulnerabilities in assets are identified, validated, and recorded.'], ['ID.IM-01','Improvements are identified from security evaluations, tests, and exercises.'], ]}, { fn:'PR', name:'Protect', icon:'shield', docs:['Identity & Access Management Policy','Data Security Policy','Platform & Infrastructure Security Policy','Security Awareness & Training Policy'], q:[ ['PR.AA-01','Identities and credentials for authorized users, services, and hardware are managed.'], ['PR.DS-01','The confidentiality, integrity, and availability of data at rest are protected.'], ['PR.PS-01','Configuration management practices are established and applied across systems.'], ]}, { fn:'DE', name:'Detect', icon:'activity', docs:['Continuous Monitoring Policy','Security Monitoring & Alerting Procedure'], q:[ ['DE.CM-01','Networks and network services are monitored to find potentially adverse events.'], ['DE.AE-03','Information from multiple sources is correlated to understand the scope of adverse events.'], ['DE.AE-08','Incidents are declared when adverse events meet the defined incident criteria.'], ]}, { fn:'RS', name:'Respond', icon:'zap', docs:['Incident Response Policy','Incident Response Procedure'], q:[ ['RS.MA-01','The incident response plan is executed in coordination with relevant third parties.'], ['RS.AN-03','Analysis is performed to establish what occurred and the root cause of the incident.'], ['RS.MI-01','Incidents are contained to limit their impact on operations.'], ]}, { fn:'RC', name:'Recover', icon:'refresh', docs:['Business Continuity & Recovery Policy','Business Continuity & Recovery Testing Procedure'], q:[ ['RC.RP-01','The recovery portion of the incident response plan is executed once initiated.'], ['RC.RP-05','The integrity of restored assets is verified and normal operating status is confirmed.'], ['RC.CO-03','Recovery activities and progress are communicated to designated internal and external stakeholders.'], ]}, ]; const NIST_TIER_LABELS = { tier1:'Tier 1 · Partial', tier2:'Tier 2 · Risk Informed', tier3:'Tier 3 · Repeatable', tier4:'Tier 4 · Adaptive' }; function nSectionsForTier(tier) { // All 6 functions are always assessed; tier shapes reco but not sections. const lv = (tier==='tier1'||tier==='tier2'||tier==='tier3'||tier==='tier4') ? tier : 'tier3'; const grp = NIST_TIER_LABELS[lv]; return NIST_Q.map(f=>({ fn:f.fn, theme:f.fn+' · '+f.name, group:grp, icon:f.icon, docs:f.docs, controls:N_FN_COUNTS[f.fn]||10, q:f.q })); } const NOPTIONS = [['Not started',0],['Partial',1],['Largely',2],['Fully',3]]; const NBANDS = [ [0,'Initial',GV.orange,'Foundational cybersecurity practices are largely absent — start with Govern and Identify.'], [40,'Developing',GV.orange,'Some practices exist but the program is not consistently applied across the organization.'], [60,'Repeatable',GV.purple,'Formal, approved practices are in place — focus on closing the weaker functions.'], [80,'Adaptive',GV.purple,'Strong posture — refine evidence, expand monitoring, and optimize continuously.'], ]; const nBandFor = pct=>{ let b=NBANDS[0]; NBANDS.forEach(x=>{ if(pct>=x[0]) b=x; }); return b; }; function nCompute(answers, sections, tier) { const secs=sections||nSectionsForTier('tier3'); const themes=secs.map((t,ti)=>{ const max=t.q.length*3; let sum=0; t.q.forEach((_,qi)=>{ sum+=(answers[ti+'-'+qi]||0); }); const pct=Math.round((sum/max)*100); return {fn:t.fn,theme:t.theme,group:t.group,icon:t.icon,docs:t.docs,controls:t.controls,pct,gaps:Math.round((1-pct/100)*t.controls)}; }); const overall=themes.length?Math.round(themes.reduce((a,t)=>a+t.pct,0)/themes.length):0; const totalControls=secs.reduce((a,t)=>a+t.controls,0); const gaps=themes.reduce((a,t)=>a+t.gaps,0); return { themes, overall, gaps, totalControls, tier:(secs[0]&&secs[0].group)||NIST_TIER_LABELS.tier3, band:nBandFor(overall) }; } /* ── SEGMENTED ────────────────────────────────────────────── */ const NSegmented = ({ value, onChange }) => (
{NOPTIONS.map(([label,val])=>{ const on=value===val; return ; })}
); /* ── QUIZ ────────────────────────────────────────────────── */ const EvalQuizNIST = ({ onComplete, onExit, tier, initialAnswers, initialStep, onProgress }) => { const sections = React.useMemo(()=>nSectionsForTier(tier),[tier]); const [step,setStep] = React.useState(()=>Math.min(Math.max(initialStep||0,0),sections.length-1)); const [answers,setAnswers] = React.useState(()=>initialAnswers||{}); const total=sections.length; const cur=sections[step]; const report=(a,s)=>{ if(onProgress) onProgress(a,s); }; const set=(qi,v)=>setAnswers(a=>{ const na={...a,[step+'-'+qi]:v}; report(na,step); return na; }); const stepDone=cur.q.every((_,qi)=>answers[step+'-'+qi]!==undefined); const answeredCount=Object.keys(answers).length; const totalQ=sections.reduce((a,t)=>a+t.q.length,0); const progress=Math.round((answeredCount/totalQ)*100); const next=()=>{ if(step{ if(step>0){const ns=step-1;setStep(ns);report(answers,ns);window.scrollTo({top:0});}else{onExit();} }; return (
Function {step+1} of {total} {progress}% complete
{cur.group}

{cur.theme}

For each statement, choose how completely it reflects your organization today. Each maps to a specific NIST CSF v2.0 subcategory.

{cur.q.map(([id,text],qi)=>(
{id}

{text}

set(qi,v)} />
))}
{step===0?'Exit':'Back'} {step===total-1?'See my results':'Next function'}
{!stepDone&&

Answer all {cur.q.length} statements to continue.

}
Your progress is saved automatically — you can leave and resume later.
); }; /* ── RESULTS ─────────────────────────────────────────────── */ const NDonut = ({ pct }) => (
{pct} / 100
); const EvalResultsNIST = ({ results, user, onNav, onRetake }) => { const { themes, overall, gaps, band, totalControls, tier } = results; const [,label,,bandDesc] = band; const isHighTier = /Tier 3|Tier 4/.test(tier||''); const sorted = [...themes].sort((a,b)=>a.pct-b.pct); const focus = sorted.filter(t=>t.pct<80); const MODULES = window.TK_NIST.QUOTE_MODULES; const reco = new Set(['tailor']); if (overall<80) reco.add('guidance'); if (overall<60) { reco.add('impl'); reco.add('profile'); reco.add('gap'); } if (isHighTier) reco.add('mapping'); themes.forEach(t=>{ if(t.pct<60&&t.fn==='DE') reco.add('monitor'); }); const recoModules = MODULES.filter(m=>reco.has(m.id)); const goBuild = ()=>{ try{localStorage.setItem('verigo_reco_nist',JSON.stringify([...reco]));}catch(e){} onNav('checkout'); }; return (
Your readiness report · {tier}
Overall maturity · {label}

{overall>=80?"You're approaching your target tier.":overall>=60?"A solid base, with clear gaps to close.":"There's real groundwork to do first."}

{bandDesc} We estimate ~{gaps} of {totalControls} subcategories need attention across your profile.{user&&user.company?' Prepared for '+user.company:''}

{themes.map((t,i)=>{ const c=t.pct<60?GV.orange:GV.purple; return(
{t.theme} {t.pct}%
~{t.gaps} of {t.controls} subcategories need attention
); })}
{focus.length===0&&(
Every function scored 80%+ — you're in strong shape. A practitioner can validate evidence and optimize your target profile.
)} {focus.map((t,i)=>(
{i+1}

{t.theme}

{t.pct}%
{t.docs.map((d,di)=>({d}))}
onNav('toolkit:nist')} style={{whiteSpace:'nowrap'}}>Open toolkit
))}
window.EVAL_NIST.generateEvalPDF(results,user)}> Download my results (PDF) Retake the evaluation
{recoModules.map((m,i)=>(
{m.name}
{m.desc}
))}
Build my custom package Pick your options, see the price, then download a custom quote.
); }; /* ── RESULTS PDF ──────────────────────────────────────────── */ function genEvalPDFNIST(results, user) { const lib=window.jspdf; if(!lib||!lib.jsPDF){alert('PDF engine is still loading.');return;} const {themes,overall,gaps,band,totalControls,tier}=results; const doc=new lib.jsPDF({unit:'pt',format:'a4'}); const W=doc.internal.pageSize.getWidth(); const H=doc.internal.pageSize.getHeight(); const M=50; const PURPLE=[91,46,145],ORANGE=[232,98,42],INK=[30,30,46],GREY=[120,120,134]; let y=100; doc.setFillColor(...PURPLE);doc.rect(0,0,W,70,'F');doc.setFillColor(...ORANGE);doc.rect(W-M-11,28,11,11,'F');doc.setTextColor(255,255,255);doc.setFont('helvetica','bold');doc.setFontSize(15);doc.text('VERIGO GLOBAL',M,33);doc.setFont('helvetica','normal');doc.setFontSize(8.5);doc.setTextColor(214,204,232);doc.text('Compliance by Design',M,49);doc.setTextColor(255,255,255);doc.setFontSize(8.5);doc.text('NIST CSF v2.0',W-M-20,35,{align:'right'}); doc.setTextColor(...INK);doc.setFont('helvetica','bold');doc.setFontSize(21);doc.text('NIST CSF v2.0 Readiness Report',M,y);y+=12;doc.setDrawColor(...ORANGE);doc.setLineWidth(2.5);doc.line(M,y,M+54,y);y+=16;doc.setFont('helvetica','normal');doc.setFontSize(10);doc.setTextColor(...GREY);doc.text(String(tier||''),M,y);y+=18; doc.setFont('helvetica','bold');doc.setFontSize(40);doc.setTextColor(...PURPLE);doc.text(String(overall),M,y+14);doc.setFontSize(11);doc.setTextColor(...GREY);doc.setFont('helvetica','normal');doc.text('/ 100',M+58,y+14);doc.setFont('helvetica','bold');doc.setFontSize(13);doc.setTextColor(...INK);doc.text('Overall maturity: '+band[1],M+130,y-2);doc.setFont('helvetica','normal');doc.setFontSize(10);doc.setTextColor(...GREY);doc.splitTextToSize('~'+gaps+' of '+(totalControls||90)+' subcategories need attention. '+band[3],W-M-(M+130)).forEach((ln,i)=>doc.text(ln,M+130,y+14+i*13));y+=56; if(user&&(user.company||user.name)){doc.setDrawColor(224,224,232);doc.setLineWidth(0.5);doc.line(M,y,W-M,y);y+=16;doc.setFontSize(9);doc.setTextColor(...PURPLE);doc.setFont('helvetica','bold');doc.text('Prepared for '+(user.company||user.name),M,y);doc.setFont('helvetica','normal');doc.setTextColor(...GREY);doc.text(new Date().toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'}),W-M,y,{align:'right'});y+=22;} doc.setFont('helvetica','bold');doc.setFontSize(12.5);doc.setTextColor(...PURPLE);doc.text('Maturity by CSF function',M,y);y+=6;doc.setDrawColor(...PURPLE);doc.setLineWidth(0.8);doc.line(M,y,W-M,y);y+=20; const barX=M+200,barW=W-M-barX; themes.forEach(t=>{if(y>H-70){doc.addPage();y=60;}doc.setFont('helvetica','bold');doc.setFontSize(10);doc.setTextColor(...INK);doc.text(t.theme,M,y+3);doc.setFillColor(237,237,245);doc.rect(barX,y-7,barW,10,'F');const col=t.pct<60?ORANGE:PURPLE;doc.setFillColor(...col);doc.rect(barX,y-7,barW*(t.pct/100),10,'F');doc.setFont('helvetica','bold');doc.setFontSize(10);doc.setTextColor(...col);doc.text(t.pct+'%',W-M,y+3,{align:'right'});y+=26;}); const focus=[...themes].sort((a,b)=>a.pct-b.pct).filter(t=>t.pct<80); if(focus.length){if(y>H-90){doc.addPage();y=60;}doc.setFont('helvetica','bold');doc.setFontSize(12.5);doc.setTextColor(...PURPLE);doc.text('Recommended next steps',M,y);y+=6;doc.setDrawColor(...PURPLE);doc.setLineWidth(0.8);doc.line(M,y,W-M,y);y+=18;focus.forEach((t,i)=>{if(y>H-90){doc.addPage();y=60;}doc.setFont('helvetica','bold');doc.setFontSize(10.5);doc.setTextColor(...INK);doc.text((i+1)+'. '+t.theme+' ('+t.pct+'%)',M,y);y+=14;doc.setFont('helvetica','normal');doc.setFontSize(9.5);doc.setTextColor(...GREY);doc.splitTextToSize('Toolkit documents: '+t.docs.join(', '),W-2*M-14).forEach(ln=>{doc.text(ln,M+14,y);y+=12;});y+=8;});} const total=doc.internal.getNumberOfPages();for(let i=1;i<=total;i++){doc.setPage(i);doc.setDrawColor(224,224,232);doc.setLineWidth(0.5);doc.line(M,H-38,W-M,H-38);doc.setFont('helvetica','normal');doc.setFontSize(8);doc.setTextColor(150,150,160);doc.text('© 2026 Verigo Global · Readiness self-assessment — indicative, not a formal assessment',M,H-24);doc.text(i+' / '+total,W-M,H-24,{align:'right'});} doc.save('Verigo-NIST-Readiness-Report.pdf'); } window.EVAL_NIST = { QUESTIONS:NIST_Q, sectionsForTier:nSectionsForTier, computeResults:nCompute, EvalQuiz:EvalQuizNIST, EvalResults:EvalResultsNIST, generateEvalPDF:genEvalPDFNIST }; Object.assign(window, { EvalQuizNIST, EvalResultsNIST });