Volume 2: Organizational Intelligence Platforms

Pattern 23: Triggered Interventions

Intent

Establish event-driven intervention system that instantly detects critical conditions and automatically initiates appropriate responses with millisecond-to-minute latency, preventing disasters through early detection and immediate action rather than waiting for scheduled checks or human discovery.

Also Known As

  • Event-Driven Interventions
  • Real-Time Response System
  • Trigger-Action Rules
  • Conditional Automation
  • Alert-Based Interventions

Problem

Waiting for scheduled checks means disasters happen in the gap.

The polling problem:

Current approach: System checks families every night at midnight - Midnight check: Martinez family engagement score = 72 (OK) - 3am: Martinez family's payment bounces (bank account overdrawn) - 8am: Martinez family tries portal login, gets "payment failed" error, feels embarrassed - 9am: Martinez family gets angry, starts looking at other co-ops - 11:59pm: Next scheduled check runs, detects problem - 24 hours too late - family already decided to leave

The human discovery problem:

Sarah manually reviews dashboard weekly - Monday: Checks reports, everything looks fine - Tuesday: Johnson family's email starts bouncing (changed address, didn't update) - Wednesday-Friday: System sends 3 more emails, all bounce - Next Monday: Sarah notices 4 bounced emails - 7 days too late - missed critical communication window

The batch processing problem:

System generates "at-risk" list weekly - Week 1: Chen family engagement = 68 (not flagged) - Tuesday Week 2: Chen family engagement drops to 42 (sudden decline) - Sunday Week 2: Weekly report runs, flags Chen family - 5 days too late - problem festered unnoticed

What happens in the gap: - Payment issues escalate (overdraft fees, angry parents) - Communication breaks down (bounced emails go unnoticed) - Engagement crashes (sudden drops missed) - Opportunities lost (positive moments not celebrated) - Families make decisions (choose to leave before we respond)

The fundamental problem: Time matters

For negative events: - Payment bounced → Need response within HOURS (before embarrassment → anger) - Email bounce → Need response within HOURS (before missing critical messages) - Engagement cliff → Need response within DAYS (before family disengages completely)

For positive events: - First volunteer signup → Need response within MINUTES (strike while iron is hot!) - Referral made → Need response within HOURS (thank them immediately!) - Payment early → Need response within HOURS (positive reinforcement!)

What we need: Event-driven architecture

Instead of "check everything periodically," use "react instantly to events":

Event happens → Trigger evaluates → Intervention executes
(milliseconds to minutes, not hours to days)

Examples:

WHEN payment_bounce DETECTED
  → IMMEDIATELY send friendly "heads up" email
  → CREATE urgent task for coordinator
  → PAUSE any aggressive collection messages
  → WITHIN 1 hour of bounce
WHEN engagement_drops_30_points_in_7_days DETECTED
  → IMMEDIATELY flag for coordinator
  → SEND caring "we miss you" message
  → SCHEDULE call for tomorrow
  → WITHIN 15 minutes of detection
WHEN first_volunteer_signup DETECTED
  → IMMEDIATELY send thank you email
  → NOTIFY coordinator (for personal follow-up)
  → ADD to "active volunteers" cohort
  → WITHIN 5 minutes of signup

Without triggered interventions: - React in hours/days instead of seconds/minutes - Miss critical windows (anger, disengagement, excitement) - Discover problems after damage done - Can't respond to positive events (no positive reinforcement)

With triggered interventions: - React in seconds/minutes (real-time) - Catch problems at inception (before escalation) - Capitalize on positive moments (immediate reinforcement) - Prevent disasters through early intervention

Context

When this pattern applies:

  • Time-sensitive situations exist (minutes/hours matter)
  • Events can be detected as they occur
  • Appropriate interventions can be defined per event type
  • System can act autonomously (with oversight)
  • Real-time/near-real-time response adds value

When this pattern may not be needed:

  • Batch processing acceptable (daily/weekly fine)
  • No time-sensitive events
  • All interventions require human judgment
  • Event detection infrastructure not available

Forces

Competing concerns:

1. Immediate vs Thoughtful - Immediate = fast response, prevent escalation - Thoughtful = consider context, avoid over-reaction - Balance: Immediate for critical, delayed for complex

2. Automated vs Human - Automated = instant, scalable, consistent - Human = judgment, empathy, flexibility - Balance: Automate simple, alert human for complex

3. Sensitive vs Robust - Sensitive = catch everything (false positives) - Robust = only true issues (miss some) - Balance: Tune thresholds per trigger type

4. Simple vs Complex Triggers - Simple = single event (payment bounced) - Complex = patterns (3 emails bounced in 7 days) - Balance: Both - simple for speed, complex for accuracy

5. Proactive vs Reactive - Proactive = predict and prevent - Reactive = respond after event - Balance: Both - predict with ML, respond to actuals

Solution

Build comprehensive trigger-action system with:

1. Trigger Types

A. Single Event Triggers:

WHEN payment_bounced
WHEN email_bounced
WHEN login_failed_3_times
WHEN volunteer_signup

B. Threshold Triggers:

WHEN engagement_score < 40
WHEN days_since_last_login > 30
WHEN bounced_email_count >= 3

C. Pattern Triggers:

WHEN engagement_drops_30_points_in_7_days
WHEN 3_consecutive_payments_late
WHEN no_communication_in_21_days

D. Combination Triggers:

WHEN (engagement_score < 50) AND (payment_overdue) AND (no_login_14_days)
// Multiple conditions must be true

E. Predictive Triggers:

WHEN ml_prediction(withdrawal_risk) > 0.85 AND confidence > 80
// From Pattern 12 + 13

2. Intervention Matching

TriggerCondition → EvaluateContext → SelectIntervention → Execute

3. Priority Levels

  • P1 Critical: Execute immediately (seconds)
  • Payment bounced, safety concern, system error

  • P2 High: Execute within minutes

  • Email bounced, engagement cliff, communication breakdown

  • P3 Medium: Execute within hours

  • Low engagement, inactive, approaching threshold

  • P4 Low: Execute within day

  • Positive reinforcement, gentle nudges

4. Deduplication

Prevent same trigger firing repeatedly:

// Don't send "payment bounced" email 10 times if payment bounces 10 times
if (sentSameInterventionInLast24Hours) {
  skip();
}

5. Context Awareness

Same trigger, different intervention based on context:

WHEN payment_bounced
  IF first_time_ever → Send friendly "heads up"
  IF happened_before_paid_quickly → Send reminder
  IF chronic_payment_issues → Create urgent coordinator task

Structure

Trigger System Tables

-- Define trigger rules
CREATE TABLE intervention_triggers (
  trigger_id INT PRIMARY KEY IDENTITY(1,1),

  trigger_name VARCHAR(200) NOT NULL,
  description NVARCHAR(1000),

  -- Trigger type
  trigger_type VARCHAR(100),  -- 'single_event', 'threshold', 'pattern', 'combination', 'predictive'

  -- Condition
  condition_definition NVARCHAR(MAX) NOT NULL,  -- JSON or SQL expression

  -- Evaluation
  evaluation_frequency VARCHAR(50),  -- 'real_time', 'every_minute', 'every_5_minutes', 'hourly'

  -- Priority
  priority INT DEFAULT 3,  -- 1=critical, 2=high, 3=medium, 4=low

  -- Deduplication
  dedupe_window_hours INT DEFAULT 24,

  -- Intervention
  intervention_action_type VARCHAR(100),  -- 'email', 'sms', 'task', 'workflow', 'alert'
  intervention_template_id INT,
  intervention_workflow_id INT,

  -- Control
  active BIT DEFAULT 1,
  created_date DATETIME2 DEFAULT GETDATE(),

  CONSTRAINT UQ_trigger_name UNIQUE (trigger_name)
);

-- Track trigger evaluations
CREATE TABLE trigger_evaluations (
  evaluation_id INT PRIMARY KEY IDENTITY(1,1),
  trigger_id INT NOT NULL,

  evaluated_at DATETIME2 DEFAULT GETDATE(),

  -- Result
  condition_met BIT NOT NULL,
  entity_id INT,  -- family_id or other entity

  -- Context
  trigger_context NVARCHAR(MAX),  -- JSON: relevant data at time of trigger

  -- Action taken
  intervention_executed BIT DEFAULT 0,
  intervention_skipped_reason NVARCHAR(500),

  CONSTRAINT FK_evaluation_trigger FOREIGN KEY (trigger_id)
    REFERENCES intervention_triggers(trigger_id)
);

-- Log executed interventions
CREATE TABLE triggered_interventions (
  intervention_id INT PRIMARY KEY IDENTITY(1,1),
  trigger_id INT NOT NULL,
  evaluation_id INT NOT NULL,

  family_id INT NOT NULL,

  -- Execution
  triggered_at DATETIME2 DEFAULT GETDATE(),
  executed_at DATETIME2,

  -- Action
  action_type VARCHAR(100),
  action_details NVARCHAR(MAX),  -- JSON

  -- Result
  execution_status VARCHAR(50),  -- 'pending', 'executing', 'completed', 'failed'
  execution_result NVARCHAR(MAX),

  -- Deduplication tracking
  dedupe_key VARCHAR(500),  -- Combination of trigger + family + timeframe

  CONSTRAINT FK_intervention_trigger FOREIGN KEY (trigger_id)
    REFERENCES intervention_triggers(trigger_id),
  CONSTRAINT FK_intervention_family FOREIGN KEY (family_id)
    REFERENCES families(family_id)
);

-- Index for deduplication checks
CREATE INDEX IX_intervention_dedupe ON triggered_interventions(dedupe_key, triggered_at);

-- Track trigger effectiveness
CREATE TABLE trigger_effectiveness (
  effectiveness_id INT PRIMARY KEY IDENTITY(1,1),
  trigger_id INT NOT NULL,

  -- Performance
  times_triggered INT DEFAULT 0,
  times_executed INT DEFAULT 0,
  times_successful INT DEFAULT 0,  -- Resulted in desired outcome

  success_rate DECIMAL(5,2),

  -- Timing
  avg_response_time_minutes INT,

  calculation_date DATE,

  CONSTRAINT FK_effectiveness_trigger FOREIGN KEY (trigger_id)
    REFERENCES intervention_triggers(trigger_id)
);

Implementation

Trigger Engine

class TriggerEngine {
  constructor(db, eventBus) {
    this.db = db;
    this.eventBus = eventBus;  // Event emitter for real-time events
    this.activeTriggers = new Map();
  }

  async start() {
    console.log('Trigger Engine starting...');

    // Load all active triggers
    await this.loadTriggers();

    // Subscribe to real-time events
    this.subscribeToEvents();

    // Start periodic evaluation for non-real-time triggers
    this.startPeriodicEvaluation();
  }

  async loadTriggers() {
    const triggers = await this.db.query(`
      SELECT * FROM intervention_triggers WHERE active = 1
    `);

    for (const trigger of triggers) {
      this.activeTriggers.set(trigger.trigger_id, trigger);

      if (trigger.evaluation_frequency === 'real_time') {
        this.setupRealTimeTrigger(trigger);
      }
    }

    console.log(`Loaded ${triggers.length} active triggers`);
  }

  subscribeToEvents() {
    // Subscribe to real-time events from event bus
    this.eventBus.on('payment.bounced', (event) => this.handleEvent('payment_bounced', event));
    this.eventBus.on('email.bounced', (event) => this.handleEvent('email_bounced', event));
    this.eventBus.on('user.login_failed', (event) => this.handleEvent('login_failed', event));
    this.eventBus.on('volunteer.signed_up', (event) => this.handleEvent('volunteer_signup', event));
    this.eventBus.on('referral.made', (event) => this.handleEvent('referral_made', event));
    this.eventBus.on('payment.received_early', (event) => this.handleEvent('payment_early', event));

    // Subscribe to metric updates (from Pattern 6, 7, 10)
    this.eventBus.on('metrics.updated', (event) => this.handleMetricsUpdate(event));
  }

  setupRealTimeTrigger(trigger) {
    // Real-time triggers are handled via event subscriptions
    console.log(`Real-time trigger active: ${trigger.trigger_name}`);
  }

  async handleEvent(eventType, eventData) {
    // Find triggers that match this event type
    const matchingTriggers = Array.from(this.activeTriggers.values())
      .filter(t => {
        const condition = JSON.parse(t.condition_definition);
        return condition.event_type === eventType;
      });

    for (const trigger of matchingTriggers) {
      await this.evaluateTrigger(trigger, eventData);
    }
  }

  async handleMetricsUpdate(event) {
    // Check threshold and pattern triggers when metrics update
    const { family_id, metric_type, new_value, old_value } = event;

    // Threshold triggers
    await this.checkThresholdTriggers(family_id, metric_type, new_value);

    // Pattern triggers (e.g., drops 30 points in 7 days)
    await this.checkPatternTriggers(family_id, metric_type, new_value, old_value);
  }

  async evaluateTrigger(trigger, context) {
    try {
      // Parse condition
      const condition = JSON.parse(trigger.condition_definition);

      // Check if condition met
      const conditionMet = await this.evaluateCondition(condition, context);

      // Log evaluation
      const evalResult = await this.db.query(`
        INSERT INTO trigger_evaluations (
          trigger_id,
          condition_met,
          entity_id,
          trigger_context
        )
        OUTPUT INSERTED.evaluation_id
        VALUES (?, ?, ?, ?)
      `, [
        trigger.trigger_id,
        conditionMet ? 1 : 0,
        context.family_id,
        JSON.stringify(context)
      ]);

      const evaluationId = evalResult[0].evaluation_id;

      if (conditionMet) {
        // Check deduplication
        const shouldExecute = await this.checkDeduplication(
          trigger,
          context.family_id
        );

        if (shouldExecute) {
          await this.executeIntervention(trigger, context, evaluationId);
        } else {
          await this.db.query(`
            UPDATE trigger_evaluations
            SET intervention_skipped_reason = 'deduplicated'
            WHERE evaluation_id = ?
          `, [evaluationId]);
        }
      }

    } catch (error) {
      console.error(`Error evaluating trigger ${trigger.trigger_name}:`, error);
    }
  }

  async evaluateCondition(condition, context) {
    switch (condition.type) {
      case 'simple_match':
        return condition.event_type === context.event_type;

      case 'threshold':
        return this.evaluateThreshold(condition, context);

      case 'pattern':
        return await this.evaluatePattern(condition, context);

      case 'combination':
        return await this.evaluateCombination(condition, context);

      case 'predictive':
        return await this.evaluatePredictive(condition, context);

      default:
        return false;
    }
  }

  evaluateThreshold(condition, context) {
    const value = context[condition.field];

    switch (condition.operator) {
      case '<': return value < condition.threshold;
      case '>': return value > condition.threshold;
      case '<=': return value <= condition.threshold;
      case '>=': return value >= condition.threshold;
      case '==': return value == condition.threshold;
      default: return false;
    }
  }

  async evaluatePattern(condition, context) {
    // Example: engagement dropped 30 points in 7 days
    if (condition.pattern === 'metric_drop') {
      const history = await this.db.query(`
        SELECT metric_value, calculation_date
        FROM family_engagement_metrics_history
        WHERE family_id = ?
          AND calculation_date >= DATEADD(DAY, -?, GETDATE())
        ORDER BY calculation_date DESC
      `, [context.family_id, condition.days]);

      if (history.length >= 2) {
        const drop = history[0].metric_value - history[history.length - 1].metric_value;
        return drop >= condition.drop_amount;
      }
    }

    return false;
  }

  async evaluateCombination(condition, context) {
    // Evaluate all sub-conditions
    const results = await Promise.all(
      condition.conditions.map(c => this.evaluateCondition(c, context))
    );

    // Combine based on operator
    if (condition.operator === 'AND') {
      return results.every(r => r);
    } else if (condition.operator === 'OR') {
      return results.some(r => r);
    }

    return false;
  }

  async evaluatePredictive(condition, context) {
    // Check ML prediction (from Pattern 12, 13)
    const prediction = await this.db.query(`
      SELECT 
        predicted_probability,
        confidence_score
      FROM ml_predictions
      WHERE family_id = ?
        AND prediction_type = ?
      ORDER BY prediction_date DESC
      LIMIT 1
    `, [context.family_id, condition.prediction_type]);

    if (prediction.length > 0) {
      const pred = prediction[0];
      return (
        pred.predicted_probability > condition.probability_threshold &&
        pred.confidence_score > condition.confidence_threshold
      );
    }

    return false;
  }

  async checkDeduplication(trigger, familyId) {
    // Create deduplication key
    const dedupeKey = `${trigger.trigger_id}_${familyId}`;
    const windowHours = trigger.dedupe_window_hours || 24;

    // Check if same intervention sent recently
    const recent = await this.db.query(`
      SELECT intervention_id
      FROM triggered_interventions
      WHERE dedupe_key = ?
        AND triggered_at >= DATEADD(HOUR, -?, GETDATE())
    `, [dedupeKey, windowHours]);

    return recent.length === 0;
  }

  async executeIntervention(trigger, context, evaluationId) {
    console.log(`Executing intervention for trigger: ${trigger.trigger_name}`);

    // Create intervention record
    const dedupeKey = `${trigger.trigger_id}_${context.family_id}`;

    const result = await this.db.query(`
      INSERT INTO triggered_interventions (
        trigger_id,
        evaluation_id,
        family_id,
        action_type,
        execution_status,
        dedupe_key
      )
      OUTPUT INSERTED.intervention_id
      VALUES (?, ?, ?, ?, 'pending', ?)
    `, [
      trigger.trigger_id,
      evaluationId,
      context.family_id,
      trigger.intervention_action_type,
      dedupeKey
    ]);

    const interventionId = result[0].intervention_id;

    // Execute based on priority
    const priority = trigger.priority || 3;

    if (priority === 1) {
      // Critical: Execute immediately
      await this.performIntervention(interventionId, trigger, context);
    } else {
      // Queue for near-term execution
      setTimeout(() => {
        this.performIntervention(interventionId, trigger, context);
      }, this.getPriorityDelay(priority));
    }
  }

  getPriorityDelay(priority) {
    // Convert priority to delay in milliseconds
    const delays = {
      1: 0,           // Immediate
      2: 60000,       // 1 minute
      3: 300000,      // 5 minutes
      4: 900000       // 15 minutes
    };

    return delays[priority] || delays[3];
  }

  async performIntervention(interventionId, trigger, context) {
    try {
      await this.db.query(`
        UPDATE triggered_interventions
        SET execution_status = 'executing', executed_at = GETDATE()
        WHERE intervention_id = ?
      `, [interventionId]);

      let result;

      switch (trigger.intervention_action_type) {
        case 'email':
          result = await this.sendEmail(trigger, context);
          break;

        case 'sms':
          result = await this.sendSMS(trigger, context);
          break;

        case 'task':
          result = await this.createTask(trigger, context);
          break;

        case 'workflow':
          result = await this.startWorkflow(trigger, context);
          break;

        case 'alert':
          result = await this.sendAlert(trigger, context);
          break;

        default:
          throw new Error(`Unknown action type: ${trigger.intervention_action_type}`);
      }

      await this.db.query(`
        UPDATE triggered_interventions
        SET 
          execution_status = 'completed',
          execution_result = ?
        WHERE intervention_id = ?
      `, [JSON.stringify(result), interventionId]);

      // Update effectiveness tracking
      await this.updateTriggerEffectiveness(trigger.trigger_id, true);

    } catch (error) {
      console.error(`Intervention ${interventionId} failed:`, error);

      await this.db.query(`
        UPDATE triggered_interventions
        SET 
          execution_status = 'failed',
          execution_result = ?
        WHERE intervention_id = ?
      `, [JSON.stringify({ error: error.message }), interventionId]);

      await this.updateTriggerEffectiveness(trigger.trigger_id, false);
    }
  }

  async sendEmail(trigger, context) {
    const emailService = require('./email-service');
    const template = await this.getTemplate(trigger.intervention_template_id);

    const family = await this.getFamilyData(context.family_id);

    return await emailService.send({
      to: family.email,
      subject: this.renderTemplate(template.subject, family, context),
      html: this.renderTemplate(template.body, family, context)
    });
  }

  async sendSMS(trigger, context) {
    const smsService = require('./sms-service');
    const template = await this.getTemplate(trigger.intervention_template_id);

    const family = await this.getFamilyData(context.family_id);

    return await smsService.send({
      to: family.phone,
      message: this.renderTemplate(template.sms_text, family, context)
    });
  }

  async createTask(trigger, context) {
    const family = await this.getFamilyData(context.family_id);

    await this.db.query(`
      INSERT INTO coordinator_tasks (
        family_id,
        task_type,
        description,
        priority,
        due_date
      ) VALUES (?, 'triggered_intervention', ?, ?, DATEADD(DAY, 1, GETDATE()))
    `, [
      context.family_id,
      `${trigger.trigger_name}: ${family.family_name}`,
      trigger.priority === 1 ? 'critical' : 'high'
    ]);

    return { task_created: true };
  }

  async startWorkflow(trigger, context) {
    // Start workflow from Pattern 21
    const WorkflowTrigger = require('./workflow-trigger');
    const workflowTrigger = new WorkflowTrigger(this.db);

    const workflowId = await workflowTrigger.createWorkflowInstance(
      context.family_id,
      trigger.intervention_workflow_id,
      `trigger:${trigger.trigger_id}`
    );

    return { workflow_id: workflowId };
  }

  async sendAlert(trigger, context) {
    // Send alert to coordinator dashboard/mobile app
    await this.db.query(`
      INSERT INTO coordinator_alerts (
        alert_type,
        family_id,
        message,
        priority,
        acknowledged
      ) VALUES ('trigger', ?, ?, ?, 0)
    `, [
      context.family_id,
      `${trigger.trigger_name}`,
      trigger.priority
    ]);

    return { alert_sent: true };
  }

  async checkThresholdTriggers(familyId, metricType, newValue) {
    const triggers = Array.from(this.activeTriggers.values())
      .filter(t => {
        const condition = JSON.parse(t.condition_definition);
        return condition.type === 'threshold' && condition.field === metricType;
      });

    for (const trigger of triggers) {
      await this.evaluateTrigger(trigger, {
        family_id: familyId,
        [metricType]: newValue
      });
    }
  }

  async checkPatternTriggers(familyId, metricType, newValue, oldValue) {
    const triggers = Array.from(this.activeTriggers.values())
      .filter(t => {
        const condition = JSON.parse(t.condition_definition);
        return condition.type === 'pattern';
      });

    for (const trigger of triggers) {
      await this.evaluateTrigger(trigger, {
        family_id: familyId,
        metric_type: metricType,
        new_value: newValue,
        old_value: oldValue
      });
    }
  }

  async updateTriggerEffectiveness(triggerId, success) {
    await this.db.query(`
      MERGE INTO trigger_effectiveness AS target
      USING (SELECT ? AS trigger_id) AS source
      ON (target.trigger_id = source.trigger_id AND target.calculation_date = CAST(GETDATE() AS DATE))
      WHEN MATCHED THEN
        UPDATE SET
          times_executed = times_executed + 1,
          times_successful = times_successful + (CASE WHEN ? = 1 THEN 1 ELSE 0 END),
          success_rate = (CAST(times_successful AS FLOAT) / times_executed) * 100
      WHEN NOT MATCHED THEN
        INSERT (trigger_id, times_executed, times_successful, success_rate, calculation_date)
        VALUES (?, 1, ?, ?, CAST(GETDATE() AS DATE));
    `, [triggerId, success ? 1 : 0, triggerId, success ? 1 : 0, success ? 100 : 0]);
  }

  async getTemplate(templateId) {
    const template = await this.db.query(`
      SELECT * FROM message_templates WHERE template_id = ?
    `, [templateId]);

    return template[0];
  }

  async getFamilyData(familyId) {
    const family = await this.db.query(`
      SELECT * FROM families WHERE family_id = ?
    `, [familyId]);

    return family[0];
  }

  renderTemplate(template, family, context) {
    return template
      .replace(/\{\{family_name\}\}/g, family.family_name)
      .replace(/\{\{event_type\}\}/g, context.event_type || '');
  }

  startPeriodicEvaluation() {
    // For triggers that need periodic evaluation (every 5 minutes, hourly, etc.)
    setInterval(() => {
      this.evaluatePeriodicTriggers();
    }, 60000);  // Check every minute
  }

  async evaluatePeriodicTriggers() {
    // Implementation for periodic triggers
    // (Threshold checks, pattern detection that runs on schedule)
  }
}

module.exports = TriggerEngine;

Usage Example

// Define critical triggers

// 1. Payment Bounced (Critical - P1)
await db.query(`
  INSERT INTO intervention_triggers (
    trigger_name,
    description,
    trigger_type,
    condition_definition,
    evaluation_frequency,
    priority,
    intervention_action_type,
    intervention_template_id,
    dedupe_window_hours
  ) VALUES (
    'payment_bounced',
    'Immediate response when payment bounces',
    'single_event',
    ?,
    'real_time',
    1,  -- Critical
    'email',
    201,
    24
  )
`, [JSON.stringify({
  type: 'simple_match',
  event_type: 'payment_bounced'
})]);

// 2. Email Bounced 3 Times (High - P2)
await db.query(`
  INSERT INTO intervention_triggers (
    trigger_name,
    description,
    trigger_type,
    condition_definition,
    evaluation_frequency,
    priority,
    intervention_action_type
  ) VALUES (
    'email_bounced_3x',
    'Communication breakdown - multiple bounces',
    'threshold',
    ?,
    'real_time',
    2,
    'task'
  )
`, [JSON.stringify({
  type: 'threshold',
  field: 'bounced_email_count',
  operator: '>=',
  threshold: 3
})]);

// 3. Engagement Cliff (High - P2)
await db.query(`
  INSERT INTO intervention_triggers (
    trigger_name,
    description,
    trigger_type,
    condition_definition,
    evaluation_frequency,
    priority,
    intervention_action_type,
    intervention_workflow_id
  ) VALUES (
    'engagement_cliff',
    'Engagement dropped 30+ points in 7 days',
    'pattern',
    ?,
    'every_5_minutes',
    2,
    'workflow',
    5  -- Engagement recovery workflow
  )
`, [JSON.stringify({
  type: 'pattern',
  pattern: 'metric_drop',
  field: 'engagement_score',
  drop_amount: 30,
  days: 7
})]);

// 4. First Volunteer (Low - P4 - Positive!)
await db.query(`
  INSERT INTO intervention_triggers (
    trigger_name,
    description,
    trigger_type,
    condition_definition,
    evaluation_frequency,
    priority,
    intervention_action_type,
    intervention_template_id
  ) VALUES (
    'first_volunteer',
    'Thank you for first-time volunteer',
    'single_event',
    ?,
    'real_time',
    4,
    'email',
    301  -- Thank you template
  )
`, [JSON.stringify({
  type: 'simple_match',
  event_type: 'volunteer_signup'
})]);

// Start engine
const EventEmitter = require('events');
const eventBus = new EventEmitter();

const engine = new TriggerEngine(db, eventBus);
await engine.start();

// System emits events as they happen
eventBus.emit('payment.bounced', {
  family_id: 123,
  event_type: 'payment_bounced',
  amount: 450,
  reason: 'insufficient_funds'
});
// → Email sent within seconds!

eventBus.emit('volunteer.signed_up', {
  family_id: 456,
  event_type: 'volunteer_signup',
  event_name: 'Fall Festival',
  hours: 4
});
// → Thank you email sent within 5 minutes!

Variations

By Trigger Complexity

Simple Event Triggers: - Payment bounced, login failed, email bounced - Fast, easy to understand - 1:1 event to trigger

Pattern Triggers: - 3 consecutive late payments - Engagement drop over time - Requires historical analysis

Predictive Triggers: - ML model predicts high risk - Combines multiple signals - Most sophisticated

By Response Time

Immediate (Seconds): - Critical events (safety, errors) - Real-time processing required - Highest infrastructure cost

Near-Real-Time (Minutes): - Important but not critical - Batch micro-windows (1-5 min) - Good balance

Delayed (Hours/Days): - Lower priority - Can batch process - Lowest cost

By Action Type

Notify: - Send message (email, SMS, push) - Alert coordinator - Simple, fast

Create Task: - Human follow-up needed - Coordinator intervention - Bridges auto + human

Start Workflow: - Multi-step intervention - Uses Pattern 21 - Complex but powerful

Consequences

Benefits

1. Real-time response React in seconds/minutes, not hours/days.

2. Catch problems early Payment bounced → respond before family gets angry (1 hour vs 24 hours).

3. Never miss critical events Email bounce, engagement cliff, safety concern - all detected instantly.

4. Positive reinforcement First volunteer → immediate thank you (capitalize on excitement).

5. Prevent disasters Engagement cliff caught at 7 days (not 30 days later).

6. Scale infinitely Monitor 10,000 families with same infrastructure as 10.

7. Measurable effectiveness Track which triggers work (optimize over time).

Costs

1. Infrastructure complexity Event-driven architecture more complex than batch.

2. False positive risk Over-sensitive triggers → alert fatigue.

3. Immediate errors If trigger fires incorrectly, damage is instant.

4. Monitoring required Need dashboards to track trigger health.

5. Tuning burden Thresholds need ongoing adjustment.

6. Context loss Automatic systems may miss nuance.

Sample Code

Monitor trigger health:

async function getTriggerHealthDashboard() {
  const health = await db.query(`
    SELECT 
      it.trigger_name,
      it.priority,
      COUNT(ti.intervention_id) as times_fired,
      AVG(DATEDIFF(SECOND, ti.triggered_at, ti.executed_at)) as avg_execution_seconds,
      SUM(CASE WHEN ti.execution_status = 'failed' THEN 1 ELSE 0 END) as failures
    FROM intervention_triggers it
    LEFT JOIN triggered_interventions ti ON it.trigger_id = ti.trigger_id
      AND ti.triggered_at >= DATEADD(DAY, -7, GETDATE())
    WHERE it.active = 1
    GROUP BY it.trigger_name, it.priority
    ORDER BY it.priority ASC, times_fired DESC
  `);

  return health;
}

Known Uses

Homeschool Co-op Intelligence Platform - Payment bounce: 5-minute response (vs 24-hour before) - Email bounce 3x: Immediate coordinator alert - Engagement cliff: 15-minute detection, intervention within hour - First volunteer: Thank you within 5 minutes (95% positive feedback)

E-Commerce - Cart abandonment (15-minute trigger) - Payment failure (immediate retry + notification) - Fraud detection (instant blocking)

SaaS - User onboarding triggers (signup → welcome sequence) - Feature usage triggers (first use → tooltip) - Churn signals (activity drop → intervention)

Healthcare - Vital sign alerts (immediate for critical) - Medication adherence (missed dose → reminder) - Appointment no-show (immediate reschedule offer)

Financial Services - Fraud triggers (instant block) - Low balance alerts (real-time notification) - Payment due reminders (configurable timing)

Requires: - Pattern 1: Universal Event Log - events to trigger on - Pattern 21: Automated Workflow Execution - execute complex interventions

Triggered By: - Pattern 9: Early Warning Signals - detection creates trigger - Pattern 12: Risk Stratification - predictions trigger interventions - Pattern 17: Anomaly Detection - anomalies trigger alerts

Enables: - Pattern 22: Progressive Escalation - first step in sequence - Pattern 26: Feedback Loop - track what triggers work

Enhanced By: - Pattern 24: Template-Based Communication - triggered messages - Pattern 25: Multi-Channel Orchestration - coordinate response

References

Academic Foundations

  • Fowler, Martin (2005). "Event Sourcing." https://martinfowler.com/eaaDev/EventSourcing.html - Event-driven architecture patterns
  • Richardson, Chris (2018). Microservices Patterns. Manning. ISBN: 978-1617294549 - Event-driven architecture in microservices
  • Stopford, Ben (2018). Designing Event-Driven Systems. O'Reilly. ISBN: 978-1492038252 - Kafka and event streams
  • Hohpe, Gregor, and Bobby Woolf (2003). Enterprise Integration Patterns. Addison-Wesley. ISBN: 978-0321200686 - https://www.enterpriseintegrationpatterns.com/

Event-Driven Architecture

Practical Implementation

Trigger Patterns

  • Pattern 9: Early Warning Signals - Warnings trigger interventions
  • Pattern 15: Intervention Recommendation Engine - Recommend triggered actions
  • Pattern 21: Automated Workflow Execution - Execute triggered workflows
  • Pattern 22: Progressive Escalation Sequences - Triggers escalate interventions
  • Pattern 24: Webhooks & Event Streaming - Event delivery mechanisms
  • Volume 3, Pattern 24: Webhooks & Event Streaming - Form-triggered events

Tools & Services