- {getCategoryIcon(question.category)} {question.category.toUpperCase()} ({question.points}pt)
+
-
-
- Calculated Score:
{calculateSectionScore().toFixed(2)} / 25
+ {/* Actions */}
+
+
+ Current section score (normalized):{' '}
+ {normalizedSectionScore.toFixed(1)} / 25
+
+
+
+
+
-
- {tab > 0 && (
-
-
- {/* Report Generation Section - Only show when assessment is complete */}
- {isAssessmentComplete && (
-
-
-
-
- ✅ Assessment Complete!
-
-
- Total Score: {assessmentResults.reduce((sum, result) => sum + result.score, 0).toFixed(1)} / 150
-
-
-
-
-
-
-
-
- )}
diff --git a/src/app/components/questionnaire/assessmentPrint.ts b/src/app/components/questionnaire/assessmentPrint.ts
new file mode 100644
index 0000000..e70efa8
--- /dev/null
+++ b/src/app/components/questionnaire/assessmentPrint.ts
@@ -0,0 +1,266 @@
+export interface AssessmentAnswerDetail {
+ questionId: string;
+ questionText: string;
+ selectedAnswer: string;
+ pointsEarned: number;
+ maxPoints: number;
+}
+
+export interface AssessmentResult {
+ sectionName: string;
+ score: number;
+ maxScore: number;
+ answers: AssessmentAnswerDetail[];
+ completedAt: Date;
+}
+
+export interface AssessmentSummary {
+ totalScore: number;
+ maxTotalScore: number;
+ overallPercentage: number;
+ maturityLevel: string;
+}
+
+export const getAssessmentSummary = (assessmentResults: AssessmentResult[]): AssessmentSummary => {
+ if (!assessmentResults.length) {
+ return {
+ totalScore: 0,
+ maxTotalScore: 0,
+ overallPercentage: 0,
+ maturityLevel: 'Developing',
+ };
+ }
+
+ const totalScore = assessmentResults.reduce((sum, result) => sum + result.score, 0);
+ const maxTotalScore = assessmentResults.reduce((sum, result) => sum + result.maxScore, 0);
+ const overallPercentage = maxTotalScore > 0 ? (totalScore / maxTotalScore) * 100 : 0;
+
+ let maturityLevel = 'Developing';
+ if (overallPercentage >= 80) maturityLevel = 'Expert';
+ else if (overallPercentage >= 65) maturityLevel = 'Advanced';
+ else if (overallPercentage >= 45) maturityLevel = 'Intermediate';
+ else if (overallPercentage >= 25) maturityLevel = 'Basic';
+
+ return { totalScore, maxTotalScore, overallPercentage, maturityLevel };
+};
+
+export const printAssessmentReport = (assessmentResults: AssessmentResult[]): void => {
+ const printWindow = window.open('', '_blank');
+ if (!printWindow) return;
+
+ const { totalScore, maxTotalScore, overallPercentage, maturityLevel } = getAssessmentSummary(assessmentResults);
+
+ const maturityColorMap: Record
= {
+ Expert: '#ef4444',
+ Advanced: '#f97316',
+ Intermediate: '#eab308',
+ Basic: '#22c55e',
+ Developing: '#6b7280',
+ };
+
+ const maturityColor = maturityColorMap[maturityLevel] ?? '#3b82f6';
+
+ const sectionsHtml = assessmentResults
+ .map((result) => {
+ const sectionPercentage = result.maxScore > 0 ? (result.score / result.maxScore) * 100 : 0;
+ return `
+
+ ${result.sectionName}
+
+ Score: ${result.score.toFixed(1)} / 25
+ ${sectionPercentage.toFixed(1)}%
+
+
+ ${result.answers
+ .map(
+ (answer) => `
+
+
Q: ${answer.questionText}
+
A: ${answer.selectedAnswer}
+
${answer.pointsEarned.toFixed(1)} / ${answer.maxPoints.toFixed(1)} pts
+
+ `,
+ )
+ .join('')}
+
+
+ `;
+ })
+ .join('');
+
+ printWindow.document.write(`
+
+
+ PolydraIQ Assessment Report
+
+
+
+
+ PolydraIQ AI Governance Assessment Report
+ Generated: ${new Date().toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+
+
+
+
Overall Score: ${totalScore.toFixed(1)} / ${maxTotalScore}
+
Overall Maturity: ${overallPercentage.toFixed(1)}%
+
+
Maturity Level: ${maturityLevel}
+
+
+
+ Section scores are normalized to a maximum of 25 points each. Detailed responses are listed below for audit and follow-up planning.
+
+
+ ${sectionsHtml}
+
+
+ USE DISCLAIMER: This assessment report is generated from a self-assessed questionnaire and is provided "AS IS" for informational and preliminary evaluation purposes only. It does not constitute legal, regulatory, risk, or audit advice, and does not create any form of certification or accreditation. Organizations remain solely responsible for their own governance, risk, and compliance decisions and should seek qualified professional guidance for any production or regulatory use.
+
+
+
+ `);
+
+ printWindow.document.close();
+ printWindow.print();
+}
diff --git a/src/app/context/AssessmentContext.tsx b/src/app/context/AssessmentContext.tsx
new file mode 100644
index 0000000..1b22461
--- /dev/null
+++ b/src/app/context/AssessmentContext.tsx
@@ -0,0 +1,59 @@
+import React, { createContext, useContext, useState, ReactNode } from 'react';
+import { AssessmentResult, printAssessmentReport } from '../components/questionnaire/assessmentPrint';
+
+interface AssessmentContextValue {
+ assessmentResults: AssessmentResult[];
+ /** Replace the entire result set (e.g., after completing or recomputing the assessment). */
+ setAssessmentResults: (results: AssessmentResult[]) => void;
+ /** Whether the guided assessment has been fully completed. */
+ isAssessmentComplete: boolean;
+ /** Mark the assessment as complete or incomplete. */
+ setAssessmentComplete: (complete: boolean) => void;
+ /** Clear results and completion state (for a fresh run). */
+ resetAssessment: () => void;
+ /** Invoke the shared print helper for the current results. */
+ printReport: () => void;
+}
+
+const AssessmentContext = createContext(undefined);
+
+export const AssessmentProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
+ const [assessmentResults, setAssessmentResultsState] = useState([]);
+ const [isAssessmentComplete, setAssessmentComplete] = useState(false);
+
+ const resetAssessment = () => {
+ setAssessmentResultsState([]);
+ setAssessmentComplete(false);
+ };
+
+ const printReport = () => {
+ if (!assessmentResults.length) {
+ window.alert('To generate a full report, please complete the Guided Assessment first.');
+ return;
+ }
+ printAssessmentReport(assessmentResults);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAssessmentContext = (): AssessmentContextValue => {
+ const ctx = useContext(AssessmentContext);
+ if (!ctx) {
+ throw new Error('useAssessmentContext must be used within an AssessmentProvider');
+ }
+ return ctx;
+};
diff --git a/src/app/pages/home.spec.tsx b/src/app/pages/home.spec.tsx
index d72bc5d..f97ad69 100644
--- a/src/app/pages/home.spec.tsx
+++ b/src/app/pages/home.spec.tsx
@@ -1,4 +1,4 @@
-import { render } from '@testing-library/react';
+import { render } from '../../test-utils';
import Home from './home';
diff --git a/src/test-utils/index.tsx b/src/test-utils/index.tsx
index 3fb9d43..13174f1 100644
--- a/src/test-utils/index.tsx
+++ b/src/test-utils/index.tsx
@@ -5,6 +5,7 @@
import { render, RenderOptions, RenderResult } from '@testing-library/react';
import React, { ReactElement } from 'react';
import { BrowserRouter } from 'react-router-dom';
+import { AssessmentProvider } from '../app/context/AssessmentContext';
// Mock localStorage
export const mockLocalStorage = (() => {
@@ -70,10 +71,13 @@ export const customRender = (
{ withRouter = false, ...options }: CustomRenderOptions = {}
): RenderResult => {
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
+ let content = children;
+
if (withRouter) {
- return {children};
+ content = {content};
}
- return <>{children}>;
+
+ return {content};
};
return render(ui, { wrapper: AllTheProviders, ...options });