alumni_lookup

Sub-Phase 1.12: Community Foundation

Champion Portal Development Sub-Phase 1.12

Estimated Effort: 2–3 weeks
Focus: Database schema and models for emergent communities

Prerequisites: Phase 1.10 (Community News) and Phase 1.11 (Admin Events) complete

Related Documents:


Implementation Progress

Sub-Phase Name Status Notes
1.12.1 Database Schema ✅ Complete 3 migrations created
1.12.2 Community & Membership Models ✅ Complete Models, validations, associations
1.12.3 Suggestion Model & Matching Service ✅ Complete 70+ tests passing
1.12.4 Profile Update Hooks ✅ Complete Callbacks on Champion and Affinity
1.12.5 News/Events Migration ✅ Complete Community targeting for news/events
1.12.6 Admin Communities UI ✅ Complete Lookup Portal admin page
1.12.7 Champion Community UI ✅ Complete My Communities, suggestions, join/leave

What Was Implemented (1.12.1-1.12.4)

Database:

Models:

Services:

Callbacks:

Tests Created:

Fixtures:

What Was Implemented (1.12.5-1.12.7)

1.12.5 - News/Events Community Targeting:

1.12.6 - Admin Communities UI:

1.12.7 - Champion Community UI:

Controller Files:

View Files:

Test Files:

Bootstrap Rake Tasks:

Key Design Decisions (Final):


Table of Contents

  1. Overview
  2. Why This Sub-Phase Exists
  3. Design Decisions
  4. Internal Sub-Phases
  5. Schema Design
  6. Community Types
  7. Threshold Logic
  8. Scope
  9. Definition of Success
  10. Tests to Create
  11. Documentation Updates
  12. Questions Resolved

1. Overview

Phase 1.12 establishes the community foundation — the database schema and business logic that enables the “emergent community” model where communities form organically when enough Champions share an attribute.

The Core Insight:

Not all Champions fit neatly into metro districts. The Champion Portal must serve:

Alumni Bucket Challenge Community Solution
Metro expats Far from Nashville but in major city District community (Nashville, Atlanta, etc.)
Sparse district alumni Only person in their city College, Major, or Affinity communities connect them
Nashville locals Many nearby, need more specific belonging College, Major, Affinity communities add specificity

After Phase 1.12:


2. Why This Sub-Phase Exists

The “Place to Belong” Problem

From the planning conversation:

“One of our main tenants is the idea that Alumni find a place of belonging — a place ‘for them.’”

Districts alone don’t create belonging for everyone. A Champion who is the only Bruin in Boise needs a different kind of community — perhaps connecting through their college (Entertainment & Music Business) or affinity (Greek life, athletics).

The Emergent Community Model

Communities aren’t pre-created for every possible combination. Instead:

  1. Attributes exist — Districts, colleges, majors, affinities are already in the system
  2. Threshold triggers creation — When 3+ Champions share an attribute, a community can form
  3. Champions get auto-assigned — When verified, Champions join communities they qualify for
  4. Content gets targeted — News/Events can be directed to specific communities

From JOBS-TO-BE-DONE.md:

Job C9: Feel Like I Belong

“When I’m far from Nashville or years past graduation, I want to feel part of something bigger, so I can maintain my Belmont identity as part of who I am.”

Communities are the mechanism for delivering belonging at scale.


3. Design Decisions

Decisions made during planning interview:

Decision Choice Rationale
Community threshold 3 Champions minimum Balance between too exclusive (5+) and too fragmented (2)
Community types district, college, major, affinity, industry Cover the main ways alumni connect
Slug generation Auto-generate with admin override Consistent, clean URLs with flexibility
Auto-assignment On Champion verification Ensures communities are populated
News/Events targeting community_id FK, null = global Simple “global if null” pattern
Multiple communities per Champion Yes — Champions can belong to many A Champion might be in Nashville District + Music Business College + Phi Mu Affinity
Community visibility Public (all can join) for MVP Private/Invite-only deferred to future

3.1 Future Considerations

Note: These items are recorded for future planning. They are NOT in scope for Phase 1.12 MVP.

CLC as “Community Leader Council”

Note: As of Phase 1.15.3, CLC now means “Community Leader Council” rather than “City Leadership Council” throughout the codebase.

Idea: Instead of associating CLCs with Districts/Regions only, CLCs could become general community leaders associated with any Community type.

Benefits:

Admin Requirement: Identify which communities do NOT have at least one CLC assigned, enabling targeted recruitment.

Target Phase: Phase 2+ (after Community Foundation proves out)

Community Visibility Settings

Idea: Allow communities to have different visibility/join settings:

Setting Behavior
Public Anyone can see and join (default)
Invite-Only Visible in browse, but requires invite or request-to-join
Private Hidden from browse, invite-only

Use Cases:

MVP Decision: All communities are Public for Phase 1.12-1.14. Visibility settings can be added in Phase 2+.


4. Internal Sub-Phases

Sub-Phase Name Est. Time Status
1.12.1 Database Schema 2–3 days ✅ Complete
1.12.2 Community & Membership Models 2–3 days ✅ Complete
1.12.3 Suggestion Model & Matching Service 2–3 days ✅ Complete
1.12.4 Profile Update Hooks 1 day ✅ Complete
1.12.5 News/Events Migration 1–2 days ✅ Complete
1.12.6 Admin Communities UI 2–3 days ✅ Complete
1.12.7 Champion Community UI 2–3 days ✅ Complete

Note: Original spec had different sub-phase breakdown. Updated to reflect actual implementation which includes suggestion-based model and profile hooks.


5. Schema Design

cp_communities Table

create_table :cp_communities do |t|
  # Identity
  t.string :name, null: false              # Display name: "Nashville", "Music Business"
  t.string :slug, null: false              # URL slug: "nashville", "music-business"
  t.text :description                      # Optional description for landing page
  
  # Type & Source
  t.integer :community_type, null: false   # district, college, major, affinity, industry
  
  # Source reference (polymorphic-ish but simpler)
  # Only ONE of these will be set, based on community_type
  t.references :district, foreign_key: true, null: true
  t.string :college_code, null: true       # References colleges.college_code
  t.string :major_code, null: true         # References majors.major_code
  t.references :affinity, foreign_key: true, null: true
  t.string :industry, null: true           # String value for industry communities
  
  # Thresholds & Status
  t.integer :member_count, default: 0      # Cached count for performance
  t.integer :threshold, default: 3         # Min members to be "active"
  t.boolean :active, default: false        # True when member_count >= threshold
  t.boolean :featured, default: false      # Show prominently in discovery
  
  # Customization (admin override)
  t.string :custom_name                    # Override auto-generated name
  t.text :custom_description               # Override default description
  
  # Metadata
  t.timestamps
  
  t.index :slug, unique: true
  t.index :community_type
  t.index [:community_type, :active]
  t.index :district_id
  t.index :college_code
  t.index :major_code
  t.index :affinity_id
  t.index :industry
end

cp_champion_communities Table (Join Table)

create_table :cp_champion_communities do |t|
  t.references :champion, null: false, foreign_key: { to_table: :cp_champions }
  t.references :community, null: false, foreign_key: { to_table: :cp_communities }
  
  # Membership metadata
  t.boolean :primary, default: false       # Champion's primary community of this type
  t.datetime :joined_at, default: -> { 'CURRENT_TIMESTAMP' }
  
  t.timestamps
  
  t.index [:champion_id, :community_id], unique: true
  t.index [:community_id, :champion_id]
end

Updates to Existing Tables

# Add community_id to cp_news_posts (if not already present from 1.10)
add_reference :cp_news_posts, :community, foreign_key: { to_table: :cp_communities }, null: true

# cp_events already has community_id from Phase 1.11

6. Community Types

Type Definitions

# app/models/cp/community.rb
enum :community_type, {
  district: 0,    # Geographic — tied to District model
  college: 1,     # Academic — tied to College (via college_code)
  major: 2,       # Academic — tied to Major (via major_code)
  affinity: 3,    # Interest — tied to Affinity model
  industry: 4     # Professional — based on Champion's industry field
}

Type Details

Type Source Name Generation Example
district districts table district.name “Nashville”, “Atlanta”
college colleges table college.college_name “College of Entertainment & Music Business”
major majors table major.major_desc “Music Business”
affinity affinities table affinity.name “Phi Mu”, “SAAC”
industry cp_champions.industry Industry value “Music Industry”, “Healthcare”

Source Attribute Mapping

Community Type Champion Attribute Used
district champion.district_id
college champion.alumni.degrees.*.major.college_code
major champion.alumni.degrees.*.major_code
affinity champion.alumni.affinities.*.id
industry champion.industry

7. Threshold Logic

The 3-Champion Threshold

A community becomes active when it has 3 or more members. This is tracked via:

  1. member_count — Cached integer updated on join/leave
  2. active — Boolean set to true when member_count >= threshold

Why 3?

Threshold Pros Cons
2 More communities exist Fragmented, conversations die
3 Balance — enough for conversation, not too exclusive
5 Higher quality communities Many Champions left out

Counter Cache Pattern

# In Cp::ChampionCommunity (join model)
after_create :increment_community_count
after_destroy :decrement_community_count

private

def increment_community_count
  community.with_lock do
    community.increment!(:member_count)
    community.update!(active: true) if community.member_count >= community.threshold
  end
end

def decrement_community_count
  community.with_lock do
    community.decrement!(:member_count)
    community.update!(active: false) if community.member_count < community.threshold
  end
end

8. Scope

In Scope

Feature Description
cp_communities table Full schema with all community types
cp_champion_communities table Join table with membership metadata
Cp::Community model Validations, scopes, type-specific logic
Cp::ChampionCommunity model Join model with counter cache
Auto-assignment service Assign Champions to communities on verification
Member count tracking Counter cache pattern
Active/inactive status Threshold-based activation
Admin community list View all communities in Lookup Portal
Update News targeting Add community_id FK to news posts
Update Events targeting Verify community_id FK works

Out of Scope (Deferred)

Feature Reason Target Phase
Community landing pages Separate UI phase Phase 1.13
Dynamic community creation Separate logic phase Phase 1.14
Community discovery UI Requires landing pages first Phase 1.13
Champion community preferences Nice-to-have Backlog
Community moderation Future need Phase 2+

9. Definition of Success

Functional Acceptance Criteria

Data Integrity Criteria

Performance Criteria


10. Tests to Create

Model Tests

# test/models/cp/community_test.rb
class Cp::CommunityTest < ActiveSupport::TestCase
  test "validates name presence"
  test "validates slug uniqueness"
  test "validates community_type presence"
  test "generates slug from name"
  test "district community requires district_id"
  test "college community requires college_code"
  test "active? returns true when member_count >= threshold"
  test "scope active returns only active communities"
  test "scope by_type filters by community_type"
end

# test/models/cp/champion_community_test.rb
class Cp::ChampionCommunityTest < ActiveSupport::TestCase
  test "validates uniqueness of champion + community"
  test "increments community member_count on create"
  test "decrements community member_count on destroy"
  test "activates community when threshold reached"
  test "deactivates community when below threshold"
end

Service Tests

# test/services/cp/community_assignment_service_test.rb
class Cp::CommunityAssignmentServiceTest < ActiveSupport::TestCase
  test "assigns champion to district community"
  test "assigns champion to college communities"
  test "assigns champion to major communities"
  test "assigns champion to affinity communities"
  test "assigns champion to industry community"
  test "creates community if it doesn't exist"
  test "does not duplicate existing memberships"
  test "handles champion with no affinities"
  test "handles champion with multiple degrees"
end

Controller Tests

# test/controllers/settings/champion_communities_controller_test.rb
class Settings::ChampionCommunitiesControllerTest < ActionDispatch::IntegrationTest
  test "index requires portal_admin"
  test "index lists all communities with counts"
  test "index filters by type"
  test "index filters by active status"
end

11. Documentation Updates

After completing Phase 1.12:


12. Questions Resolved

Question Answer Date
Community threshold? 3 Champions minimum Jan 7, 2026
How many community types? 5: district, college, major, affinity, industry Jan 7, 2026
Slug generation? Auto-generate with admin override Jan 7, 2026
When to assign Champions? On verification Jan 7, 2026
News/Events targeting pattern? community_id FK, null = global Jan 7, 2026
Can Champions be in multiple communities? Yes — one per type minimum, more if qualified Jan 7, 2026

Files to Create

New Files

Files to Modify


Appendix: Community Assignment Flow

Champion Verified
       │
       ▼
┌──────────────────────────────┐
│ CommunityAssignmentService   │
│ .assign_all(champion)        │
└──────────────────────────────┘
       │
       ├─► District Community
       │   └─ Based on champion.district_id
       │
       ├─► College Communities (1 per degree)
       │   └─ Based on champion.alumni.degrees.*.major.college_code
       │
       ├─► Major Communities (1 per degree)
       │   └─ Based on champion.alumni.degrees.*.major_code
       │
       ├─► Affinity Communities (0 to many)
       │   └─ Based on champion.alumni.affinities
       │
       └─► Industry Community (0 or 1)
           └─ Based on champion.industry

For each assignment:

  1. Find or create community for that source
  2. Create ChampionCommunity join record (skip if exists)
  3. Counter cache updates member count
  4. Community becomes active if threshold met