Production engineering guide for compliance training engineering covering patterns, implementation strategies, and operational best practices.
Compliance Training Engineering is the practice of designing, implementing, and maintaining systems that ensure individuals and teams within an organisation are aware of, educated on, and validated in their adherence to compliance requirements. It matters when regulations, policies, or standards—like GDPR, HIPAA, SOX, or internal security frameworks—require regular training, and when proof of training is not just a formality but a legal or audit-ready artifact. This engineering discipline ensures that training is not a manual, ad-hoc process but a repeatable, versioned, automated, and traceable workflow.
Onboarding new hires requires them to complete a set of mandatory compliance training modules before they can access systems, approve documents, or handle regulated data. The system must assign training, track progress, and confirm completion.
A typical onboarding flow begins with a new employee record in the HRIS (e.g., Workday, BambooHR, or a custom PostgreSQL database). When the employee’s status is updated to active, a service triggers a compliance training assignment via a webhook. The payload is a JSON object sent to https://training-engine.example.com/api/v1/assign:
{
"employee_id": "emp-12345",
"department": "engineering",
"role": "software-engineer",
"start_date": "2023-10-01",
"training_programs": [
"data-protection-basics",
"security-incident-response",
"access-control-policy"
]
}
The training engine responds with a 202 Accepted status and a Location header pointing to the assignment:
HTTP/1.1 202 Accepted
Location: https://training-engine.example.com/api/v1/assignments/12345
Content-Type: application/json
{
"assignment_id": "12345",
"employee_id": "emp-12345",
"status": "assigned",
"due_date": "2023-10-15",
"created_at": "2023-10-01T10:00:00Z"
}
The assignment is stored in a training_assignments table in the database:
CREATE TABLE training_assignments (
assignment_id UUID PRIMARY KEY,
employee_id UUID NOT NULL,
program_id TEXT NOT NULL,
due_date DATE NOT NULL,
status TEXT CHECK (status IN ('assigned', 'in-progress', 'completed', 'overdue')),
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
Each assignment links to a training_programs table that defines the content, duration, and required completion criteria:
-- training_programs table
id: text PRIMARY KEY,
title: text,
description: text,
duration_minutes: integer,
completion_criteria: jsonb,
created_at: timestamptz,
updated_at: timestamptz
The completion_criteria field may contain:
{
"required_modules": [
"intro-to-gdpr",
"data-mining-privacy",
"consent-lifecycle"
],
"passing_score": 80,
"time_spent": "45m",
"final_quiz": true,
"evidence_submission": true
}
A module is a training_module with a content_url pointing to a static HTML file or a video hosted on a CDN. The training engine serves the module via a GET /training/{program_id}/{module_id} endpoint that returns a 200 OK with a Content-Type: text/html and an HTML page containing embedded tracking scripts.
Progress tracking is done through a client-side event stream sent to a POST /training/events endpoint. Each interaction—play, pause, time elapsed, quiz answer, submission, and completion—generates an event:
{
"event_type": "module_viewed",
"employee_id": "emp-12345",
"program_id": "data-protection-basics",
"module_id": "intro-to-gdpr",
"timestamp": "2023-10-02T14:30:12Z",
"data": {
"duration_seconds": 124,
"viewed_percentage": 95
}
}
The backend processes these events asynchronously. A kafka topic named training.events receives the events, and a python consumer service processes them into a training_progress table:
CREATE TABLE training_progress (
employee_id UUID,
program_id TEXT,
module_id TEXT,
last_event_at TIMESTAMPTZ,
total_time_spent INTERVAL,
completion_status TEXT CHECK (completion_status IN ('not-started', 'in-progress', 'completed')),
updated_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (employee_id, program_id, module_id)
);
The system periodically checks for completions. A cron job runs every 15 minutes via systemctl:
0,15,30,45 * * * * training-engine /opt/training/bin/check-completions
The check-completions script queries the training_progress table and updates the training_assignments table when all criteria are met:
def check_completions():
query = """
SELECT a.employee_id, a.program_id, a.assignment_id
FROM training_assignments a
JOIN training_progress p ON a.employee_id = p.employee_id AND a.program_id = p.program_id
WHERE a.status = 'in-progress'
GROUP BY a.employee_id, a.program_id, a.assignment_id
HAVING COUNT(DISTINCT p.module_id) = (
SELECT COUNT(*) FROM training_programs p
WHERE p.id = a.program_id
)
AND SUM(p.total_time_spent) >= (
SELECT duration_minutes * INTERVAL '1 minute'
FROM training_programs
WHERE id = a.program_id
)
AND (
SELECT COUNT(*) FROM (
SELECT module_id FROM training_progress
WHERE employee_id = a.employee_id AND program_id = a.program_id
AND completion_status = 'completed'
) AS completed
) = (
SELECT COUNT(*) FROM training_modules
WHERE program_id = a.program_id
);
"""
for row in execute_query(query):
update_assignment_status(row.employee_id, row.program_id, 'completed', now())
When the status changes to completed, the system fires a training.completed event to Kafka and updates a training_certificates table with a certificate_url and issued_at timestamp.
Training content is versioned and managed in a Git repository. Each program is a directory under content/training/ with a config.yaml file defining metadata:
# content/training/data-protection-basics/config.yaml
title: "Data Protection Basics"
description: "Introduction to core principles of data protection under GDPR and internal standards."
version: "1.2"
author: "[email protected]"
created_at: "2023-09-15T08:00:00Z"
updated_at: "2023-10-01T12:00:00Z"
modules:
- id: intro-to-gdpr
title: "Introduction to GDPR"
content: "modules/intro-to-gdpr/index.html"
duration_minutes: 30
prerequisites: []
completion_criteria:
time_spent: "20m"
quiz: true
quiz_passing_score: 80
- id: data-mining-privacy
title: "Data Mining and Privacy"
content: "modules/data-mining-privacy/index.html"
duration_minutes: 45
prerequisites: ["intro-to-gdpr"]
completion_criteria:
time_spent: "35m"
quiz: true
quiz_passing_score: 85
A post-commit hook in the Git repository triggers a build-training-content script that:
pandoc and webpack.manifest.json file listing all modules and their metadata.aws s3 sync.The build script runs on git push:
#!/bin/bash
# .git/hooks/post-commit
set -e
echo "Building training content..."
# Compile modules
find content/training -name 'config.yaml' | while read config; do
dir=$(dirname "$config")
program_id=$(basename "$dir")
echo "Building $program_id..."
# Process each module
modules_dir="$dir/modules"
for module in "$modules_dir"/*; do
module_id=$(basename "$module")
html_file="$module/index.html"
if [[ -f "$html_file" ]]; then
# Add tracking script
sed -i 's|</head>|<script src="/tracking.js"></script></head>|g' "$html_file"
fi
done
# Generate manifest
jq -n \
--arg program "$program_id" \
--arg version "$(git log -1 --format=%H)" \
--arg date "$(date -I)" \
'{ program: $program, version: $version, date: $date, modules: [ inputs | .[] | { id: .id, title: .title, content: .content } ] }' \
< <(find "$modules_dir" -name "config.yaml" -exec cat {} \; | jq -s .) \
> "$dir/manifest.json"
done
# Deploy to S3
aws s3 sync content/training/ s3://training-content.example.com/ --delete
echo "Training content built and deployed."
The training engine reads the manifest.json file on startup and uses it to populate the training_programs and training_modules tables.
When a module is completed, the client sends a POST /training/complete event with the following shape:
{
"employee_id": "emp-12345",
"program_id": "data-protection-basics",
"module_id": "intro-to-gdpr",
"timestamp": "2023-10-03T16:22:10Z",
"data": {
"quiz_score": 88,
"time_spent_seconds": 185,
"evidence": {
"submission_id": "sub-7890",
"file_url": "https://files.example.com/submissions/emp-12345/gdpr-quiz.pdf",
"uploaded_at": "2023-10-03T16:21:55Z"
},
"final_quiz_passed": true
}
}
The system validates the completion by checking:
quiz_score is at least the passing_score defined in training_programs.time_spent_seconds exceeds the required duration.evidence includes a file upload with a file_url and uploaded_at timestamp.If all criteria pass, the system updates the training_progress table and triggers a module.completed event to Kafka. The event includes:
{
"event_type": "module.completed",
"employee_id": "emp-12345",
"program_id": "data-protection-basics",
"module_id": "intro-to-gdpr",
"timestamp": "2023-10-03T16:22:10Z",
"data": {
"quiz_score": 88,
"time_spent_seconds": 185,
"evidence_url": "https://files.example.com/submissions/emp-12345/gdpr-quiz.pdf"
}
}
The system also generates a PDF certificate using weasyprint and stores it in the training_certificates table with a certificate_url and issued_at.
module_viewed event is sent before the module.completed event. The system misses the final timestamp, leading to undercounted time spent.config.yaml file is updated but the manifest.json is not regenerated. The training engine uses old module metadata, and new prerequisites or updated durations are ignored.training.completed event is processed 30 minutes late, causing assignments to be marked as completed after the due date, but the due_date field is not updated in the training_assignments table.quiz_score in the event is a float, but the training_programs table expects an integer. A score of 88.5 is stored as 88, and a passing_score of 80 fails the module even though the user scored 88.5.evidence object is sent, but the file_url is malformed or the file is not available at the given URL. The system fails to validate the evidence, and the module is marked as in-progress even after the quiz and time criteria are met.The training engine exposes an API for auditors and compliance teams:
# Get all assignments for a department
curl -X GET https://training-engine.example.com/api/v1/assignments \
-H "Authorization: Bearer audit-token-12345" \
-H "X-Department: engineering" \
-G \
--data-urlencode "status=completed"
Response:
{
"assignments": [
{
"assignment_id": "12345",
"employee_id": "emp-12345",
"program_id": "data-protection-basics",
"status": "completed",
"due_date": "2023-10-15",
"completed_at": "2023-10-03T16:22:10Z",
"evidence_url": "https://files.example.com/submissions/emp-12345/gdpr-quiz.pdf"
}
]
}
Auditors can download certificates, validate quiz scores, and verify time spent. The system logs all actions and events, which are stored in a training_audit_log table with event_type, timestamp, actor, and details.
This engineering discipline ensures that training is not a box-ticking exercise but a living, verifiable, and actionable component of an organisation’s compliance infrastructure.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy