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.