alumni_lookup

Affinaquest Contact Import

This feature imports contact information from Affinaquest (CRM) CSV exports to keep alumni data synchronized for search and matching purposes.

Overview

The import process:

  1. Matches alumni by BUID (primary) or Contact ID (secondary)
  2. Uses recency-aware updates - only overwrites local data if the import data is newer
  3. Logs conflicts for review and potential CRM export
  4. Processes large files via background jobs to avoid timeouts

Architecture

Data Model Changes

Alumni Model (new fields)
├── email_school        # School email address
├── email_personal      # Personal email address  
├── email_business      # Business email address
├── email_other         # Other email address
├── zip                 # ZIP code for district lookup
├── affinaquest_updated_at   # Last modified in Affinaquest
└── affinaquest_synced_at    # Last sync timestamp

Email Field Mapping

CSV Column Affinaquest Field Alumni Field Purpose
email Email Address email Primary email (already existed)
email_school School Email email_school .edu addresses
email_personal Personal Email email_personal Gmail, Yahoo, etc.
email_business Business Email email_business Work addresses
email_other Alternate Email email_other Catch-all

CRM Sync Architecture

Recency conflicts are logged to the unified crm_data_changes table for eventual export back to the CRM. This infrastructure is shared with the Champion Portal for self-service updates.

See: Unified CRM Sync Architecture

How to Use

1. Access the Import Page

Navigate to Settings → Affinaquest Import (/settings/affinaquest)

2. Upload a CSV File

The CSV must include these columns:

Optional contact fields:

3. Preview the Import

Click Preview to see:

4. Commit the Import

Click Commit to process the import. Large files (5000+ rows) run as background jobs.

5. Review Conflicts

After import, review:

Recency Logic

For each field, the import compares timestamps:

If local data is newer, the import keeps local data and logs a CrmDataChange record for potential export back to the CRM.

Data Flow

CSV Upload → Preview → Commit → Background Job (if large)
                                      ↓
                              Process each row:
                              1. Find alumni by BUID
                              2. Check Contact ID match
                              3. Compare timestamps
                              4. Update if import is newer
                              5. Log conflicts if local is newer
Table Purpose
alumni Main contact records
affinaquest_import_batches Tracks each import batch
affinaquest_import_conflicts ID mismatch conflicts
crm_data_changes Field-level recency conflicts for export

Maintenance Tasks

Check for Duplicate BUIDs

bin/rails alumni:check_duplicate_buids

Cleanup Duplicate BUIDs

If duplicates exist (shouldn’t happen with unique index):

bin/rails alumni:cleanup_duplicate_buids         # Dry run
bin/rails alumni:cleanup_duplicate_buids CONFIRM=1  # Actually merge

Cleanup Duplicate CRM Changes

If duplicate conflict records exist:

bin/rails alumni:cleanup_duplicate_crm_changes         # Dry run
bin/rails alumni:cleanup_duplicate_crm_changes CONFIRM=1  # Actually delete

Key Files

File Purpose
app/services/csv/affinaquest_contact_importer.rb Main import logic
app/jobs/affinaquest_import_job.rb Background job wrapper
app/controllers/settings/affinaquest_controller.rb UI controller
app/models/affinaquest_import_batch.rb Batch tracking
app/models/affinaquest_import_conflict.rb ID conflicts
app/models/crm_data_change.rb Recency conflict logging
lib/tasks/cleanup_duplicate_buids.rake Maintenance tasks

Troubleshooting

“Contact has already been taken” Errors

This indicates duplicate BUID records in the database. Run:

bin/rails alumni:check_duplicate_buids

If duplicates exist, merge them:

bin/rails alumni:cleanup_duplicate_buids CONFIRM=1

Import Stuck at “Processing”

Background jobs require Sidekiq + Redis. Check:

  1. Redis is running: redis-cli ping
  2. Sidekiq is running: bundle exec sidekiq

Conflicts Not Appearing

Ensure the CrmDataChange model matches the database schema. Check:

Large Import Timeouts

Files with 5000+ rows automatically use background jobs. Ensure Sidekiq is configured.

Database Constraints

Search Capabilities

The imported contact data enhances search functionality:

The filter_by_any_email scope searches across all email fields:

Used in: Alumni search, batch search, event RSVP matching

ZIP codes enable district-based filtering:

ZIP Integration

Alumni.zip → ZipCode.zip → ZipCode.district_id → District
                        → ZipCode.region_id → Region

Methods available:

Future Enhancements

  1. CRM Export - Export crm_data_changes back to Affinaquest
  2. Conflict Resolution UI - Allow manual resolution of ID conflicts
  3. Scheduled Imports - Automated daily/weekly sync