Skip to content

πŸ“Š 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)

  1. Identify 50-100 highest-value questions
  2. Manually map to 4 frameworks (E8, ACSC, S180, Privacy)
  3. Build answer storage system (Supabase tables)
  4. Implement basic lookup-based reuse
  5. Add dependency system (simple if/then logic)
  6. Implement calculated questions (SQL aggregation)
  7. 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)

  1. Add ML-powered inference engine
  2. Implement AI question categorization
  3. Build predictive answering
  4. Add intelligent routing

Trigger: After 50 customers actively using the system + Question bank > 500 items

Phase 3: Post-MVP - Full Automation (Year 2+)

  1. AI-powered question generation
  2. Automatic framework mapping
  3. Dynamic question optimization
  4. Predictive answering

Trigger: Revenue supports ML infrastructure costs + 500+ questions in bank

Success Metrics (MVP)

  1. Efficiency Gains

  2. 80% reduction in duplicate questions

  3. 60% faster onboarding
  4. 90% answer reuse rate

  5. Quality Improvements

  6. 95% answer accuracy

  7. 100% framework coverage
  8. 30-day average freshness

  9. User Satisfaction

  10. "Never asks same thing twice"
  11. "Knows what I need"
  12. "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.