π Unified Question Bank: Answer Once, Use Everywhere¶
MVP Scope: Database structure + manual question-to-control mappings + simple reuse logic Post-MVP: AI categorization, ML inference, automated mapping (see sections marked "Post-MVP" below)
Concept Overview¶
The Unified Question Bank is GetCimple's foundational data structure that enables the "answer once, use everywhere" philosophy. By capturing information once during onboarding and reusing it across all compliance frameworks, we dramatically reduce user burden while improving data quality.
MVP Implementation: The UQB starts as a structured database with manually-defined mappings between questions, policies, and controls. The "intelligence" comes from the data structure itself, not from AI/ML.
Core Philosophy¶
Single Source of Truth: Every piece of compliance information should be captured exactly once, stored intelligently, and reused automatically across all contexts where it's relevant.
Question Architecture¶
Question Structure¶
interface UnifiedQuestion {
id: string // Unique identifier
question: string // Human-readable question
type: QuestionType // Input type
domain: ComplianceDomain // Primary domain
// Manual mappings (MVP)
mappings: {
frameworks: FrameworkMapping[] // Which frameworks use this
controls: ControlMapping[] // Which controls it satisfies
evidence: EvidenceMapping[] // What evidence it generates
}
// MVP features
dependencies: QuestionDependency[] // Questions that affect this
validations: ValidationRule[] // Data quality checks
// Post-MVP: AI-powered suggestions
suggestions?: SuggestionRule[] // AI-powered helpers (Post-MVP)
// Reusability
contexts: UsageContext[] // Where this appears
variations: QuestionVariation[] // Different phrasings
// Metadata
importance: 'critical' | 'high' | 'medium' | 'low'
frequency: 'onboarding' | 'quarterly' | 'annual' | 'ongoing'
lastUpdated: Date
updateTriggers: UpdateTrigger[] // What causes re-ask
}
Question Types¶
enum QuestionType {
BOOLEAN = 'boolean', // Yes/No
SINGLE_CHOICE = 'single_choice', // Radio buttons
MULTI_CHOICE = 'multi_choice', // Checkboxes
NUMBER = 'number', // Numeric input
DATE = 'date', // Date picker
TEXT = 'text', // Short text
LONG_TEXT = 'long_text', // Paragraph
FILE_UPLOAD = 'file_upload', // Evidence attachment
ENTITY_SELECT = 'entity_select', // Dropdown from DB
CALCULATED = 'calculated', // Derived from others
}
The Master Question Set¶
Core Identity Questions (Answer Once)¶
const IDENTITY_QUESTIONS: UnifiedQuestion[] = [
{
id: 'ORG_001',
question: "What is your organization's primary industry?",
type: 'single_choice',
domain: 'organizational',
mappings: {
frameworks: ['E8', 'ACSC', 'S180', 'Privacy'],
controls: ['risk-assessment', 'data-classification'],
evidence: ['industry-certification'],
},
contexts: ['onboarding', 'risk-assessment', 'board-reporting'],
},
{
id: 'ORG_002',
question: 'How many employees does your organization have?',
type: 'number',
domain: 'organizational',
mappings: {
frameworks: ['E8', 'Privacy'],
controls: ['access-control', 'training-requirements'],
evidence: ['organization-size'],
},
validations: [{ min: 1, max: 100000 }],
},
{
id: 'ORG_003',
question: 'Do you handle personal information of Australian residents?',
type: 'boolean',
domain: 'privacy',
mappings: {
frameworks: ['Privacy', 'ACSC'],
controls: ['privacy-act-compliance', 'data-protection'],
evidence: ['privacy-policy', 'data-handling-procedures'],
},
dependencies: [
{
if: true,
triggers: ['PRIV_001', 'PRIV_002', 'PRIV_003'], // Privacy questions
},
],
},
]
Framework-Spanning Questions¶
const SECURITY_QUESTIONS: UnifiedQuestion[] = [
{
id: 'SEC_001',
question: 'Is multi-factor authentication (MFA) mandatory for all users?',
type: 'boolean',
domain: 'security',
mappings: {
frameworks: ['E8', 'ACSC'],
controls: [
'E8.1', // Application control
'ACSC-Essential-1', // MFA
'ISM-Control-1173', // Strong authentication
],
evidence: ['mfa-policy', 'mfa-enrollment-report'],
},
variations: [
'Do all users have MFA enabled?',
'Is two-factor authentication required?',
'Have you implemented multi-factor authentication?',
],
},
{
id: 'SEC_002',
question: 'How often are security patches applied to critical systems?',
type: 'single_choice',
domain: 'security',
options: [
'Within 48 hours',
'Within 1 week',
'Within 1 month',
'Quarterly',
'No regular schedule',
],
mappings: {
frameworks: ['E8', 'ACSC'],
controls: [
'E8.2', // Patch applications
'ACSC-Essential-2', // Patching
'ISM-Control-1407', // Patch management
],
evidence: ['patching-policy', 'patch-reports'],
},
importance: 'critical',
suggestions: [
{
if: 'option >= 3',
suggest:
'Consider implementing automated patching for critical systems',
},
],
},
]
Calculated Questions (MVP: Simple SQL Aggregation)¶
MVP Implementation: Use simple SQL/database aggregation functions for calculated values.
// MVP: Simple database aggregation
async function calculateE8MaturityLevel(orgId: string): Promise<number> {
const { data } = await supabase
.from('e8_answers')
.select('maturity_level')
.eq('org_id', orgId)
// Simple average
const sum = data.reduce((acc, row) => acc + row.maturity_level, 0)
return sum / data.length
}
Post-MVP: Weighted Calculations
const CALCULATED_QUESTIONS: UnifiedQuestion[] = [
{
id: 'CALC_001',
question: 'Overall Essential Eight Maturity Level',
type: 'calculated',
domain: 'compliance',
calculation: {
formula: 'WEIGHTED_AVERAGE',
inputs: ['SEC_001', 'SEC_002', 'SEC_003' /* ... */],
weights: {
SEC_001: 0.15, // MFA is 15% of score
SEC_002: 0.15, // Patching is 15%
// ... other weights
},
},
mappings: {
frameworks: ['E8'],
controls: ['overall-maturity'],
evidence: ['maturity-calculation'],
},
},
]
Reuse Patterns¶
1. Cross-Framework Mapping (MVP)¶
class QuestionMapper {
// Map single answer to multiple framework requirements
mapAnswerToFrameworks(questionId: string, answer: any): FrameworkResult[] {
const question = this.getQuestion(questionId)
const results: FrameworkResult[] = []
for (const mapping of question.mappings.frameworks) {
results.push({
framework: mapping,
control: this.getControlForFramework(questionId, mapping),
status: this.calculateStatus(answer, mapping),
evidence: this.generateEvidence(questionId, answer, mapping),
})
}
return results
}
}
2. Context-Aware Presentation¶
class QuestionPresenter {
// Show question differently based on context
presentQuestion(
questionId: string,
context: UsageContext
): PresentedQuestion {
const question = this.getQuestion(questionId)
const previousAnswer = this.getPreviousAnswer(questionId)
if (previousAnswer && !this.requiresUpdate(question, previousAnswer)) {
return {
...question,
defaultValue: previousAnswer.value,
message: `Previously answered: ${previousAnswer.value}`,
allowEdit: true,
}
}
// Customize question based on context
return this.customizeForContext(question, context)
}
}
3. Evidence Generation¶
class EvidenceGenerator {
// Generate evidence from answers automatically
generateEvidenceFromAnswer(questionId: string, answer: any): Evidence[] {
const question = this.getQuestion(questionId)
const evidence: Evidence[] = []
for (const mapping of question.mappings.evidence) {
evidence.push({
type: mapping,
content: this.formatEvidence(answer, mapping),
frameworks: question.mappings.frameworks,
generatedFrom: questionId,
timestamp: new Date(),
})
}
return evidence
}
}
Implementation Patterns¶
Onboarding Flow¶
class SmartOnboarding {
async startOnboarding(organization: Organization) {
// 1. Ask identity questions first
const identityAnswers = await this.askQuestions(IDENTITY_QUESTIONS)
// 2. Determine relevant follow-up questions
const relevantQuestions = this.determineRelevantQuestions(identityAnswers)
// 3. Skip questions we can infer
const inferredAnswers = this.inferAnswers(identityAnswers)
// 4. Ask only necessary questions
const additionalAnswers = await this.askQuestions(
relevantQuestions.filter((q) => !inferredAnswers[q.id])
)
// 5. Generate comprehensive compliance profile
return this.generateComplianceProfile({
...identityAnswers,
...inferredAnswers,
...additionalAnswers,
})
}
}
Progressive Disclosure¶
class ProgressiveQuestioning {
async askInContext(context: string, existingAnswers: AnswerSet) {
// Only ask questions that:
// 1. Haven't been answered
// 2. Are relevant to current context
// 3. Can't be inferred from existing answers
const contextQuestions = this.getQuestionsForContext(context)
const unanswered = contextQuestions.filter(
(q) => !existingAnswers[q.id] && !this.canInfer(q, existingAnswers)
)
// Show how many we're skipping
if (contextQuestions.length > unanswered.length) {
this.notify(
`Skipping ${contextQuestions.length - unanswered.length} ` +
`questions already answered`
)
}
return this.askQuestions(unanswered)
}
}
Data Quality & Validation¶
Answer Validation¶
interface ValidationRule {
type: 'required' | 'format' | 'range' | 'dependency' | 'custom'
validate: (value: any, context?: any) => ValidationResult
message: string
}
const VALIDATION_RULES: Record<string, ValidationRule[]> = {
SEC_001: [
{
type: 'required',
validate: (value) => ({ valid: value !== null }),
message: 'MFA status is required for compliance',
},
],
ORG_002: [
{
type: 'range',
validate: (value) => ({ valid: value > 0 && value < 100000 }),
message: 'Employee count must be between 1 and 100,000',
},
],
}
Answer Freshness¶
interface UpdateTrigger {
type: 'time' | 'event' | 'dependency'
condition: any
action: 'prompt' | 'invalidate' | 'warn'
}
const UPDATE_TRIGGERS: Record<string, UpdateTrigger[]> = {
SEC_002: [
{
type: 'time',
condition: { months: 3 },
action: 'prompt',
message: 'Please confirm your patching schedule is still current',
},
],
ORG_002: [
{
type: 'event',
condition: 'significant_growth',
action: 'invalidate',
message: 'Organization size has changed significantly',
},
],
}
Reporting & Analytics¶
Unified Reporting¶
class UnifiedReporter {
generateBoardReport(period: string): BoardReport {
const answers = this.getAnswersForPeriod(period)
return {
sections: [
{
title: 'Essential Eight Compliance',
data: this.mapAnswersToFramework(answers, 'E8'),
source: this.getQuestionSources(answers, 'E8'),
},
{
title: 'Director Duties (S180)',
data: this.mapAnswersToFramework(answers, 'S180'),
source: this.getQuestionSources(answers, 'S180'),
},
],
metadata: {
answersUsed: Object.keys(answers).length,
lastUpdated: this.getLatestAnswerDate(answers),
dataQuality: this.assessDataQuality(answers),
},
}
}
}
Answer Analytics¶
interface AnswerAnalytics {
coverage: number // % of questions answered
freshness: number // % of answers < 3 months old
quality: number // % passing validation
reusability: number // Avg times each answer used
timeToComplete: number // Minutes to answer
}
class AnswerAnalyzer {
analyzeAnswerEfficiency(): AnswerAnalytics {
return {
coverage: this.calculateCoverage(),
freshness: this.calculateFreshness(),
quality: this.calculateQuality(),
reusability: this.calculateReusability(),
timeToComplete: this.calculateCompletionTime(),
}
}
}
Integration Patterns¶
Essential Eight (E8) Integration¶
The E8 assessment framework is a first-class citizen in the UQB, with specific patterns for role-based delegation and intelligent pre-fill:
interface E8Question extends UnifiedQuestion {
// E8-specific metadata
boardTranslation?: string // Plain English for directors
quickWin?: boolean // Can be answered quickly
estimatedTime?: string // Time to answer
// Role-based routing
primaryRole: 'board' | 'executive' | 'it_manager' | 'admin' | 'staff'
delegationPath: string[] // e.g., ['board', 'executive', 'it_manager']
// E8-specific mappings
e8Mappings: {
strategy: E8Strategy // Which of 8 strategies
controls: string[] // ACSC control IDs
maturityImpact: Record<string, any> // ML0-ML3 impact
}
// Crossover intelligence
crossoverSources: CrossoverSource[]
}
interface CrossoverSource {
type: 'policy' | 'insurance' | 'assessment'
id: string
field?: string
confidence: number // 0-1 confidence score
}
Role-Based Question Routing¶
class E8QuestionRouter {
// Intelligently route questions based on role and delegation hierarchy
routeQuestions(questions: E8Question[], userRole: UserRole): RoutedQuestions {
const routed = {
assigned: [] as E8Question[],
delegated: [] as E8Question[],
completed: [] as E8Question[],
visibility: [] as E8Question[],
}
for (const question of questions) {
// Technical questions go to IT by default
if (question.primaryRole === 'it_manager' && userRole === 'board') {
routed.delegated.push({
...question,
delegatedTo: 'IT Manager',
reason: 'Technical implementation detail',
})
}
// Board only sees governance questions and summaries
else if (userRole === 'board' && question.primaryRole === 'board') {
routed.assigned.push(question)
}
// Show pre-filled questions as completed
else if (this.isPreFilled(question)) {
routed.completed.push({
...question,
preFilledFrom: this.getPreFillSource(question),
confidence: this.getConfidence(question),
})
}
}
return routed
}
// Calculate pre-fill confidence from crossover sources
getConfidence(question: E8Question): number {
if (!question.crossoverSources?.length) return 0
// Weighted average based on source type
const weights = {
policy: 0.9, // Policies are most reliable
insurance: 0.85, // Insurance answers are very reliable
assessment: 0.7, // Other assessments are somewhat reliable
}
return (
question.crossoverSources.reduce((acc, source) => {
return acc + weights[source.type] * source.confidence
}, 0) / question.crossoverSources.length
)
}
}
E8-UQB Pre-Fill Engine¶
class E8PreFillEngine {
// Pre-fill E8 questions from existing UQB answers
async preFillE8Assessment(orgId: string): Promise<E8PreFillResult> {
const e8Questions = await this.getE8Questions()
const existingAnswers = await this.uqb.getAnswers(orgId)
const preFilled: PreFilledQuestion[] = []
for (const e8q of e8Questions) {
// Check each crossover source
for (const source of e8q.crossoverSources) {
const answer = await this.findCrossoverAnswer(source, existingAnswers)
if (answer) {
preFilled.push({
questionId: e8q.id,
preFilledValue: answer.value,
source: {
type: source.type,
name: this.getSourceName(source),
confidence: source.confidence,
lastUpdated: answer.updatedAt,
},
requiresVerification: source.confidence < 0.9,
})
break // Use first available source
}
}
}
return {
totalQuestions: e8Questions.length,
preFilled: preFilled.length,
preFilledPercentage: (preFilled.length / e8Questions.length) * 100,
bySource: this.groupBySource(preFilled),
estimatedTimeSaved: this.calculateTimeSaved(preFilled),
}
}
}
Board Initiative Experience¶
class E8BoardExperience {
// Create the "board initiative" view of E8
async presentE8ToBoard(orgId: string): Promise<BoardE8View> {
const assessment = await this.getE8Assessment(orgId)
return {
// One number that matters
currentMaturityLevel: assessment.overallMaturity, // e.g., 0.5
// Context for decision
industryBenchmark: await this.getIndustryBenchmark(orgId),
regulatoryMinimum: this.getRegulatedMinimum(orgId),
// Clear options
targetOptions: [
{
level: 1,
label: 'Basic Protection',
cost: '$50k',
timeline: '6 months',
suitable: 'Small businesses without sensitive data',
},
{
level: 2,
label: 'Industry Standard',
cost: '$150k',
timeline: '9 months',
suitable: 'Most businesses with customer data',
recommended: true,
},
{
level: 3,
label: 'Advanced Protection',
cost: '$300k',
timeline: '18 months',
suitable: 'Large orgs, critical infrastructure',
},
],
// Delegation summary
workCompleted: {
byIT: '35 technical questions answered',
byManagement: '3 policy questions answered',
remaining: '2 governance decisions needed',
percentComplete: 95,
},
// Next steps are clear
boardActions: [
'Review current maturity level',
'Select target maturity level',
'Approve budget and timeline',
'Delegate implementation to management',
],
}
}
}
WhatsApp Quick Updates¶
class WhatsAppQuestionBot {
async handleUpdate(message: string, user: User) {
// Understand what they're updating
const intent = await this.parseIntent(message)
if (intent.type === 'update_answer') {
const question = this.findQuestion(intent.topic)
const currentAnswer = await this.getCurrentAnswer(question.id, user)
// Show current value and ask for update
await this.send(`Current ${question.question}: ${currentAnswer}`)
await this.send("What's the new value?")
// Update affects all frameworks
const newAnswer = await this.waitForResponse()
const impacts = await this.updateAnswer(question.id, newAnswer)
await this.send(`Updated! This affects: ${impacts.join(', ')}`)
}
}
}
API Access¶
// REST API for question bank
app.get('/api/questions', async (req, res) => {
const { context, framework, answered } = req.query
let questions = await questionBank.getQuestions({
context,
framework,
includeAnswered: answered === 'true',
})
// Include previous answers if requested
if (req.query.withAnswers) {
questions = await questionBank.enrichWithAnswers(questions, req.user)
}
res.json({ questions, stats: questionBank.getStats(questions) })
})
E8 Assessment Flow in UQB¶
Two-Phase Process¶
The E8 assessment leverages the UQB to create a streamlined board governance experience:
class E8AssessmentFlow {
// Phase 1: Discovery (IT/Management handles this)
async phase1Discovery(orgId: string): Promise<DiscoveryResult> {
// 1. Pre-fill from existing UQB data
const preFilled = await this.preFillEngine.preFillE8Assessment(orgId)
// 2. Route remaining questions by role
const remaining = await this.getUnansweredQuestions(orgId)
const routed = await this.router.routeQuestions(remaining, 'it_manager')
// 3. IT completes technical questions
const technicalAnswers = await this.collectAnswers(routed.assigned)
// 4. Calculate current maturity
const currentMaturity = await this.calculateMaturity(orgId)
return {
currentLevel: currentMaturity.overall, // e.g., 0.5
byStrategy: currentMaturity.strategies, // 8 individual scores
completedBy: {
it: routed.assigned.length,
preFilled: preFilled.length,
total: 40,
},
readyForBoard: true,
}
}
// Phase 2: Board Decision (Board sees this)
async phase2BoardDecision(orgId: string): Promise<BoardDecision> {
const discovery = await this.getDiscoveryResults(orgId)
// Present simple decision interface
const decision = await this.boardInterface.present({
currentLevel: discovery.currentLevel,
options: this.getMaturityOptions(orgId),
recommendation: this.getRecommendation(orgId),
})
// Board makes ONE decision
if (decision.approved) {
await this.createImplementationPlan({
targetLevel: decision.targetLevel,
timeline: decision.timeline,
budget: decision.budget,
delegatedTo: decision.assignee,
})
}
return decision
}
}
UQB Value in E8 Context¶
// Example: How UQB powers the E8 "pre-fill magic"
const e8Question = {
id: 'E8_MFA_001',
question: 'Is multi-factor authentication mandatory for all admin accounts?',
crossoverSources: [
{
type: 'policy',
id: 'password-authentication-policy',
field: 'mfa_admin_required',
confidence: 0.95,
},
{
type: 'insurance',
id: 'ins_112',
confidence: 0.95,
},
],
}
// UQB automatically finds and uses existing answer
const existingAnswer = await uqb.findAnswer({
policyId: 'password-authentication-policy',
field: 'mfa_admin_required',
})
if (existingAnswer) {
// E8 question is pre-filled with 95% confidence
e8Answers[e8Question.id] = {
value: existingAnswer.value,
source: 'Password Policy (approved 2024-03-15)',
confidence: 0.95,
needsVerification: false,
}
}
Implementation Strategy¶
Phase 1: MVP - Core Question Mapping (8-12 weeks)¶
- Identify 50-100 highest-value questions
- Manually map to 4 frameworks (E8, ACSC, S180, Privacy)
- Build answer storage system (Supabase tables)
- Implement basic lookup-based reuse
- Add dependency system (simple if/then logic)
- Implement calculated questions (SQL aggregation)
- Add validation layer (Zod schemas)
Success Metrics:
- 80% reduction in duplicate questions
- 60% faster onboarding
- 90% answer reuse rate
Phase 2: Post-MVP - Intelligent Expansion (After 50+ customers)¶
- Add ML-powered inference engine
- Implement AI question categorization
- Build predictive answering
- Add intelligent routing
Trigger: After 50 customers actively using the system + Question bank > 500 items
Phase 3: Post-MVP - Full Automation (Year 2+)¶
- AI-powered question generation
- Automatic framework mapping
- Dynamic question optimization
- Predictive answering
Trigger: Revenue supports ML infrastructure costs + 500+ questions in bank
Success Metrics (MVP)¶
-
Efficiency Gains
-
80% reduction in duplicate questions
- 60% faster onboarding
-
90% answer reuse rate
-
Quality Improvements
-
95% answer accuracy
- 100% framework coverage
-
30-day average freshness
-
User Satisfaction
- "Never asks same thing twice"
- "Knows what I need"
- "Makes compliance easy"
Post-MVP Future Enhancements¶
1. AI-Powered Intelligence (Post-MVP - Year 2+)¶
- Learn optimal question ordering
- Predict likely answers
- Suggest process improvements
Trigger: ML infrastructure + sufficient training data (1000+ question responses)
2. Industry Benchmarking (Post-MVP - After 100+ customers)¶
- Compare answers to peers
- Identify outliers
- Suggest best practices
Trigger: 100+ customers with permission to anonymize data
3. Regulatory Updates (Post-MVP - Ongoing)¶
- Auto-update question mappings
- Flag affected answers
- Prompt for revalidation
Trigger: Partnership with regulatory intelligence provider
Conclusion¶
The Unified Question Bank transforms compliance from repetitive questionnaires to structured data management. By capturing each piece of information once and reusing it everywhere through intelligent database design, we deliver a dramatically better user experience while ensuring higher data quality and compliance coverage.
MVP Philosophy: Start with a well-structured database and manual mappings. The power comes from the data model, not from AI. Add intelligence later when we have the data and revenue to support it.
Remember: Every question asked should make the system smarter by building our database of mappings, not just checking a box.