alumni_lookup

Phase 1: Foundations

Champion Portal Development Phase 1

Estimated Effort: 8–12 weeks
Focus: Auth, Profile, Directory, Regional structure, Dashboard

Related Documents:


Table of Contents

  1. Overview
  2. Sub-Phases
  3. Questions to Answer Before Starting
  4. Scope
  5. Definition of Success
  6. Tests to Create
  7. Documentation Updates

1. Overview

Phase 1 establishes the foundation for the Champion Portal. Champions will be able to:

This phase delivers the core identity and discovery features that make Champions feel part of a community from day one.


2. Sub-Phases

Phase 1 is divided into sub-phases that can be implemented incrementally. Each sub-phase is a standalone prompt for an implementation agent.

Sub-Phase Name Spec Status Est. Time
1.1 Database & Models ✅ Complete 1–2 days
1.2 Email Authentication ✅ Complete 1–2 days
1.3 SSO Authentication ✅ Complete 1–2 days
1.4 Profile & Directory ✅ Complete 2–3 days
1.5 Dashboard & Admin ✅ Complete 2–3 days
1.6 Dashboard, Directory & Metrics ✅ Complete 1–2 weeks
1.7 In-App Messaging 1.7-messaging.md ✅ Complete 1–2 weeks
1.8 Staff Metrics Dashboard 1.8-staff-metrics-dashboard.md ✅ Complete 1–2 weeks
1.9 Pre-Beta Polish 1.9-pre-beta-polish.md ✅ Complete 2–3 weeks
1.10 Community News & Announcements 1.10-community-news.md ✅ Complete 2–3 weeks
1.11 Admin Events 1.11-admin-events.md ✅ Complete 1–2 weeks
1.12 Community Foundation 1.12-community-foundation.md ✅ Complete 1–2 weeks
1.13 Community Landing Pages 1.13-community-landing-pages.md ✅ Complete 1–2 weeks
1.14 Dynamic Community Creation 1.14-dynamic-community-creation.md ✅ Complete 1–2 weeks
1.15 Community Enhancements 1.15-community-enhancements.md ✅ Complete 1–2 weeks
1.16 Notifications System 1.16-notifications.md 🔲 Not Started 3–4 weeks

Sub-Phase 1.1: Database & Models

Goal: Create all database tables and models for Champion Portal foundations.

Status: ✅ COMPLETE (December 5, 2025)

Deliverables:

Implementation Notes:

Migrations Created:

Deployment: After deploying migrations, run seed task on staging/production:

heroku run rails champion_portal:seed_geographic_data --app alumni-lookup-staging

Acceptance Test:

# In rails console:
champion = Cp::Champion.create!(first_name: "Test", last_name: "User", email: "test@example.com", password: "password123")
champion.verification_status # => "unverified"
ZipCode.find_by(zip: "37027").district.name # => "Nashville"
ZipCode.find_by(zip: "37027").district.region.name # => "Southeast"

Sub-Phase 1.2: Email Authentication

Goal: Champions can sign up with email, verify, and log in.

Status: ✅ COMPLETE (December 5, 2025)

Deliverables:

Test Coverage (53 tests, 119 assertions):

Note: SSO buttons deferred to Phase 1.3 due to Devise multi-model OmniAuth path prefix conflict.

Files Created:

Routes Added:

Acceptance Test:

  1. Visit champions.local.test/signup → enter name + email → submit
  2. Receive confirmation email → click link
  3. Set password (min 8 characters) → account now “email_verified”
  4. Log in with email + password → see dashboard
  5. Log out → request password reset → complete reset → log in

Sub-Phase 1.3: SSO Authentication

Goal: Champions can sign up/log in with Google (Apple/Facebook deferred to future enhancement).

Status: ✅ COMPLETE (December 8, 2025)

Deliverables:

Implementation Notes:

Files Created:

Files Modified:

Routes Added:

Acceptance Test:

  1. Visit dev.alumnichampions.com:3000/login → see “Sign in with Google” at top
  2. Click “Sign in with Google” → authorize with Google → redirected to ZIP prompt
  3. Enter ZIP code → submit → redirected to dashboard
  4. Log out → “Sign in with Google” again → logged in directly to dashboard

Sub-Phase 1.4: Profile & Directory

Goal: Champions can complete profiles and search for other Champions.

Deliverables:

Profile Completion Wizard:

Affinity Selection (Step 4 & Profile Edit):

Profile Edit Page:

Directory:

Export Infrastructure:

Key UX Decisions:

Acceptance Test:

  1. Log in as Email Verified → start profile completion wizard
  2. Complete Step 1 (required) → skip Steps 2-4 → upload photo or skip
  3. Land on dashboard with “Profile incomplete” reminder
  4. Go to Profile Edit → add affinities using search + category browse
  5. Affinities appear on profile page
  6. Attempt to access directory → redirected with “pending verification” message
  7. (Staff verifies account in Lookup Portal)
  8. Refresh → access directory → search by affinity → view another Champion’s profile

Sub-Phase 1.5: Dashboard & Admin

Goal: Champions see personalized dashboard; Staff can verify Champions.

Status: ✅ COMPLETE (December 19, 2025)

Prerequisites: Portal Admin role must be implemented (see AUTH_AND_ROLES_SYSTEM.md)

Portal Admin Role (December 2025): ✅ Complete

Deliverables:

Note: The Champion-facing dashboard (Cp::DashboardController) is part of the Champion Portal itself (accessed via alumnichampions.com). This phase focused on the admin-side verification tools in the Lookup Portal. Champion-facing features continue in Phase 1.4 (Profile & Directory).

Implementation Notes:

Files Created:

Routes Added:

Test Coverage: 41 new tests (18 verifications + 23 districts)

Important: Pending BUID Verification (Security Feature)

As of December 2025, champions who confirm their education via name-based match (not email match) have their selected BUID stored in pending_buid instead of auto-linking. This creates two verification workflows:

Scenario Staff Action
Champion has pending_buid Review pending match → Approve (link BUID) or Reject
Champion has no match Search alumni manually → Link BUID

The verification queue should:

  1. Show pending matches first — Champions with pending_buid set, ordered by signup date
  2. Display the pending alumni record details alongside champion info
  3. Provide “Approve” (links BUID, sets champion_verified) and “Reject” (clears pending_buid) actions
  4. Fall back to manual BUID search for champions without pending matches

Database columns used:

Model helper:

Acceptance Test:

  1. Champion logs in → sees dashboard with their name, region (deferred to Phase 1.4)
  2. Staff logs into Lookup Portal → Verification Queue → sees pending Champions ✅
  3. If champion has pending_buid: Staff sees suggested alumni match → clicks “Approve” → BUID linked ✅
  4. If champion has no pending_buid: Staff searches alumni by name → selects match → clicks “Link BUID” ✅
  5. Champion’s status changes to “champion_verified” ✅
  6. Champion can now access directory (directory is Phase 1.4)

Sub-Phase 1.6: Dashboard, Directory & Metrics

Goal: Complete the Staging MVP — champion-facing dashboard, enhanced directory filters, and activity tracking infrastructure.

Status: ✅ COMPLETE

Prerequisites: Phase 1.5 complete

Replaces: Phase 1B (Metrics Foundation) — absorbed and scoped for MVP

Related Documents:

Why This Sub-Phase Exists:

Phase 1B was originally planned as a separate metrics-only phase. We’ve absorbed its core infrastructure into Sub-Phase 1.6 because:

  1. Dashboard and Directory are nearly complete — Phase 1.4 built 85% of what was needed
  2. Activity tracking should exist from launch — Can’t measure impact without infrastructure
  3. Simpler phase structure — Keeps all “staging MVP” work in one place

Jobs to Be Done Alignment:

Job How This Sub-Phase Addresses It
C1: Find My Tribe Enhanced directory filters make it easier to find Champions with shared background
E2: See the Big Picture Staff dashboard shows Champion activity metrics at a glance

Internal Sub-Phases (1.6.1, 1.6.2, 1.6.3):

Sub-Phase Name Est. Time
1.6.1 Activity Tracking Infrastructure 1–2 days
1.6.2 Champion Dashboard Completion 1–2 days
1.6.3 Directory Enhancements 2–3 days

Sub-Phase 1.6.1: Activity Tracking Infrastructure

Goal: Create the data infrastructure to track Champion activities from day one.

Deliverables:

Event Types (Initial):

Event Description Recorded When
login Champion logged in On sign_in callback
profile_edit Champion updated their profile On profile#update
directory_search Champion searched directory On directory#index with params
profile_view Champion viewed another profile On directory#show

Database Schema:

create_table :cp_activity_events do |t|
  t.references :cp_champion, null: false, foreign_key: true
  t.string :event_type, null: false
  t.jsonb :metadata, default: {}
  t.datetime :occurred_at, null: false
  t.timestamps
end

add_index :cp_activity_events, [:event_type, :occurred_at]
add_index :cp_activity_events, [:cp_champion_id, :event_type, :occurred_at]

ActivityRecorder Service:

# Usage: Cp::ActivityRecorder.record(champion, :login)
# Usage: Cp::ActivityRecorder.record(champion, :profile_view, target_id: other_champion.id)
# Usage: Cp::ActivityRecorder.record(champion, :directory_search, filters: { district_id: 1 })

module Cp
  class ActivityRecorder
    VALID_EVENTS = %w[login profile_edit directory_search profile_view].freeze
    
    def self.record(champion, event_type, metadata = {})
      return unless VALID_EVENTS.include?(event_type.to_s)
      
      champion.activity_events.create!(
        event_type: event_type.to_s,
        metadata: metadata,
        occurred_at: Time.current
      )
    end
  end
end

Acceptance Test:

  1. Log in as Champion → activity_event created with type login
  2. Edit profile → activity_event created with type profile_edit
  3. Search directory → activity_event created with type directory_search and filter metadata
  4. View another Champion’s profile → activity_event created with type profile_view

Sub-Phase 1.6.2: Champion Dashboard Completion

Goal: Complete the champion-facing dashboard with real community stats and district preview.

Status: ✅ COMPLETE

Current State:

Deliverables:

Dashboard Sections (Updated):

┌─────────────────────────────────────────────────────────────────────┐
│ Welcome Banner (existing)                                            │
│ - Personalized greeting                                              │
│ - Profile completion prompt (if incomplete)                          │
├─────────────────────────────────────────────────────────────────────┤
│ Community Snapshot (unified block)                                   │
│ - District name + community counts                                   │
│ - Alumni in your district preview                                    │
│   (degree + class year shown, fallback: "Belmont Alum")              │
│ - CTA: Browse all alumni in your district (or invite others)         │
├─────────────────────────────────────────────────────────────────────┤
│ Quick Actions (existing)                                             │
├─────────────────────────────────────────────────────────────────────┤
│ Recent Activity (improved placeholder)                               │
│ "Your activity will appear here as you explore and connect."         │
│ [Browse the Directory →] [Complete Your Profile →]                   │
└─────────────────────────────────────────────────────────────────────┘

Controller Changes:

# app/controllers/cp/dashboard_controller.rb
def show
  @champion = current_cp_champion
  
  # Record login activity
  Cp::ActivityRecorder.record_login_once_per_session(@champion, session)
  
  # Community stats
  @total_champions = Cp::Champion.verified.count
  @district_champions_count = if @champion.district.present?
    Cp::Champion.verified
                .location_visible_to(@champion)
                .where(district: @champion.district)
                .count
  else
    0
  end
  
  # Preview cards (3 random alumni from district, excluding self)
  @district_preview = if @champion.district.present?
    Cp::Champion.verified
                .location_visible_to(@champion)
                .where(district: @champion.district)
                .where.not(id: @champion.id)
                .includes(alumni: { degrees: :major })
                .order("RANDOM()")
                .limit(3)
  else
    []
  end
end

Acceptance Test:

  1. Log in as verified Champion → see community stats (total alumni, district count)
  2. See preview cards of up to 3 other alumni in district
  3. Click “Browse all alumni in your district” → redirected to directory with district pre-filtered
  4. Check activity_events table → login event recorded

Sub-Phase 1.6.3: Directory Enhancements

Goal: Add the most valuable directory filters and sorting options based on competitive analysis.

Current State:

Deliverables:

New Filters:

Sorting:

Activity Tracking:

Graduation Year Filter (Hierarchical Dropdown):

The graduation year filter uses a hierarchical dropdown where selecting a decade header selects all years within it:

┌─────────────────────────────┐
│ Graduation Year         ▼  │
├─────────────────────────────┤
│ ▶ All Years                 │
│ ▶ 2020s                     │
│   ├── 2024                  │
│   ├── 2023                  │
│   ├── 2022                  │
│   ├── 2021                  │
│   └── 2020                  │
│ ▶ 2010s                     │
│   ├── 2019                  │
│   ├── 2018                  │
│   └── ...                   │
│ ▶ 2000s                     │
│ ▶ 1990s                     │
│ ▶ Earlier                   │
└─────────────────────────────┘

Behavior:

Filter Logic (Backend):

# Decade selection: ?grad_decade=2020
# Individual year: ?grad_year=2024
# Multiple years: ?grad_years[]=2022&grad_years[]=2023

if params[:grad_decade].present?
  decade_start = params[:grad_decade].to_i
  decade_end = decade_start + 9
  query = query.joins(alumni: :degrees)
               .where("EXTRACT(YEAR FROM degrees.degree_date) BETWEEN ? AND ?", 
                      decade_start, decade_end)
               .distinct
elsif params[:grad_year].present?
  query = query.joins(alumni: :degrees)
               .where("EXTRACT(YEAR FROM degrees.degree_date) = ?", params[:grad_year])
               .distinct
elsif params[:grad_years].present?
  query = query.joins(alumni: :degrees)
               .where("EXTRACT(YEAR FROM degrees.degree_date) IN (?)", params[:grad_years])
               .distinct
end

College Filter:

# Load colleges from degrees of verified champions
@colleges = College.joins(majors: { degrees: { alumni: :cp_champion } })
                   .where(cp_champions: { verification_status: :champion_verified })
                   .distinct
                   .order(:college_name)

District Filter (Autocomplete):

Important: With 820 districts, a dropdown would be terrible UX. Reuse the existing district_autocomplete_controller.js Stimulus controller, which is already used in the Lookup Portal alumni search.

<!-- Reuse the existing district autocomplete pattern from alumni/search.html.erb -->
<div data-controller="district-autocomplete" class="relative">
  <label for="district_name">District</label>
  <input type="hidden" name="district_id" id="district_id" value="<%= params[:district_id] %>"
         data-district-autocomplete-target="districtCode">
  <input type="text" 
         id="district_name" 
         data-district-autocomplete-target="input"
         data-action="input->district-autocomplete#search keydown->district-autocomplete#navigate"
         value="<%= @selected_district&.display_name %>"
         placeholder="Type to search districts..."
         autocomplete="off">
  
  <% if params[:district_id].present? %>
    <button type="button" data-action="district-autocomplete#clear">×</button>
  <% end %>
  
  <div data-district-autocomplete-target="results" class="absolute z-10 hidden"></div>
</div>

API Endpoint (existing): /api/districts?q= — Returns JSON with code, display_name, name, state, region

Sort Dropdown:

<select name="sort">
  <option value="name" <%= 'selected' if params[:sort] == 'name' %>>Name A-Z</option>
  <option value="recent" <%= 'selected' if params[:sort] == 'recent' %>>Recently Joined</option>
</select>

# Controller
case params[:sort]
when "recent"
  query.order(created_at: :desc)
else
  query.order(:first_name, :last_name)
end

Activity Tracking:

# In DirectoryController#index
def index
  # ... existing filter logic ...
  
  # Record search activity (only if authenticated and has filters)
  if current_cp_champion && params.except(:controller, :action, :page).any?
    Cp::ActivityRecorder.record(
      current_cp_champion,
      :directory_search,
      filters: params.slice(:q, :district_id, :district, :industry, :role,
                           :affinity_code, :grad_years, :grad_year, :grad_decade,
                           :college_id, :college_code).to_h
    )
  end
end

# In DirectoryController#show
def show
  @champion = Cp::Champion.verified.find(params[:id])
  
  # Record profile view (don't record viewing own profile)
  if current_cp_champion && current_cp_champion != @champion
    Cp::ActivityRecorder.record(
      current_cp_champion,
      :profile_view,
      target_champion_id: @champion.id
    )
  end
end

Acceptance Test:

  1. As verified Champion, visit directory
  2. Filter by graduation decade (2010s) → shows all 2010-2019 graduates
  3. Filter by specific year (2015) → shows only 2015 graduates
  4. Filter by college → shows Champions from that college
  5. Type in district filter → autocomplete shows matching districts
  6. Select district → shows Champions in that district
  7. Sort by “Recently Joined” → newest Champions first
  8. View another Champion’s profile → profile_view event recorded
  9. Check activity_events → directory_search events have filter metadata

Key Decisions (Sub-Phase 1.6):

Decision Choice Rationale
Area terminology “District” not “Region” District = metro area (Nashville), Region = too broad (Southeast)
Graduation filter style Hierarchical dropdown Can select decade (all years) OR individual year
District filter style Autocomplete 820 districts — dropdown would be terrible UX; reuse existing district_autocomplete_controller.js
Initial sort options Name A-Z, Recently Joined Simple MVP; can add “Recently Active” after tracking is live
Dashboard preview 3 cards + count + link Shows community exists without overwhelming
Phase structure Absorb 1B into 1.6 Cleaner MVP grouping, no metrics-only phase

Scope:

In Scope:

Out of Scope (Phase 6 / Future):

Definition of Success (Sub-Phase 1.6):

Staging MVP Readiness:

Tests to Create (Sub-Phase 1.6):

Model Tests (test/models/cp/activity_event_test.rb):

Service Tests (test/services/cp/activity_recorder_test.rb):

Controller Tests:

Documentation Updates (Sub-Phase 1.6):

Relationship to Other Phases:

Builds on:

Enables:

Supersedes:


3. Questions to Answer Before Starting

3.1 Already Resolved (Tier 1 Decisions)

Question Decision Source
What is the onboarding flow? Progressive Account Creation (name + email first) DECISIONS.md §2.1
Is BUID required at signup? No — Optional + Two-tier verification DECISIONS.md §2.2
What is the account data model? Single cp_champions table with verification_status enum DECISIONS.md §2.3
Which SSO providers? Google, Apple, Facebook DECISIONS.md §2.12
How is location captured? ZIP-first — ZIP → city/state/district/region DECISIONS.md §2.10
How are photos handled? Separate: cp_champions.photo for Champion Portal DECISIONS.md §2.9
Mobile or desktop first? Mobile-first DECISIONS.md §2.5
Table naming convention? cp_ prefix for Champion Portal tables DECISIONS.md §2.6

3.2 Resolved Before Starting (December 4, 2025)

Question Decision Notes
How should ZIP → District mapping be seeded? CSV import — Seed from zip_district_region.csv Rake task: rails champion_portal:seed_geographic_data
What districts/regions exist initially? 820 districts, 7 regions, with 8 highlighted See data details below
How should verification queue be organized in Lookup? New /champions namespace in Lookup Portal Implemented December 2025 with dedicated layout
Password requirements? 8+ characters, at least 1 letter and 1 number Balanced security/friction
Session duration? 7 days default, 30 days with “Remember me” Mobile-friendly with security option

Geographic Data (Imported December 5, 2025):

3.3 Needs Clarification from Stakeholders

Question Who Decides Impact
Pilot group composition Engagement Team Which Champions get first access
Verification turnaround SLA Engagement Team User messaging about wait time
Profile fields required vs optional Product/Engagement Onboarding friction
Champion role quiz — required or optional? Product Onboarding flow

4. Scope

4.1 In Scope

Area Deliverables
Authentication Email signup, password reset, email verification, Google/Apple/Facebook SSO
Account model cp_champions table with verification status enum
Profile Personal info, contact info, employment, affinities, photo upload
Privacy controls Per-field visibility settings
Location ZIP-based city/district/region assignment
Directory Search by name, city, grad year, college, major, industry
Dashboard Welcome, role display, quick actions, regional events (placeholder), regional posts (placeholder)
Admin: Verification Engagement Team can search alumni, link BUID to Champion account
Regional structure Districts and regions tables, ZIP mapping

4.2 Out of Scope (Later Phases)

Feature Phase
Event submission/calendar Phase 2
Story submission Phase 2
Mentorship matching Phase 2
Discussion boards Phase 3
Direct messaging Phase 4
Visit mode Phase 5
Map view Phase 5
Reporting dashboards Phase 6

5. Definition of Success

5.1 Core Acceptance Criteria

Criterion Validation
Champion can sign up with email End-to-end test passes
Champion can sign up with Google SSO End-to-end test passes
Champion can sign up with Apple SSO End-to-end test passes
Champion can sign up with Facebook SSO End-to-end test passes
Email verification flow works User receives email, clicks link, can set password
Password reset works User can request and complete reset
Email Verified user can edit own profile Access control enforced
Email Verified user cannot see directory Access control enforced
Champion Verified user can browse directory Directory search returns results
Champion Verified user appears in directory Searchable by name, city, etc.
ZIP code → city/district/region assignment works Auto-populated on profile
Profile changes are logged crm_data_changes records created (unified CRM sync)
Staff can verify Champions in Lookup Portal Verification queue functional
Staff can search alumni and link BUID BUID linking works
Dashboard displays for logged-in Champion Personalized greeting, role, region

5.2 Pilot Success Metrics

Metric Target
Signup completion rate (email verified) >80%
Profile completion rate >70%
Time from signup to Champion Verified <1 business day
Directory searches per pilot user 2+ during pilot

6. Tests to Create

6.1 Model Tests

Model Test Cases
Cp::Champion Valid factory, validation errors, verification status transitions, associations
Cp::Champion ZIP → city/state/district/region lookup
Cp::Champion Photo attachment (ActiveStorage)
Cp::ProfileChange Auto-creates on attribute changes
Cp::ProfileChange Tracks field_name, old_value, new_value, changed_by

6.2 Controller Tests

Controller Test Cases
Cp::RegistrationsController Create account with valid data, reject invalid email, reject duplicate email
Cp::RegistrationsController SSO callback creates account, links existing account
Cp::SessionsController Login with password, login with SSO, logout
Cp::SessionsController Reject unverified users, reject invalid credentials
Cp::ChampionsController Email Verified can edit own profile, cannot edit others
Cp::ChampionsController Champion Verified can view own profile
Cp::DirectoryController Champion Verified can search, Email Verified cannot
Cp::DirectoryController Search by name, city, grad year, college, industry
Cp::DashboardController Returns dashboard for logged-in Champion
Admin::VerificationsController Staff can view queue, search alumni, link BUID
Admin::VerificationsController Queue shows champions with pending_buid first
Admin::VerificationsController Staff can approve pending_buid (links BUID, sets verified)
Admin::VerificationsController Staff can reject pending_buid (clears pending_buid)

6.3 Feature/Integration Tests

Feature Test Cases
Email signup flow Complete journey: signup → email → verify → set password → profile
Google SSO flow Click Google → authorize → account created → profile completion
Apple SSO flow Click Apple → authorize → account created → profile completion
Facebook SSO flow Click Facebook → authorize → account created → profile completion
Password reset Request reset → email → click link → new password → login
Directory search Log in as Champion Verified → search by name → view profile
Verification workflow (pending) Staff logs into Lookup → sees pending match → approves → BUID linked
Verification workflow (manual) Staff logs into Lookup → searches alumni → links BUID → champion verified

6.4 Permissions Tests

Scenario Expected Behavior
Unverified user tries to log in Rejected with “verify your email” message
Email Verified user visits directory Redirected with “verification pending” message
Email Verified user edits own profile Allowed
Champion Verified user visits directory Allowed
Non-staff user visits verification queue Rejected (403)

6.5 Data Validation Tests

Data Test Cases
Email Unique, valid format, required
ZIP code Valid 5-digit format, exists in database/lookup
Password Meets complexity requirements
Profile fields Required fields enforced, optional fields nullable

7. Documentation Updates

After completing Phase 1, update the following documents:

7.1 Required Updates

Document Changes
../README.md Mark Phase 1 as complete, update prerequisites
../development/DATA-ARCHITECTURE.md Finalize cp_champions schema, add actual column list
../development/DECISIONS.md Record any new decisions made during implementation
../STAKEHOLDER-OVERVIEW.md Update pilot status
../../features/CHAMPION_PORTAL.md Create implemented feature doc (move from planning)

7.2 New Documentation to Create

Document Purpose
docs/features/CHAMPION_PORTAL.md Document implemented functionality
docs/development/CHAMPION_VERIFICATION_WORKFLOW.md Staff workflow guide
Champion Portal user guide (for pilots) End-user documentation

7.3 Test Fixtures to Add

Fixture Purpose
cp_champions.yml Test champions in various verification states
districts.yml Test districts (Nashville, etc.)
regions.yml Test regions (Southeast, etc.)
zip_codes.yml Test ZIP codes mapped to districts

For detailed specifications on each feature area, see: