Compliance Score Engine
Measure and improve your NDPA 2023 compliance posture with the built-in scoring engine
What the Compliance Score Engine Does
The compliance score engine analyses the state of all active toolkit modules and produces a numeric score from 0–100, a rating, per-module breakdowns, and prioritised recommendations. It gives your team an at-a-glance view of where you stand against the Nigeria Data Protection Act (NDPA) 2023 and highlights the highest-impact gaps to close.
Not a legal audit
The score reflects the completeness of your technical implementation using the toolkit. It does not replace a formal Data Protection Impact Assessment or legal advice. Use it as a development health check, not a compliance certificate.
Using getComplianceScore()
getComplianceScore() is a pure function — pass it a ComplianceInput object describing your current implementation state and it returns a full ComplianceReport synchronously. Import it from the main package:
import { getComplianceScore } from '@tantainnovative/ndpr-toolkit';
const report = getComplianceScore({
// Consent module
consent: {
hasConsentMechanism: true, // affirmative consent collected before processing
hasPurposeSpecification: true, // purpose communicated at point of consent
hasWithdrawalMechanism: true, // data subjects can withdraw consent
hasMinorProtection: false, // age-verification / parental-consent not yet implemented
consentRecordsRetained: true, // consent records persisted
},
// Data Subject Rights module
dsr: {
hasRequestMechanism: true, // formal channel exists for DSR submissions
supportsAccess: true, // right of access honoured
supportsRectification: true, // right to rectification honoured
supportsErasure: false, // erasure workflow not yet implemented
supportsPortability: false, // portability not yet implemented
supportsObjection: false, // right to object not yet implemented
responseTimelineDays: 14, // max days to respond (must be ≤ 30)
},
// Data Protection Impact Assessment module
dpia: {
conductedForHighRisk: true, // DPIA conducted before high-risk processing
documentedRisks: true, // risks to data subjects documented
mitigationMeasures: false, // mitigation measures not yet documented
},
// Breach notification module
breach: {
hasNotificationProcess: true, // documented breach response process exists
notifiesWithin72Hours: true, // NDPC notified within 72 hours of discovery
hasRiskAssessment: false, // per-breach risk assessment not yet performed
hasRecordKeeping: true, // breach register maintained
},
// Privacy policy module
policy: {
hasPrivacyPolicy: true, // privacy policy exists
isPubliclyAccessible: true, // policy is reachable on website/app
lastUpdated: '2024-11-01', // ISO date — >13 months old counts as a gap
coversAllSections: true, // all NDPA-mandated disclosures present
},
// Lawful basis module
lawfulBasis: {
documentedForAllProcessing: true, // lawful basis recorded for every activity
hasLegitimateInterestAssessment: false, // LIA not yet completed
},
// Cross-border transfers module
crossBorder: {
hasTransferMechanisms: true, // SCCs / BCRs / adequacy decisions in place
adequacyAssessed: true, // destination-country adequacy reviewed
ndpcApprovalObtained: false, // NDPC approval pending
},
// Records of Processing Activities module
ropa: {
maintained: true, // ROPA exists and is maintained
includesAllProcessing: false, // not all activities captured yet
lastReviewed: '2024-10-15', // ISO date — >6 months old counts as a gap
},
});
console.log(report.score); // e.g. 68
console.log(report.rating); // 'needs-work'
console.log(report.recommendations); // array of prioritised Recommendation objectsComplianceInput Reference
The full ComplianceInput interface — every field and its type:
interface ComplianceInput {
consent: {
hasConsentMechanism: boolean;
hasPurposeSpecification: boolean;
hasWithdrawalMechanism: boolean;
hasMinorProtection: boolean;
consentRecordsRetained: boolean;
};
dsr: {
hasRequestMechanism: boolean;
supportsAccess: boolean;
supportsRectification: boolean;
supportsErasure: boolean;
supportsPortability: boolean;
supportsObjection: boolean;
/** Max response time in days — values > 30 count as a gap */
responseTimelineDays: number;
};
dpia: {
conductedForHighRisk: boolean;
documentedRisks: boolean;
mitigationMeasures: boolean;
};
breach: {
hasNotificationProcess: boolean;
notifiesWithin72Hours: boolean;
hasRiskAssessment: boolean;
hasRecordKeeping: boolean;
};
policy: {
hasPrivacyPolicy: boolean;
isPubliclyAccessible: boolean;
/** ISO date string (YYYY-MM-DD) — > 13 months old counts as a gap */
lastUpdated: string;
coversAllSections: boolean;
};
lawfulBasis: {
documentedForAllProcessing: boolean;
hasLegitimateInterestAssessment: boolean;
};
crossBorder: {
hasTransferMechanisms: boolean;
adequacyAssessed: boolean;
ndpcApprovalObtained: boolean;
};
ropa: {
maintained: boolean;
includesAllProcessing: boolean;
/** ISO date string (YYYY-MM-DD) — > 6 months old counts as a gap */
lastReviewed: string;
};
}Understanding the Report
The returned ComplianceReport object has the following shape:
interface ComplianceReport {
/** Numeric score 0–100 */
score: number;
/** Rating bucket */
rating: 'excellent' | 'good' | 'needs-work' | 'critical';
/** Per-module breakdown keyed by module name */
modules: Record<string, {
name: string; // e.g. "consent"
score: number; // 0–100 for this module
maxScore: number; // always 100
weightedScore: number; // weighted contribution to the overall score
ndpaSections: string[]; // NDPA sections this module maps to
gaps: string[]; // human-readable list of failing checks
}>;
/** Recommendations sorted by priority (critical first) */
recommendations: Array<{
module: string;
key: string;
label: string;
priority: 'critical' | 'high' | 'medium' | 'low';
effort: 'low' | 'medium' | 'high';
recommendation: string;
ndpaSection: string;
}>;
/** Top-level regulatory references */
regulatoryReferences: Array<{
section: string;
title: string;
url?: string;
}>;
/** ISO timestamp of when the report was generated */
generatedAt: string;
}Rating thresholds
| Score | Rating | Interpretation |
|---|---|---|
| 90–100 | excellent | Fully implemented — only minor gaps remain |
| 70–89 | good | Mostly compliant — a few medium-priority items outstanding |
| 40–69 | needs-work | Partial implementation — several notable gaps |
| 0–39 | critical | Critical non-compliance — immediate action required |
Module Weights & NDPA Section References
Weights reflect the relative emphasis each topic receives in the NDPA 2023 text and the Nigeria Data Protection Commission (NDPC) enforcement guidance.
| Module | Weight | Primary NDPA Sections |
|---|---|---|
| Consent | 20% | Section 25, Section 26 |
| Data Subject Rights (DSR) | 15% | Sections 34–39 |
| Breach Notification | 15% | Section 40 |
| Privacy Policy | 12% | Section 29 |
| DPIA | 12% | Section 28 |
| Lawful Basis | 10% | Section 25(1) |
| Cross-Border Transfers | 8% | Section 43, Section 44 |
| ROPA | 8% | Section 30 |
Using the useComplianceScore() Hook
useComplianceScore() is the reactive version of getComplianceScore(). It memoises the report and re-computes it whenever the input changes, making it ideal for a live compliance dashboard. It requires a { input } argument — it does not read state from context automatically.
'use client';
import { useState } from 'react';
import { useComplianceScore } from '@tantainnovative/ndpr-toolkit';
import type { ComplianceInput } from '@tantainnovative/ndpr-toolkit';
const initialInput: ComplianceInput = {
consent: {
hasConsentMechanism: true,
hasPurposeSpecification: true,
hasWithdrawalMechanism: true,
hasMinorProtection: false,
consentRecordsRetained: true,
},
dsr: {
hasRequestMechanism: true,
supportsAccess: true,
supportsRectification: true,
supportsErasure: false,
supportsPortability: false,
supportsObjection: false,
responseTimelineDays: 14,
},
dpia: {
conductedForHighRisk: true,
documentedRisks: true,
mitigationMeasures: false,
},
breach: {
hasNotificationProcess: true,
notifiesWithin72Hours: true,
hasRiskAssessment: false,
hasRecordKeeping: true,
},
policy: {
hasPrivacyPolicy: true,
isPubliclyAccessible: true,
lastUpdated: '2024-11-01',
coversAllSections: true,
},
lawfulBasis: {
documentedForAllProcessing: true,
hasLegitimateInterestAssessment: false,
},
crossBorder: {
hasTransferMechanisms: true,
adequacyAssessed: true,
ndpcApprovalObtained: false,
},
ropa: {
maintained: true,
includesAllProcessing: false,
lastReviewed: '2024-10-15',
},
};
export function ComplianceWidget() {
const [input] = useState<ComplianceInput>(initialInput);
// Pass { input } — the hook does NOT read from context
const report = useComplianceScore({ input });
const { score, rating, recommendations, modules } = report;
const ratingColor =
rating === 'excellent' ? 'text-green-600' :
rating === 'good' ? 'text-blue-600' :
rating === 'needs-work'? 'text-yellow-600' :
'text-red-600';
return (
<div className="p-6 rounded-xl border">
<div className="flex items-center gap-4 mb-4">
<span className="text-5xl font-bold">{score}</span>
<span className="text-2xl font-semibold text-gray-500">/ 100</span>
<span className={ratingColor}>
{rating}
</span>
</div>
<h3 className="font-semibold mb-2">Top Recommendations</h3>
<ul className="space-y-2">
{recommendations.slice(0, 3).map((rec) => (
<li key={rec.key} className="text-sm">
<span className="font-medium">[{rec.priority.toUpperCase()}]</span>{' '}
{rec.recommendation}{' '}
<span className="text-gray-500">({rec.ndpaSection})</span>
</li>
))}
</ul>
<h3 className="font-semibold mt-4 mb-2">Module Scores</h3>
<ul className="space-y-1">
{Object.values(modules).map((mod) => (
<li key={mod.name} className="text-sm flex justify-between">
<span className="capitalize">{mod.name}</span>
<span>{mod.score}/100</span>
</li>
))}
</ul>
</div>
);
}The NDPRDashboard Component
For teams that want a complete compliance overview without building a custom UI, the toolkit ships a ready-made NDPRDashboard component. It renders a score ring, per-module cards with progress bars, and a prioritised recommendations list — all driven by a ComplianceReport you pass in as a prop.
Props
interface NDPRDashboardProps {
/** Compliance report produced by getComplianceScore() — required */
report: ComplianceReport;
/** Dashboard heading. Defaults to "NDPA Compliance Dashboard" */
title?: string;
/** Show/hide the recommendations list. Defaults to true */
showRecommendations?: boolean;
/** Maximum number of recommendations to render. Defaults to 5 */
maxRecommendations?: number;
/** Per-section class name overrides for custom styling */
classNames?: NDPRDashboardClassNames;
/** Strip all default classes so you can style from scratch */
unstyled?: boolean;
}Basic usage
Call getComplianceScore() with your ComplianceInput, then pass the resulting report to NDPRDashboard:
'use client';
import { getComplianceScore, NDPRDashboard } from '@tantainnovative/ndpr-toolkit';
const report = getComplianceScore({
consent: {
hasConsentMechanism: true,
hasPurposeSpecification: true,
hasWithdrawalMechanism: true,
hasMinorProtection: false,
consentRecordsRetained: true,
},
dsr: {
hasRequestMechanism: true,
supportsAccess: true,
supportsRectification: true,
supportsErasure: false,
supportsPortability: false,
supportsObjection: false,
responseTimelineDays: 14,
},
dpia: {
conductedForHighRisk: true,
documentedRisks: true,
mitigationMeasures: false,
},
breach: {
hasNotificationProcess: true,
notifiesWithin72Hours: true,
hasRiskAssessment: false,
hasRecordKeeping: true,
},
policy: {
hasPrivacyPolicy: true,
isPubliclyAccessible: true,
lastUpdated: '2024-11-01',
coversAllSections: true,
},
lawfulBasis: {
documentedForAllProcessing: true,
hasLegitimateInterestAssessment: false,
},
crossBorder: {
hasTransferMechanisms: true,
adequacyAssessed: true,
ndpcApprovalObtained: false,
},
ropa: {
maintained: true,
includesAllProcessing: false,
lastReviewed: '2024-10-15',
},
});
export default function ComplianceDashboardPage() {
return (
<main className="max-w-4xl mx-auto py-12 px-4">
<NDPRDashboard report={report} />
</main>
);
}Controlling what renders
// Hide recommendations and change the title
<NDPRDashboard
report={report}
title="Q2 Compliance Review"
showRecommendations={false}
/>
// Show at most 3 recommendations
<NDPRDashboard
report={report}
maxRecommendations={3}
/>Custom styling with classNames
Every visual section accepts a class name override via the classNames prop. Pass unstyled to strip all defaults and style from scratch:
<NDPRDashboard
report={report}
classNames={{
root: 'bg-card border border-border rounded-2xl p-8',
moduleCard: 'bg-muted rounded-lg p-4',
recommendationItem: 'border border-border rounded-lg p-3',
}}
/>Live dashboard with useComplianceScore()
Pair NDPRDashboard with useComplianceScore() for a dashboard that re-renders whenever your input state changes. Remember to pass { input }:
'use client';
import { useState } from 'react';
import { useComplianceScore, NDPRDashboard } from '@tantainnovative/ndpr-toolkit';
import type { ComplianceInput } from '@tantainnovative/ndpr-toolkit';
export function LiveComplianceDashboard({ input }: { input: ComplianceInput }) {
// Must pass { input } — the hook does not read from context
const report = useComplianceScore({ input });
return <NDPRDashboard report={report} />;
}Preset: NDPRComplianceDashboard
The presets package ships NDPRComplianceDashboard, which combines getComplianceScore() and NDPRDashboard into a single drop-in component — ideal for admin dashboards:
import { NDPRComplianceDashboard } from '@tantainnovative/ndpr-toolkit/presets';
// Pass your ComplianceInput directly — no need to call getComplianceScore() yourself
<NDPRComplianceDashboard input={complianceInput} />Server-side Compliance Checks
getComplianceScore() has no browser dependencies — you can run it in a Node.js API route to generate reports server-side and persist them to a database or send them in a scheduled email:
// src/app/api/compliance-report/route.ts
import { getComplianceScore } from '@tantainnovative/ndpr-toolkit';
import type { ComplianceInput } from '@tantainnovative/ndpr-toolkit';
import { getCurrentModuleState } from '@/lib/compliance-state';
import { NextResponse } from 'next/server';
export async function GET() {
// fetch your saved ComplianceInput from DB — must match the ComplianceInput shape
const input: ComplianceInput = await getCurrentModuleState();
const report = getComplianceScore(input);
return NextResponse.json(report);
}