upskill-event-manager/docs/TRAINER-PROFILE-TECHNICAL-ADDENDUM.md
bengizmo 55d0ffe207 feat: Implement comprehensive trainer profile custom post type system
This commit implements a complete trainer profile custom post type system with the following components:

## Core Features Implemented:
- Custom post type 'trainer_profile' with full CRUD operations
- Bidirectional data synchronization between wp_users and trainer profiles
- Google Maps API integration for geocoding trainer locations
- Master trainer interface for profile management
- Data migration system for existing users

## Key Components:
1. **HVAC_Trainer_Profile_Manager**: Core profile management with singleton pattern
2. **HVAC_Profile_Sync_Handler**: Bidirectional user-profile data synchronization
3. **HVAC_Geocoding_Service**: Google Maps API integration with rate limiting
4. **HVAC_Trainer_Profile_Settings**: Admin configuration interface
5. **Migration System**: Comprehensive user meta to custom post migration

## Templates & UI:
- Enhanced trainer profile view with comprehensive data display
- Full-featured profile edit form with 58+ fields
- Master trainer profile editing interface
- Professional styling and responsive design
- Certificate pages template integration fixes

## Database & Data:
- Custom post type registration with proper capabilities
- Meta field synchronization between users and profiles
- Migration of 53 existing trainers to new system
- Geocoding integration with coordinate storage

## Testing & Deployment:
- Successfully deployed to staging environment
- Executed data migration for all existing users
- Comprehensive E2E testing with 85-90% success rate
- Google Maps API configured and operational

## System Status:
 Trainer profile viewing and editing: 100% functional
 Data migration: 53 profiles created successfully
 Master dashboard integration: Clickable trainer names working
 Certificate pages: Template integration resolved
 Geocoding: Google Maps API configured and enabled
⚠️ Master trainer profile editing: Minor template issue remaining

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 18:45:41 -03:00

36 KiB

Trainer Profile Implementation - Technical Complexities Addendum

Overview

This document addresses advanced technical complexities not covered in the main implementation guide. These details are critical for robust production implementation.

1. Public Directory & Gutenberg Compatibility

REST API Integration

The show_in_rest => true setting enables several critical features:

register_post_type('trainer_profile', [
    'show_in_rest' => true,
    'rest_base' => 'trainer-profiles',
    'rest_controller_class' => 'HVAC_Trainer_Profile_REST_Controller'
]);

Custom REST Controller

class HVAC_Trainer_Profile_REST_Controller extends WP_REST_Posts_Controller {
    
    public function get_items_permissions_check($request) {
        // Allow public reading but restrict by profile visibility
        return true;
    }
    
    public function prepare_item_for_response($post, $request) {
        $response = parent::prepare_item_for_response($post, $request);
        
        // Add custom meta fields to REST response
        $meta_fields = [
            'certification_status',
            'trainer_city',
            'trainer_state',
            'business_type',
            'is_public_profile'
        ];
        
        foreach ($meta_fields as $field) {
            $response->data[$field] = get_post_meta($post->ID, $field, true);
        }
        
        return $response;
    }
}

Gutenberg Query Loop Filters

// Add custom query parameters for Gutenberg
function modify_trainer_profiles_rest_query($args, $request) {
    // Only show public profiles in frontend queries
    if (!is_admin() && !current_user_can('hvac_master_trainer')) {
        $args['meta_query'][] = [
            'key' => 'is_public_profile',
            'value' => '1',
            'compare' => '='
        ];
    }
    
    // Filter by certification status
    if ($request->get_param('certification_status')) {
        $args['meta_query'][] = [
            'key' => 'certification_status',
            'value' => $request->get_param('certification_status'),
            'compare' => '='
        ];
    }
    
    // Filter by location
    if ($request->get_param('trainer_city')) {
        $args['meta_query'][] = [
            'key' => 'trainer_city',
            'value' => $request->get_param('trainer_city'),
            'compare' => 'LIKE'
        ];
    }
    
    // Proximity search using coordinates
    if ($request->get_param('latitude') && $request->get_param('longitude')) {
        $lat = floatval($request->get_param('latitude'));
        $lng = floatval($request->get_param('longitude'));
        $radius = intval($request->get_param('radius')) ?: 50; // Default 50km
        
        // Add proximity calculation to query
        add_filter('posts_fields', function($fields) use ($lat, $lng) {
            global $wpdb;
            $fields .= ", (
                6371 * acos(
                    cos(radians($lat)) * 
                    cos(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8)))) * 
                    cos(radians(CAST(lng_meta.meta_value AS DECIMAL(11,8))) - radians($lng)) + 
                    sin(radians($lat)) * 
                    sin(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8))))
                )
            ) AS distance";
            return $fields;
        });
        
        add_filter('posts_join', function($join) {
            global $wpdb;
            $join .= " LEFT JOIN {$wpdb->postmeta} lat_meta ON {$wpdb->posts}.ID = lat_meta.post_id AND lat_meta.meta_key = 'latitude'";
            $join .= " LEFT JOIN {$wpdb->postmeta} lng_meta ON {$wpdb->posts}.ID = lng_meta.post_id AND lng_meta.meta_key = 'longitude'";
            return $join;
        });
        
        add_filter('posts_where', function($where) use ($radius) {
            $where .= " HAVING distance < $radius";
            return $where;
        });
        
        add_filter('posts_orderby', function($orderby) {
            return "distance ASC";
        });
    }
    
    return $args;
}
add_filter('rest_trainer_profile_query', 'modify_trainer_profiles_rest_query', 10, 2);

Gutenberg Block Registration

function register_trainer_directory_block() {
    register_block_type('hvac/trainer-directory', [
        'render_callback' => 'render_trainer_directory_block',
        'attributes' => [
            'certification_status' => ['type' => 'string'],
            'location' => ['type' => 'string'],
            'radius' => ['type' => 'number', 'default' => 50],
            'show_map' => ['type' => 'boolean', 'default' => false]
        ]
    ]);
}
add_action('init', 'register_trainer_directory_block');

2. Detailed Permission Matrix

Field-Level Permission System

class HVAC_Trainer_Profile_Permissions {
    
    private static $field_permissions = [
        // Fields editable by profile owner only
        'owner_only' => [
            'linkedin_profile_url',
            'personal_accreditation',
            'biographical_info',
            'training_audience',
            'training_formats',
            'training_locations',
            'training_resources',
            'annual_revenue_target'
        ],
        
        // Fields editable by master trainers only
        'master_trainer_only' => [
            'certification_status',
            'date_certified',
            'certification_type',
            'is_public_profile'
        ],
        
        // Fields editable by both owner and master trainer
        'shared_edit' => [
            'trainer_first_name',
            'trainer_last_name',
            'trainer_display_name',
            'trainer_city',
            'trainer_state',
            'trainer_country',
            'business_type',
            'application_details'
        ],
        
        // Read-only fields (auto-generated)
        'readonly' => [
            'latitude',
            'longitude',
            'last_geocoded_timestamp',
            'geocoding_status'
        ],
        
        // Never accessible via trainer profile
        'restricted' => [
            'user_email',
            'user_pass',
            'user_login'
        ]
    ];
    
    public static function can_edit_field($field_name, $user_id, $profile_user_id) {
        $is_owner = ($user_id === $profile_user_id);
        $is_master = user_can($user_id, 'hvac_master_trainer');
        $is_admin = user_can($user_id, 'administrator');
        
        // Admins can edit everything except restricted
        if ($is_admin && !in_array($field_name, self::$field_permissions['restricted'])) {
            return true;
        }
        
        // Check field-specific permissions
        if (in_array($field_name, self::$field_permissions['owner_only'])) {
            return $is_owner;
        }
        
        if (in_array($field_name, self::$field_permissions['master_trainer_only'])) {
            return $is_master;
        }
        
        if (in_array($field_name, self::$field_permissions['shared_edit'])) {
            return $is_owner || $is_master;
        }
        
        // Readonly and restricted fields
        return false;
    }
    
    public static function filter_editable_fields($fields, $user_id, $profile_user_id) {
        $editable = [];
        foreach ($fields as $field_name => $field_data) {
            if (self::can_edit_field($field_name, $user_id, $profile_user_id)) {
                $editable[$field_name] = $field_data;
            }
        }
        return $editable;
    }
}

Context-Based Permission Checks

function trainer_profile_context_permissions($context, $user_id, $profile_id) {
    $profile_user_id = get_post_meta($profile_id, 'user_id', true);
    
    switch ($context) {
        case 'public_directory':
            // Only show public profiles
            return get_post_meta($profile_id, 'is_public_profile', true) === '1';
            
        case 'admin_list':
            // Admins see all, master trainers see all, owners see own
            return user_can($user_id, 'administrator') || 
                   user_can($user_id, 'hvac_master_trainer') || 
                   $user_id == $profile_user_id;
                   
        case 'edit_form':
            // Only owners and master trainers can access edit forms
            return $user_id == $profile_user_id || 
                   user_can($user_id, 'hvac_master_trainer') ||
                   user_can($user_id, 'administrator');
                   
        case 'single_view':
            // Public profiles visible to all, private to authorized users only
            $is_public = get_post_meta($profile_id, 'is_public_profile', true) === '1';
            if ($is_public) return true;
            
            return $user_id == $profile_user_id || 
                   user_can($user_id, 'hvac_master_trainer') ||
                   user_can($user_id, 'administrator');
    }
    
    return false;
}

3. Data Synchronization Edge Cases

Infinite Loop Prevention

class HVAC_Profile_Sync_Handler {
    
    private static $sync_in_progress = [];
    
    public static function sync_user_to_profile($user_id, $old_user_data = null) {
        // Prevent infinite loops
        $sync_key = "user_to_profile_{$user_id}";
        if (isset(self::$sync_in_progress[$sync_key])) {
            return;
        }
        
        self::$sync_in_progress[$sync_key] = true;
        
        try {
            $profile_id = get_user_meta($user_id, 'trainer_profile_id', true);
            if (!$profile_id) {
                unset(self::$sync_in_progress[$sync_key]);
                return;
            }
            
            // Get current user data
            $user = get_userdata($user_id);
            
            // Sync shared fields
            $sync_fields = [
                'first_name' => 'trainer_first_name',
                'last_name' => 'trainer_last_name',
                'display_name' => 'trainer_display_name'
            ];
            
            foreach ($sync_fields as $user_field => $profile_field) {
                $user_value = $user->$user_field;
                $profile_value = get_post_meta($profile_id, $profile_field, true);
                
                // Only update if values differ
                if ($user_value !== $profile_value) {
                    update_post_meta($profile_id, $profile_field, $user_value);
                }
            }
            
            // Log sync operation
            error_log("HVAC Profile Sync: User {$user_id} synced to profile {$profile_id}");
            
        } catch (Exception $e) {
            error_log("HVAC Profile Sync Error: " . $e->getMessage());
        } finally {
            unset(self::$sync_in_progress[$sync_key]);
        }
    }
    
    public static function sync_profile_to_user($post_id, $post = null) {
        if (get_post_type($post_id) !== 'trainer_profile') {
            return;
        }
        
        // Prevent infinite loops
        $sync_key = "profile_to_user_{$post_id}";
        if (isset(self::$sync_in_progress[$sync_key])) {
            return;
        }
        
        self::$sync_in_progress[$sync_key] = true;
        
        try {
            $user_id = get_post_meta($post_id, 'user_id', true);
            if (!$user_id) {
                unset(self::$sync_in_progress[$sync_key]);
                return;
            }
            
            // Sync shared fields
            $sync_fields = [
                'trainer_first_name' => 'first_name',
                'trainer_last_name' => 'last_name',
                'trainer_display_name' => 'display_name'
            ];
            
            $update_data = ['ID' => $user_id];
            $needs_update = false;
            
            foreach ($sync_fields as $profile_field => $user_field) {
                $profile_value = get_post_meta($post_id, $profile_field, true);
                $user_value = get_user_meta($user_id, $user_field, true);
                
                if ($profile_value !== $user_value) {
                    $update_data[$user_field] = $profile_value;
                    $needs_update = true;
                }
            }
            
            if ($needs_update) {
                wp_update_user($update_data);
            }
            
        } catch (Exception $e) {
            error_log("HVAC Profile Sync Error: " . $e->getMessage());
        } finally {
            unset(self::$sync_in_progress[$sync_key]);
        }
    }
    
    // Handle concurrent updates
    public static function handle_concurrent_update($user_id, $profile_id, $field, $user_value, $profile_value, $timestamp) {
        // Conflict resolution: most recent update wins
        $user_modified = get_user_meta($user_id, "_{$field}_modified", true);
        $profile_modified = get_post_meta($profile_id, "_{$field}_modified", true);
        
        if ($user_modified > $profile_modified) {
            // User data is more recent, sync to profile
            update_post_meta($profile_id, "trainer_{$field}", $user_value);
            update_post_meta($profile_id, "_{$field}_modified", $timestamp);
        } else {
            // Profile data is more recent, sync to user
            update_user_meta($user_id, $field, $profile_value);
            update_user_meta($user_id, "_{$field}_modified", $timestamp);
        }
        
        // Log conflict resolution
        error_log("HVAC Sync Conflict Resolved: Field '{$field}' for user {$user_id}");
    }
}

Failed Sync Recovery

class HVAC_Sync_Recovery {
    
    public static function schedule_sync_verification() {
        if (!wp_next_scheduled('hvac_verify_sync_integrity')) {
            wp_schedule_event(time(), 'hourly', 'hvac_verify_sync_integrity');
        }
    }
    
    public static function verify_sync_integrity() {
        $profiles = get_posts([
            'post_type' => 'trainer_profile',
            'posts_per_page' => -1,
            'meta_query' => [
                [
                    'key' => 'user_id',
                    'compare' => 'EXISTS'
                ]
            ]
        ]);
        
        $sync_issues = [];
        
        foreach ($profiles as $profile) {
            $user_id = get_post_meta($profile->ID, 'user_id', true);
            $user = get_userdata($user_id);
            
            if (!$user) {
                $sync_issues[] = [
                    'type' => 'orphaned_profile',
                    'profile_id' => $profile->ID,
                    'user_id' => $user_id
                ];
                continue;
            }
            
            // Check field synchronization
            $sync_fields = [
                'first_name' => 'trainer_first_name',
                'last_name' => 'trainer_last_name',
                'display_name' => 'trainer_display_name'
            ];
            
            foreach ($sync_fields as $user_field => $profile_field) {
                $user_value = $user->$user_field;
                $profile_value = get_post_meta($profile->ID, $profile_field, true);
                
                if ($user_value !== $profile_value) {
                    $sync_issues[] = [
                        'type' => 'field_mismatch',
                        'profile_id' => $profile->ID,
                        'user_id' => $user_id,
                        'field' => $user_field,
                        'user_value' => $user_value,
                        'profile_value' => $profile_value
                    ];
                }
            }
        }
        
        if (!empty($sync_issues)) {
            self::repair_sync_issues($sync_issues);
        }
    }
    
    private static function repair_sync_issues($issues) {
        foreach ($issues as $issue) {
            switch ($issue['type']) {
                case 'orphaned_profile':
                    // Handle orphaned profiles
                    wp_update_post([
                        'ID' => $issue['profile_id'],
                        'post_status' => 'draft'
                    ]);
                    add_post_meta($issue['profile_id'], '_sync_status', 'orphaned');
                    break;
                    
                case 'field_mismatch':
                    // Auto-repair field mismatches (user data takes precedence)
                    update_post_meta(
                        $issue['profile_id'], 
                        'trainer_' . $issue['field'], 
                        $issue['user_value']
                    );
                    break;
            }
        }
        
        // Log repair actions
        error_log("HVAC Sync Repair: Fixed " . count($issues) . " sync issues");
    }
}

add_action('hvac_verify_sync_integrity', ['HVAC_Sync_Recovery', 'verify_sync_integrity']);

4. Advanced Geocoding Implementation

Rate Limiting & Caching System

class HVAC_Geocoding_Service {
    
    private static $api_key;
    private static $rate_limit = 50; // requests per minute
    private static $cache_duration = DAY_IN_SECONDS;
    
    public static function init() {
        self::$api_key = get_option('hvac_google_maps_api_key');
        add_action('updated_post_meta', [__CLASS__, 'maybe_geocode'], 10, 4);
    }
    
    public static function maybe_geocode($meta_id, $post_id, $meta_key, $meta_value) {
        if (get_post_type($post_id) !== 'trainer_profile') {
            return;
        }
        
        $address_fields = ['trainer_city', 'trainer_state', 'trainer_country'];
        if (!in_array($meta_key, $address_fields)) {
            return;
        }
        
        // Debounce rapid updates
        $last_geocode = get_post_meta($post_id, '_last_geocode_attempt', true);
        if ($last_geocode && (time() - $last_geocode) < 30) {
            return;
        }
        
        // Schedule geocoding with delay to allow all address fields to be saved
        wp_schedule_single_event(time() + 5, 'hvac_geocode_address', [$post_id]);
    }
    
    public static function geocode_address($post_id) {
        // Check rate limiting
        if (!self::check_rate_limit()) {
            // Reschedule for later
            wp_schedule_single_event(time() + 60, 'hvac_geocode_address', [$post_id]);
            return;
        }
        
        $address = self::build_address($post_id);
        if (empty($address)) {
            return;
        }
        
        update_post_meta($post_id, '_last_geocode_attempt', time());
        
        // Check cache first
        $cache_key = 'geocode_' . md5($address);
        $cached = get_transient($cache_key);
        
        if ($cached !== false) {
            self::update_coordinates($post_id, $cached);
            return;
        }
        
        // Make API request
        $result = self::make_geocoding_request($address);
        
        if ($result && isset($result['lat'], $result['lng'])) {
            // Cache successful result
            set_transient($cache_key, $result, self::$cache_duration);
            self::update_coordinates($post_id, $result);
            
            update_post_meta($post_id, '_geocoding_status', 'success');
            update_post_meta($post_id, '_last_geocoded', time());
        } else {
            // Handle failure
            self::handle_geocoding_failure($post_id, $result);
        }
    }
    
    private static function check_rate_limit() {
        $rate_key = 'hvac_geocoding_rate_' . gmdate('Y-m-d-H-i');
        $current_count = get_transient($rate_key) ?: 0;
        
        if ($current_count >= self::$rate_limit) {
            return false;
        }
        
        set_transient($rate_key, $current_count + 1, 60);
        return true;
    }
    
    private static function make_geocoding_request($address) {
        if (empty(self::$api_key)) {
            return ['error' => 'No API key configured'];
        }
        
        $url = 'https://maps.googleapis.com/maps/api/geocode/json';
        $params = [
            'address' => $address,
            'key' => self::$api_key,
            'components' => 'country:US|country:CA' // Restrict to North America
        ];
        
        $response = wp_remote_get($url . '?' . http_build_query($params), [
            'timeout' => 10,
            'user-agent' => 'HVAC Trainer Directory/1.0'
        ]);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (!$data || $data['status'] !== 'OK' || empty($data['results'])) {
            return ['error' => $data['status'] ?? 'Unknown error'];
        }
        
        $location = $data['results'][0]['geometry']['location'];
        return [
            'lat' => $location['lat'],
            'lng' => $location['lng'],
            'formatted_address' => $data['results'][0]['formatted_address'],
            'confidence' => self::calculate_confidence($data['results'][0])
        ];
    }
    
    private static function handle_geocoding_failure($post_id, $error_result) {
        $error_message = $error_result['error'] ?? 'Unknown error';
        
        update_post_meta($post_id, '_geocoding_status', 'failed');
        update_post_meta($post_id, '_geocoding_error', $error_message);
        
        // Implement retry logic based on error type
        switch ($error_message) {
            case 'OVER_QUERY_LIMIT':
                // Retry in 1 hour
                wp_schedule_single_event(time() + HOUR_IN_SECONDS, 'hvac_geocode_address', [$post_id]);
                break;
                
            case 'ZERO_RESULTS':
                // Try fallback geocoding service
                self::try_fallback_geocoding($post_id);
                break;
                
            case 'REQUEST_DENIED':
                // Log API key issue
                error_log("HVAC Geocoding: API key issue - {$error_message}");
                break;
                
            default:
                // Retry in 5 minutes for transient errors
                wp_schedule_single_event(time() + 300, 'hvac_geocode_address', [$post_id]);
        }
    }
    
    private static function try_fallback_geocoding($post_id) {
        // Implement OpenStreetMap Nominatim as fallback
        $address = self::build_address($post_id);
        
        $url = 'https://nominatim.openstreetmap.org/search';
        $params = [
            'q' => $address,
            'format' => 'json',
            'limit' => 1,
            'countrycodes' => 'us,ca'
        ];
        
        $response = wp_remote_get($url . '?' . http_build_query($params), [
            'timeout' => 10,
            'user-agent' => 'HVAC Trainer Directory/1.0'
        ]);
        
        if (!is_wp_error($response)) {
            $body = wp_remote_retrieve_body($response);
            $data = json_decode($body, true);
            
            if (!empty($data) && isset($data[0]['lat'], $data[0]['lon'])) {
                $result = [
                    'lat' => floatval($data[0]['lat']),
                    'lng' => floatval($data[0]['lon']),
                    'formatted_address' => $data[0]['display_name'],
                    'confidence' => 0.7, // Lower confidence for fallback
                    'source' => 'nominatim'
                ];
                
                self::update_coordinates($post_id, $result);
                update_post_meta($post_id, '_geocoding_status', 'success_fallback');
                return;
            }
        }
        
        // Final fallback failed
        update_post_meta($post_id, '_geocoding_status', 'failed_all');
    }
    
    private static function calculate_confidence($result) {
        $types = $result['types'] ?? [];
        
        // Higher confidence for more specific location types
        if (in_array('street_address', $types)) return 1.0;
        if (in_array('premise', $types)) return 0.9;
        if (in_array('subpremise', $types)) return 0.85;
        if (in_array('locality', $types)) return 0.8;
        if (in_array('administrative_area_level_3', $types)) return 0.7;
        if (in_array('administrative_area_level_2', $types)) return 0.6;
        if (in_array('administrative_area_level_1', $types)) return 0.5;
        
        return 0.4; // Low confidence for country-level matches
    }
}

add_action('init', ['HVAC_Geocoding_Service', 'init']);
add_action('hvac_geocode_address', ['HVAC_Geocoding_Service', 'geocode_address']);

5. CSV Migration Complexity Handling

Migration State Management

class HVAC_CSV_Migration_Manager {
    
    private static $migration_log = [];
    
    public static function migrate_csv_data($csv_file_path) {
        // Initialize migration tracking
        $migration_id = uniqid('migration_');
        $start_time = time();
        
        update_option('hvac_migration_status', [
            'id' => $migration_id,
            'status' => 'in_progress',
            'start_time' => $start_time,
            'total_records' => 0,
            'processed' => 0,
            'errors' => []
        ]);
        
        try {
            // Validate CSV file
            if (!file_exists($csv_file_path)) {
                throw new Exception("CSV file not found: {$csv_file_path}");
            }
            
            // Parse CSV and count records
            $csv_data = self::parse_csv($csv_file_path);
            $total_records = count($csv_data);
            
            self::update_migration_status($migration_id, [
                'total_records' => $total_records
            ]);
            
            // Process each record
            foreach ($csv_data as $index => $row) {
                try {
                    self::process_csv_row($row, $index);
                    self::update_migration_status($migration_id, [
                        'processed' => $index + 1
                    ]);
                } catch (Exception $e) {
                    self::log_migration_error($migration_id, $index, $e->getMessage(), $row);
                }
            }
            
            // Complete migration
            self::complete_migration($migration_id);
            
        } catch (Exception $e) {
            self::fail_migration($migration_id, $e->getMessage());
            throw $e;
        }
    }
    
    private static function process_csv_row($row, $index) {
        // Validate required fields
        $required_fields = ['email', 'first_name', 'last_name'];
        foreach ($required_fields as $field) {
            if (empty($row[$field])) {
                throw new Exception("Missing required field: {$field}");
            }
        }
        
        // Check if user already exists
        $user = get_user_by('email', $row['email']);
        
        if ($user) {
            // Update existing user
            $profile_id = self::get_or_create_trainer_profile($user->ID);
            self::update_profile_from_csv($profile_id, $row);
        } else {
            // Create new user
            $user_id = self::create_user_from_csv($row);
            $profile_id = self::create_trainer_profile($user_id, $row);
        }
        
        // Validate profile creation
        if (!$profile_id) {
            throw new Exception("Failed to create trainer profile for user: {$row['email']}");
        }
        
        // Trigger geocoding if address data exists
        if (!empty($row['trainer_city']) || !empty($row['trainer_state'])) {
            wp_schedule_single_event(time() + (5 * $index), 'hvac_geocode_address', [$profile_id]);
        }
    }
    
    private static function get_or_create_trainer_profile($user_id) {
        $existing_profile_id = get_user_meta($user_id, 'trainer_profile_id', true);
        
        if ($existing_profile_id && get_post($existing_profile_id)) {
            return $existing_profile_id;
        }
        
        // Create new profile
        $user = get_userdata($user_id);
        $profile_id = wp_insert_post([
            'post_type' => 'trainer_profile',
            'post_title' => $user->display_name . ' - Trainer Profile',
            'post_status' => 'publish',
            'post_author' => $user_id
        ]);
        
        if (is_wp_error($profile_id)) {
            throw new Exception("Failed to create trainer profile: " . $profile_id->get_error_message());
        }
        
        // Establish relationships
        update_post_meta($profile_id, 'user_id', $user_id);
        update_user_meta($user_id, 'trainer_profile_id', $profile_id);
        
        return $profile_id;
    }
    
    private static function rollback_migration($migration_id) {
        $status = get_option('hvac_migration_status');
        if (!$status || $status['id'] !== $migration_id) {
            return false;
        }
        
        // Get all profiles created during this migration
        $created_profiles = get_option("hvac_migration_created_{$migration_id}", []);
        
        foreach ($created_profiles as $profile_id) {
            // Remove trainer profile
            wp_delete_post($profile_id, true);
            
            // Clean up user meta
            $user_id = get_post_meta($profile_id, 'user_id', true);
            if ($user_id) {
                delete_user_meta($user_id, 'trainer_profile_id');
            }
        }
        
        // Get all users created during this migration
        $created_users = get_option("hvac_migration_users_{$migration_id}", []);
        
        foreach ($created_users as $user_id) {
            wp_delete_user($user_id);
        }
        
        // Clean up migration data
        delete_option("hvac_migration_created_{$migration_id}");
        delete_option("hvac_migration_users_{$migration_id}");
        
        update_option('hvac_migration_status', [
            'id' => $migration_id,
            'status' => 'rolled_back',
            'rollback_time' => time()
        ]);
        
        return true;
    }
}

6. Form State Management & UX

Auto-save & Unsaved Changes Detection

// JavaScript for form state management
function initFormStateManagement() {
    let formData = new FormData();
    let hasUnsavedChanges = false;
    let autoSaveInterval;
    
    // Capture initial form state
    function captureFormState() {
        const form = document.getElementById('trainer-profile-form');
        formData = new FormData(form);
    }
    
    // Check for changes
    function checkForChanges() {
        const form = document.getElementById('trainer-profile-form');
        const currentData = new FormData(form);
        
        // Compare form data
        let hasChanges = false;
        for (let [key, value] of currentData.entries()) {
            if (formData.get(key) !== value) {
                hasChanges = true;
                break;
            }
        }
        
        if (hasChanges !== hasUnsavedChanges) {
            hasUnsavedChanges = hasChanges;
            toggleUnsavedIndicator(hasChanges);
            
            if (hasChanges && !autoSaveInterval) {
                startAutoSave();
            } else if (!hasChanges && autoSaveInterval) {
                stopAutoSave();
            }
        }
    }
    
    // Auto-save functionality
    function startAutoSave() {
        autoSaveInterval = setInterval(() => {
            if (hasUnsavedChanges) {
                autoSaveForm();
            }
        }, 30000); // Auto-save every 30 seconds
    }
    
    function autoSaveForm() {
        const form = document.getElementById('trainer-profile-form');
        const formData = new FormData(form);
        formData.append('action', 'hvac_auto_save_profile');
        formData.append('auto_save', '1');
        
        fetch(hvac_ajax.ajax_url, {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showAutoSaveIndicator();
                captureFormState(); // Update baseline
                hasUnsavedChanges = false;
                toggleUnsavedIndicator(false);
            }
        })
        .catch(error => {
            console.error('Auto-save failed:', error);
        });
    }
    
    // Prevent navigation with unsaved changes
    window.addEventListener('beforeunload', (e) => {
        if (hasUnsavedChanges) {
            e.preventDefault();
            e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
            return e.returnValue;
        }
    });
    
    // Real-time validation
    function setupRealTimeValidation() {
        const form = document.getElementById('trainer-profile-form');
        const inputs = form.querySelectorAll('input, select, textarea');
        
        inputs.forEach(input => {
            input.addEventListener('blur', () => {
                validateField(input);
            });
            
            input.addEventListener('input', debounce(() => {
                checkForChanges();
                if (input.value.length > 0) {
                    validateField(input);
                }
            }, 300));
        });
    }
    
    function validateField(field) {
        const fieldName = field.name;
        const fieldValue = field.value;
        
        // Client-side validation rules
        const validationRules = {
            'linkedin_profile_url': {
                pattern: /^https:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9-]+\/?$/,
                message: 'Please enter a valid LinkedIn profile URL'
            },
            'trainer_email': {
                pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
                message: 'Please enter a valid email address'
            },
            'annual_revenue_target': {
                pattern: /^\d+$/,
                message: 'Please enter a valid number'
            }
        };
        
        if (validationRules[fieldName]) {
            const rule = validationRules[fieldName];
            const isValid = rule.pattern.test(fieldValue);
            
            toggleFieldValidation(field, isValid, rule.message);
        }
    }
    
    // Initialize everything
    captureFormState();
    setupRealTimeValidation();
    
    // Form submission handling
    document.getElementById('trainer-profile-form').addEventListener('submit', (e) => {
        e.preventDefault();
        
        // Show loading state
        const submitButton = e.target.querySelector('button[type="submit"]');
        const originalText = submitButton.textContent;
        submitButton.textContent = 'Saving...';
        submitButton.disabled = true;
        
        // Submit form via AJAX
        const formData = new FormData(e.target);
        formData.append('action', 'hvac_save_trainer_profile');
        
        fetch(hvac_ajax.ajax_url, {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showSuccessMessage('Profile saved successfully!');
                captureFormState(); // Update baseline
                hasUnsavedChanges = false;
                toggleUnsavedIndicator(false);
                
                // Trigger geocoding if address changed
                if (data.data && data.data.geocoding_triggered) {
                    showGeocodingIndicator();
                }
            } else {
                showErrorMessage(data.data || 'An error occurred while saving.');
            }
        })
        .catch(error => {
            showErrorMessage('Network error occurred. Please try again.');
        })
        .finally(() => {
            submitButton.textContent = originalText;
            submitButton.disabled = false;
        });
    });
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', initFormStateManagement);

This addendum addresses the critical technical complexities that ensure robust, production-ready implementation of the trainer profile system.