Volume 3: Human-System Collaboration

Appendix F: Canonical Event Schema for Form Interactions

Cross-Volume Reference Guide
Ensuring V3 Form Events Match V2 Event Log Structure


Purpose

This appendix defines the canonical event schema for form interactions that flows between Volume 3 (Input Layer) and Volume 2 (Intelligence Layer). Following this schema ensures:

  • Consistency: All form events use the same structure as V2 Pattern 1 (Universal Event Log)
  • Interoperability: Form events can be directly logged without transformation
  • Analyzability: V2 intelligence patterns can process form events like any other event
  • Traceability: Easy to track user journey from form interaction to system action

See: - V2 Pattern 1: Universal Event Log - Core event logging infrastructure - V3 Pattern 22: Event Stream Integration - How forms generate events


Base Event Structure

All events—whether from forms, emails, SMS, or system actions—share this core structure:

interface BaseEvent {
  // Required Core Fields
  interaction_id: number;           // Auto-generated, unique
  interaction_timestamp: DateTime;   // When event occurred (ISO 8601)
  interaction_type: string;          // Event type (see taxonomy below)

  // Entity References
  family_id?: number;                // Primary entity (required for most)
  student_id?: number;               // Optional related entity
  user_id?: number;                  // User who triggered (for logged-in forms)

  // Classification
  interaction_category?: string;     // High-level grouping (enrollment, payment, etc.)
  channel: string;                   // Where it happened (portal, mobile, api, etc.)

  // Outcome
  outcome?: string;                  // Result (completed, abandoned, error, etc.)

  // Flexible Context
  metadata: Record<string, any>;     // JSON object with event-specific details

  // Audit
  created_by?: string;               // System or user identifier
}

Form Event Taxonomy

Lifecycle Events

Events that track the overall form journey:

form_viewed

When: User opens/loads form
Indicates: Intent to interact
Example:

{
  "interaction_type": "form_viewed",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "user_id": 8472,
  "metadata": {
    "form_name": "student_enrollment",
    "form_version": "2024.3",
    "referrer": "/dashboard",
    "device_type": "desktop",
    "user_agent": "Mozilla/5.0..."
  }
}

form_started

When: User begins filling (first field interaction)
Indicates: Active engagement
Example:

{
  "interaction_type": "form_started",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "in_progress",
  "metadata": {
    "form_name": "student_enrollment",
    "form_version": "2024.3",
    "first_field": "student_name",
    "session_id": "sess_xyz789",
    "time_since_viewed_seconds": 14
  }
}

form_saved

When: User saves partial progress
Indicates: Complexity signal, intention to return
Example:

{
  "interaction_type": "form_saved",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "partial",
  "metadata": {
    "form_name": "student_enrollment",
    "completion_percentage": 62,
    "fields_completed": 18,
    "fields_total": 29,
    "last_section": "guardian_information",
    "time_spent_seconds": 847,
    "save_trigger": "manual_button_click"
  }
}

form_resumed

When: User returns to saved form
Indicates: Commitment signal
Example:

{
  "interaction_type": "form_resumed",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "metadata": {
    "form_name": "student_enrollment",
    "saved_at": "2024-12-20T15:30:00Z",
    "time_since_save_hours": 18,
    "completion_at_resume": 62,
    "resume_method": "email_link"
  }
}

form_submitted

When: User completes and submits form
Indicates: Success!
Example:

{
  "interaction_type": "form_submitted",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "completed",
  "metadata": {
    "form_name": "student_enrollment",
    "total_time_seconds": 1847,
    "session_count": 2,
    "validation_errors_encountered": 3,
    "help_views": 5,
    "saves_count": 1,
    "edit_count": 23,
    "submission_method": "submit_button"
  }
}

form_abandoned

When: User leaves without completing (detected by timeout or navigation)
Indicates: Friction signal
Example:

{
  "interaction_type": "form_abandoned",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "incomplete",
  "metadata": {
    "form_name": "student_enrollment",
    "completion_percentage": 62,
    "fields_completed": 18,
    "fields_total": 29,
    "last_field": "guardian_phone",
    "last_section": "guardian_information",
    "time_spent_seconds": 847,
    "validation_errors": 3,
    "help_views": 5,
    "abandon_trigger": "navigation_away",
    "abandon_destination": "/dashboard"
  }
}

Interaction Events

Events that track specific user actions within the form:

field_focused

When: User clicks into/tabs to field
Indicates: Attention, potential difficulty if focus time is long
Example:

{
  "interaction_type": "field_focused",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "guardian_address",
    "field_type": "textarea",
    "previous_field": "guardian_name",
    "tab_order": 12
  }
}

field_changed

When: User modifies field value
Indicates: Data entry, pattern of changes reveals confusion
Example:

{
  "interaction_type": "field_changed",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "student_grade",
    "field_type": "select",
    "old_value": "3",
    "new_value": "4",
    "edit_count": 2,
    "time_since_focus_seconds": 8,
    "change_trigger": "user_edit"
  }
}

field_blurred

When: User leaves field (tabs away, clicks elsewhere)
Indicates: Completion of field interaction
Example:

{
  "interaction_type": "field_blurred",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "student_birthdate",
    "field_type": "date",
    "final_value": "2015-03-15",
    "focus_duration_seconds": 12,
    "edit_count": 3,
    "next_field": "student_grade"
  }
}

validation_error

When: System detects invalid input
Indicates: User confusion, unclear requirements, or genuine error
Example:

{
  "interaction_type": "validation_error",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "error",
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "guardian_email",
    "field_type": "email",
    "submitted_value": "john.smith@",
    "error_type": "format",
    "error_code": "INVALID_EMAIL",
    "error_message": "Please enter a valid email address",
    "validation_rule": "email_format",
    "attempt_number": 2
  }
}

validation_success

When: Previously invalid field now valid
Indicates: User understood feedback and corrected
Example:

{
  "interaction_type": "validation_success",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "resolved",
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "guardian_email",
    "corrected_value": "john.smith@email.com",
    "previous_errors": 2,
    "time_to_resolve_seconds": 45
  }
}

help_viewed

When: User clicks help icon, tooltip, or info button
Indicates: Need for clarification
Example:

{
  "interaction_type": "help_viewed",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "learning_accommodations",
    "help_type": "tooltip",
    "help_content_id": "help_accommodations_2024",
    "help_text_preview": "List any learning accommodations...",
    "trigger": "icon_click"
  }
}

autofill_accepted

When: User accepts pre-populated or suggested value
Indicates: Intelligent default was helpful
Example:

{
  "interaction_type": "autofill_accepted",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "helpful",
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "guardian_address",
    "autofill_source": "previous_submission",
    "autofill_confidence": 0.95,
    "value": "123 Main St, Pittsburgh, PA 15213"
  }
}

autofill_rejected

When: User changes pre-populated value
Indicates: Default was wrong or outdated
Example:

{
  "interaction_type": "autofill_rejected",
  "interaction_category": "enrollment",
  "channel": "portal",
  "family_id": 12845,
  "outcome": "rejected",
  "metadata": {
    "form_name": "student_enrollment",
    "field_name": "guardian_phone",
    "autofill_source": "previous_submission",
    "suggested_value": "412-555-0100",
    "corrected_value": "412-555-0199",
    "reason": "user_edit"
  }
}

Integration with V2 Intelligence Patterns

Pattern 1: Universal Event Log

All form events flow directly into the interaction_log table:

INSERT INTO interaction_log (
  interaction_timestamp,
  family_id,
  user_id,
  interaction_type,
  interaction_category,
  channel,
  outcome,
  metadata,
  created_by
) VALUES (
  ?,  -- form event timestamp
  ?,  -- family_id
  ?,  -- user_id
  ?,  -- 'form_submitted', 'field_changed', etc.
  ?,  -- 'enrollment', 'payment', etc.
  ?,  -- 'portal', 'mobile', etc.
  ?,  -- 'completed', 'error', etc.
  ?,  -- JSON metadata
  ?   -- 'system' or user identifier
);

Pattern 16: Cohort Discovery & Analysis

Form events enable pattern discovery:

-- Find fields where users get stuck
SELECT 
  JSON_VALUE(metadata, '$.field_name') as field,
  COUNT(*) as abandonment_count,
  AVG(CAST(JSON_VALUE(metadata, '$.time_spent_seconds') AS INT)) as avg_time_spent
FROM interaction_log
WHERE interaction_type = 'form_abandoned'
  AND interaction_timestamp > DATEADD(month, -1, GETDATE())
GROUP BY JSON_VALUE(metadata, '$.field_name')
ORDER BY abandonment_count DESC;

Pattern 18: Cohort Analysis

Compare form completion by user segment:

-- Completion rates by device type
SELECT 
  JSON_VALUE(metadata, '$.device_type') as device,
  COUNT(CASE WHEN interaction_type = 'form_submitted' THEN 1 END) as completions,
  COUNT(CASE WHEN interaction_type = 'form_abandoned' THEN 1 END) as abandonments,
  CAST(COUNT(CASE WHEN interaction_type = 'form_submitted' THEN 1 END) AS FLOAT) / 
    COUNT(*) * 100 as completion_rate
FROM interaction_log
WHERE interaction_category = 'enrollment'
  AND interaction_type IN ('form_submitted', 'form_abandoned')
GROUP BY JSON_VALUE(metadata, '$.device_type');

Pattern 26: Feedback Loop Implementation

Use form event data to improve forms:

-- Fields with highest validation error rates
SELECT 
  JSON_VALUE(metadata, '$.field_name') as field,
  JSON_VALUE(metadata, '$.error_type') as error_type,
  COUNT(*) as error_count,
  AVG(CAST(JSON_VALUE(metadata, '$.attempt_number') AS INT)) as avg_attempts
FROM interaction_log
WHERE interaction_type = 'validation_error'
  AND interaction_timestamp > DATEADD(month, -1, GETDATE())
GROUP BY 
  JSON_VALUE(metadata, '$.field_name'),
  JSON_VALUE(metadata, '$.error_type')
HAVING COUNT(*) > 10
ORDER BY error_count DESC;

Action: Fields with high error rates should be: 1. Redesigned (V3 Pattern 6: Domain-Aware Validation) 2. Provided better help text (V3 Pattern 2: Contextual Scaffolding) 3. Given intelligent defaults (V3 Pattern 13: Intelligent Defaults)


Implementation Guide

1. Client-Side Event Capture (V3 Implementation)

// Form event logger (React example)
class FormEventLogger {
  private formName: string;
  private familyId: number;
  private sessionId: string;

  async logEvent(eventType: string, metadata: Record<string, any>) {
    const event: BaseEvent = {
      interaction_timestamp: new Date().toISOString(),
      interaction_type: eventType,
      interaction_category: this.getCategory(),
      channel: this.getChannel(),
      family_id: this.familyId,
      user_id: this.getCurrentUserId(),
      metadata: {
        form_name: this.formName,
        form_version: '2024.3',
        session_id: this.sessionId,
        ...metadata
      },
      created_by: 'form_client'
    };

    // Send to server (async, non-blocking)
    await fetch('/api/events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event)
    });
  }

  // Lifecycle events
  onFormViewed() {
    this.logEvent('form_viewed', {
      referrer: document.referrer,
      device_type: this.getDeviceType(),
      user_agent: navigator.userAgent
    });
  }

  onFormStarted(firstField: string) {
    this.logEvent('form_started', {
      first_field: firstField,
      time_since_viewed_seconds: this.getTimeSinceViewed()
    });
  }

  onFormSaved(formData: any) {
    this.logEvent('form_saved', {
      completion_percentage: this.calculateCompletion(formData),
      fields_completed: this.countCompletedFields(formData),
      fields_total: this.getTotalFields(),
      last_section: this.getCurrentSection(),
      time_spent_seconds: this.getTimeSpent(),
      save_trigger: 'manual_button_click'
    });
  }

  onFormSubmitted(formData: any, stats: any) {
    this.logEvent('form_submitted', {
      total_time_seconds: stats.totalTime,
      session_count: stats.sessionCount,
      validation_errors_encountered: stats.errorCount,
      help_views: stats.helpViews,
      saves_count: stats.saves,
      edit_count: stats.edits,
      submission_method: 'submit_button'
    });
  }

  // Field events
  onFieldChanged(fieldName: string, oldValue: any, newValue: any) {
    this.logEvent('field_changed', {
      field_name: fieldName,
      field_type: this.getFieldType(fieldName),
      old_value: oldValue,
      new_value: newValue,
      edit_count: this.getEditCount(fieldName),
      time_since_focus_seconds: this.getTimeSinceFocus(fieldName),
      change_trigger: 'user_edit'
    });
  }

  onValidationError(fieldName: string, error: any, value: any) {
    this.logEvent('validation_error', {
      field_name: fieldName,
      field_type: this.getFieldType(fieldName),
      submitted_value: value,
      error_type: error.type,
      error_code: error.code,
      error_message: error.message,
      validation_rule: error.rule,
      attempt_number: this.getAttemptNumber(fieldName)
    });
  }
}

2. Server-Side Event Storage (V2 Implementation)

// Express API endpoint
app.post('/api/events', async (req, res) => {
  const event = req.body;

  // Validate event structure
  if (!event.interaction_type || !event.family_id) {
    return res.status(400).json({ error: 'Missing required fields' });
  }

  // Insert into interaction_log (V2 Pattern 1)
  await db.query(`
    INSERT INTO interaction_log (
      interaction_timestamp,
      family_id,
      user_id,
      interaction_type,
      interaction_category,
      channel,
      outcome,
      metadata,
      created_by
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  `, [
    event.interaction_timestamp,
    event.family_id,
    event.user_id,
    event.interaction_type,
    event.interaction_category,
    event.channel,
    event.outcome,
    JSON.stringify(event.metadata),
    event.created_by
  ]);

  res.json({ success: true });
});

Best Practices

1. Event Granularity

Do capture: - User intent signals (viewed, started) - Significant state changes (saved, submitted) - Error events (validation errors, system errors) - User requests for help - Critical field interactions

Don't over-capture: - Every mouse movement - Every keystroke (privacy concern) - Trivial UI interactions (hover events unless meaningful)

Balance: Capture enough to understand user journey without overwhelming storage or violating privacy.

2. Metadata Design

Good metadata:

{
  "field_name": "student_grade",
  "old_value": "3",
  "new_value": "4",
  "edit_count": 2,
  "time_since_focus_seconds": 8
}

Over-designed metadata:

{
  "field_name": "student_grade",
  "field_label": "Student Grade Level",
  "field_type": "select",
  "field_options": ["K", "1", "2", "3", "4", "5"],
  "field_id": "input_grade_123",
  "field_xpath": "/html/body/div[1]/form/div[3]/select",
  "cursor_position": null,
  // ... too much!
}

Principle: Include context that helps analysis, exclude redundant form metadata that's static.

3. Privacy Considerations

Never log in metadata: - Passwords or security answers - Full credit card numbers - Social Security Numbers - Medical information (unless encrypted and necessary)

Anonymize when possible:

{
  "field_name": "guardian_email",
  "submitted_value": "j***@email.com",  // Partially redacted
  "validation_passed": false
}

See V2 Pattern 5: Privacy-Preserving Observation for comprehensive privacy guidance.

4. Performance

Async logging: Never block form interaction for event logging

// Good: Fire and forget
logEvent('field_changed', {...});  // Non-blocking

// Bad: Wait for logging
await logEvent('field_changed', {...});  // Blocks UI

Batching: For high-frequency events (field changes), batch and send periodically

eventBatcher.add(event);  // Queues event
// Batch sends every 5 seconds or 50 events

Sampling: For very high-volume forms, sample percentage of sessions

if (Math.random() < 0.1) {  // 10% sampling
  logEvent('field_focused', {...});
}

Cross-Volume References

Volume 2 Patterns That Consume Form Events

  • Pattern 1: Universal Event Log - Stores all form events
  • Pattern 11: Historical Pattern Matching - Finds similar form journeys
  • Pattern 16: Cohort Discovery & Analysis - Discovers form UX issues
  • Pattern 17: Anomaly Detection - Flags unusual form behavior
  • Pattern 18: Cohort Analysis - Compares form performance across segments
  • Pattern 26: Feedback Loop Implementation - Improves forms based on events

Volume 3 Patterns That Generate Form Events

  • Pattern 6: Domain-Aware Validation - Generates validation_error events
  • Pattern 13: Intelligent Defaults - Generates autofill events
  • Pattern 22: Event Stream Integration - Implements event generation
  • Pattern 23: Audit Trail - Extends events with audit requirements

Version History

Version 1.0 - December 27, 2025 - Initial canonical schema - Complete event taxonomy - V2/V3 integration guide - Implementation examples

Maintained by: Book Trilogy Authors
Next Review: When V2 or V3 patterns evolve


This appendix ensures form interactions generate intelligence-ready events that flow seamlessly from Volume 3 (Input Layer) to Volume 2 (Intelligence Layer), enabling the complete feedback loop described in Volume 3, Chapter 5.