alumni_lookup

Phase 1C: Champion Role Selection & Quiz

Champion Portal Development Phase 1C

Estimated Effort: 1–2 weeks
Focus: Role Selection UI, Quiz Integration, Service Extraction

Prerequisites: Phase 1.4 complete (Profile Wizard & Edit)

Related Documents:


Table of Contents

  1. Overview
  2. Why This Phase Exists
  3. Sub-Phases
  4. Service Extraction Strategy
  5. Scope
  6. Definition of Success
  7. Tests to Create
  8. Documentation Updates

1. Overview

Phase 1C integrates Champion Role Selection and the “Find Your Role” quiz into the Champion Portal’s profile wizard and profile edit flows. This enables new champions to discover and select their role as part of onboarding, while existing champions can revisit their role or retake the quiz anytime.

After Phase 1C, Champions can:

Key Principle: Reuse existing code from Champion Signup. Extract to services so both the public signup flow (champions.bualum.co) and the portal profile wizard share the same logic and data.


2. Why This Phase Exists

Problem

The current profile wizard (Phase 1.4) collects profile data but does not capture Champion role. The role selection and quiz currently only exist in the public signup flow at champions.bualum.co, which is designed for anonymous prospects—not authenticated portal users.

Opportunity

Champions care about their role identity. It helps them:

  1. Understand how they contribute to the community
  2. Connect with others in similar roles
  3. See role-specific content and opportunities (future phases)

Design Goal

“What kind of Champion are you?” should be a welcoming, optional, fun step—not a barrier. The quiz is a lighthearted way to discover your role, not a test.


3. Sub-Phases

Phase 1C has 3 sub-phases:

Sub-Phase Name Est. Time  
1C.1 Service Extraction 1–2 days ✅ Complete
1C.2 Profile Wizard Integration 2–3 days ✅ Complete
1C.3 Profile Edit Integration 1–2 days 🚧 Pending

Sub-Phase 1C.1: Service Extraction ✅ COMPLETE

Completed: December 12, 2025

Goal: Extract quiz logic and role data from ChampionSignupsHelper into reusable services that both the public signup flow and the portal can use.

Rationale: The existing Champion Signup flow must continue working unchanged until the portal is fully launched. By extracting logic to services and having both flows call those services, we:

  1. Eliminate code duplication
  2. Ensure consistency between flows
  3. Make future updates easier

Deliverables:

Service Design:

# app/services/champion_role_service.rb
class ChampionRoleService
  ROLES = {
    'connection_advisor' => {
      letter: 'a',
      color: 'fountainblue',
      emoji: '🤝',
      title: 'Connection Advisor',
      seal: 'connection-advisor-seal',
      description: 'You excel at connecting people and resources...',
      short_desc: 'You love connecting people and resources.',
      detailed: {
        tagline: 'Support the network. Open doors.',
        activities: ['Share job leads', 'Make introductions', 'Offer career advice'],
        summary: 'You help Bruins grow professionally...'
      }
    },
    # ... other roles
  }.freeze

  def self.all_roles
    ROLES
  end

  def self.role(key)
    ROLES[key.to_s]
  end

  def self.role_by_letter(letter)
    ROLES.find { |_, r| r[:letter] == letter }&.first || 'community_builder'
  end

  def self.role_color(key)
    ROLES.dig(key.to_s, :color) || 'gray'
  end

  def self.role_title(key)
    ROLES.dig(key.to_s, :title) || key.to_s.humanize.titleize
  end

  def self.valid_role?(key)
    ROLES.key?(key.to_s)
  end

  def self.narrative_for(roles)
    # Returns the combined narrative for one or more roles
    # ... (existing logic from helper)
  end
end
# app/services/champion_quiz_service.rb
class ChampionQuizService
  QUESTIONS = [
    {
      text: "A friend you admire takes a bold leap — what's your response?",
      options: {
        'a' => 'Introduce them to someone who's walked a similar path.',
        'b' => 'Share their journey to inspire others.',
        'c' => 'Organize a small gathering to cheer them on.',
        'd' => 'Ask how you can practically support their next step.'
      }
    },
    # ... 6 more questions
  ].freeze

  def self.questions
    QUESTIONS
  end

  def self.question_count
    QUESTIONS.length
  end

  # Calculate the winning role(s) from quiz answers
  # @param answers [Hash] { 'q0' => 'a', 'q1' => 'c', ... }
  # @return [String] role key (e.g., 'connection_advisor')
  def self.calculate_result(answers)
    return 'community_builder' if answers.blank?
    
    counts = Hash.new(0)
    answers.each_value { |letter| counts[letter] += 1 }
    result_letter = counts.max_by { |_, count| count }&.first
    ChampionRoleService.role_by_letter(result_letter)
  end

  # Get all top roles (may be multiple if tied)
  # @return [Array<Array>] [[role_key, color], ...]
  def self.calculate_top_roles(answers)
    return [['community_builder', 'belmontblue']] if answers.blank?
    
    counts = role_counts(answers)
    max = counts.values.map { |v| v[:count] }.max
    counts.select { |_, v| v[:count] == max }
          .map { |role, v| [role, v[:color]] }
  end

  def self.role_counts(answers)
    counts = Hash.new { |h, k| h[k] = { count: 0, color: ChampionRoleService.role_color(k) } }
    
    answers.each_value do |letter|
      role = ChampionRoleService.role_by_letter(letter)
      counts[role][:count] += 1 if role
    end
    
    counts
  end
end

Migration Path:

  1. Create new service files
  2. Update ChampionSignupsHelper to delegate to services:
    def champion_roles
      ChampionRoleService.all_roles
    end
       
    def champion_questions
      ChampionQuizService.questions
    end
       
    def champion_result_role(answers)
      ChampionQuizService.calculate_result(answers)
    end
    
  3. Run existing tests to verify no regressions
  4. Create Cp::ChampionRolesHelper with same delegation pattern

Acceptance Test:

# Existing signup flow still works
ChampionSignupsHelper.new.champion_roles.keys
# => ['connection_advisor', 'digital_ambassador', 'community_builder', 'giving_advocate']

# New service works identically
ChampionRoleService.all_roles.keys
# => ['connection_advisor', 'digital_ambassador', 'community_builder', 'giving_advocate']

# Quiz calculation works
ChampionQuizService.calculate_result({ 'q0' => 'a', 'q1' => 'a', 'q2' => 'b' })
# => 'connection_advisor'

Sub-Phase 1C.2: Profile Wizard Integration ✅ COMPLETE

Completed: December 12, 2025

Goal: Add a “Champion Role” step to the profile wizard with role selection and optional quiz.

Deliverables:

Wizard Step Position:

The champion role step now comes after basic_info and before location_contact:

Step Name Required
1 basic_info Yes
2 champion_role No (new)
3 location_contact No
4 professional No
5 affinities No
6 photo No

Step Design:

┌─────────────────────────────────────────────────────────────────┐
│  Step 2 of 6                                    [Skip for now]  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  What kind of Champion are you?                                 │
│                                                                 │
│  Select the role that best fits how you like to contribute:    │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 🤝 Connection Advisor                               ○   │   │
│  │     You love connecting people and resources.           │   │
│  │     [More info ▼]                                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 🙌 Digital Ambassador                               ○   │   │
│  │     You share stories and celebrate others online.      │   │
│  │     [More info ▼]                                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 👋 Community Builder                                ○   │   │
│  │     You bring Bruins together and create community.     │   │
│  │     [More info ▼]                                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 🫶 Giving Advocate                                  ○   │   │
│  │     You inspire generosity and giving back.             │   │
│  │     [More info ▼]                                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ─────────────────────────────────────────────────────────────  │
│  Not sure? Take our fun quiz to discover your role! →          │
│                                                                 │
│                                          [Back]  [Next]         │
└─────────────────────────────────────────────────────────────────┘

Quiz Flow (within wizard):

When user clicks “Take our fun quiz”:

  1. Redirect to /profile/wizard/quiz/1 (question 1)
  2. 7 questions, each on its own page
  3. After question 7, show results page with recommendation
  4. “Use this role” button returns to wizard step with role pre-selected
  5. “Choose a different role” shows all roles again

Database Migration:

class AddQuizAnswersToCpChampions < ActiveRecord::Migration[7.1]
  def change
    add_column :cp_champions, :quiz_answers, :jsonb, default: {}
    add_column :cp_champions, :quiz_completed_at, :datetime
  end
end

Controller Changes:

# app/controllers/cp/profile_wizard_controller.rb
STEPS = %w[basic_info champion_role location_contact professional affinities photo].freeze

# Add quiz actions
def quiz
  @question_index = params[:question].to_i - 1
  @question = ChampionQuizService.questions[@question_index]
  
  if @question.nil?
    redirect_to cp_profile_wizard_path(step: 'champion_role')
    return
  end
  
  @total_questions = ChampionQuizService.question_count
end

def submit_quiz_answer
  @question_index = params[:question].to_i - 1
  session[:quiz_answers] ||= {}
  session[:quiz_answers]["q#{@question_index}"] = params[:answer]
  
  next_question = @question_index + 2 # 1-indexed
  if next_question > ChampionQuizService.question_count
    redirect_to cp_profile_wizard_quiz_results_path
  else
    redirect_to cp_profile_wizard_quiz_path(question: next_question)
  end
end

def quiz_results
  @answers = session[:quiz_answers] || {}
  @recommended_role = ChampionQuizService.calculate_result(@answers)
  @top_roles = ChampionQuizService.calculate_top_roles(@answers)
  @role_counts = ChampionQuizService.role_counts(@answers)
end

def select_quiz_role
  role = params[:role]
  if ChampionRoleService.valid_role?(role)
    @champion.update(
      primary_role: role,
      quiz_answers: session[:quiz_answers] || {},
      quiz_completed_at: Time.current
    )
    session.delete(:quiz_answers)
  end
  redirect_to cp_profile_wizard_path(step: 'location_contact')
end

Routes Addition:

# In champions subdomain routes
scope module: 'cp' do
  # Existing wizard routes
  get 'profile/wizard/:step', to: 'profile_wizard#show', as: :cp_profile_wizard
  patch 'profile/wizard/:step', to: 'profile_wizard#update'
  
  # Quiz routes (within wizard flow)
  get 'profile/wizard/quiz/:question', to: 'profile_wizard#quiz', as: :cp_profile_wizard_quiz
  post 'profile/wizard/quiz/:question', to: 'profile_wizard#submit_quiz_answer'
  get 'profile/wizard/quiz-results', to: 'profile_wizard#quiz_results', as: :cp_profile_wizard_quiz_results
  post 'profile/wizard/select-role', to: 'profile_wizard#select_quiz_role', as: :cp_profile_wizard_select_role
end

Sub-Phase 1C.3: Profile Edit Integration

Goal: Allow champions to view/change their role and retake the quiz from profile edit.

Deliverables:

Profile Edit Section Design:

┌─────────────────────────────────────────────────────────────────┐
│  Champion Role                                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Your Current Role:                                             │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 🤝 Connection Advisor                                   │   │
│  │     "You help Bruins grow professionally by making the  │   │
│  │     right connections at the right time."               │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  [Change Role]              [Retake Quiz to Discover Your Role] │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Controller Changes:

Add to Cp::ProfileController:

def role
  @current_role = @champion.primary_role
  @all_roles = ChampionRoleService.all_roles
end

def update_role
  if ChampionRoleService.valid_role?(params[:role])
    @champion.update(primary_role: params[:role])
    redirect_to cp_profile_edit_path, notice: "Your Champion role has been updated!"
  else
    redirect_to cp_profile_role_path, alert: "Please select a valid role."
  end
end

def quiz
  # Separate quiz flow for profile edit (not wizard)
  @question_index = params[:question].to_i - 1
  @question = ChampionQuizService.questions[@question_index]
  # ... similar to wizard quiz
end

Routes Addition:

# Profile role management
get 'profile/role', to: 'profile#role', as: :cp_profile_role
patch 'profile/role', to: 'profile#update_role'
get 'profile/role/quiz/:question', to: 'profile#quiz', as: :cp_profile_role_quiz
post 'profile/role/quiz/:question', to: 'profile#submit_quiz_answer'
get 'profile/role/quiz-results', to: 'profile#quiz_results', as: :cp_profile_role_quiz_results
post 'profile/role/select-role', to: 'profile#select_quiz_role', as: :cp_profile_role_select

4. Service Extraction Strategy

Principle: Single Source of Truth

Both the existing Champion Signup flow and the new Portal flows should use the same service classes for:

Migration Approach

  1. Create services first — New files, no changes to existing code
  2. Test services — Ensure they produce identical output to existing helper
  3. Update helper to delegate — Existing code now uses services under the hood
  4. Run existing tests — Verify no regressions in signup flow
  5. Create CP helper — New helper for portal that also uses services
  6. Build new views — Portal wizard and edit views use shared partials where possible

Shared Partials

These partials can be shared (or extracted to shared location):

Partial Current Location Shared Usage
Role card champions/champion_signups/steps/_role.html.erb Extract to shared/_champion_role_card.html.erb
Quiz question champions/champion_signups/steps/_question.html.erb Extract to shared/_champion_quiz_question.html.erb
Quiz results champions/champion_signups/steps/_results.html.erb Extract to shared/_champion_quiz_results.html.erb
Seal SVG champions/champion_signups/_seal.svg.erb Already reusable
Score chart champions/champion_signups/steps/_score_chart.html.erb Extract to shared

5. Scope

In Scope

Out of Scope


6. Definition of Success

Functional Acceptance Criteria

Wizard Flow:

Profile Edit Flow:

Service Extraction:

Metrics to Track (for Phase 1B dashboard)

| Metric | Measurement | |——–|————-| | Role selection rate | % of champions with primary_role set | | Quiz completion rate | % of champions with quiz_completed_at | | Role distribution | Count per role type | | Quiz-driven selections | % who used quiz vs direct selection |


7. Tests to Create

Service Tests

test/services/champion_role_service_test.rb

test/services/champion_quiz_service_test.rb

Controller Tests

test/controllers/cp/profile_wizard_controller_test.rb (additions)

test/controllers/cp/profile_controller_test.rb (additions)

Integration Tests

test/integration/cp/champion_role_wizard_test.rb

Regression Tests

test/controllers/champions/champion_signups_controller_test.rb


8. Documentation Updates

After Phase 1C completion, update:


Appendix: Existing Code References

Files to Extract From

Files to Create

Files to Modify