⚠️ PLANNING DOCUMENT — Guidelines for Champion Portal development.
Related Documents:
- DATA-ARCHITECTURE.md — Data ownership and table design
- DECISIONS.md — Architectural decision log
- ../README.md — Champion Portal technical overview
As we build the Champion Portal alongside the existing Lookup Portal, we must continuously evaluate whether functionality, data, or features:
This applies to code, data, features, and user-facing functionality — not just technical implementation.
| Feature | Lookup Portal | Champion Portal | Decision |
|---|---|---|---|
| Profile Photos | Alumni photos (staff uploaded) | Champion photos (self uploaded) | ✅ DECIDED: Separate — different purposes |
| Contact Info | alumni.email, alumni.phone |
cp_champions.email, cp_champions.phone |
✅ DECIDED: Separate — no sync |
| Preferred Name | alumni.pref_name |
N/A | ✅ DECIDED: Shared on alumni, Champion can edit |
| Maiden Name | alumni.maiden_name |
N/A | ✅ DECIDED: Shared on alumni, Champion can edit |
| Affinities | alumni_affinities (verified) |
cp_affinities (self-reported) |
Decide: How do these relate? Merge UI? |
| Location/Address | alumni.city, alumni.state |
cp_champions.city, cp_champions.zip_code |
✅ DECIDED: Separate — Champion owns their address |
| Feature | Lookup Portal | Champion Portal | Decision |
|---|---|---|---|
| Employment Info | Not currently tracked | cp_champions.employer, job_title |
Decide: Should this flow back to Alumni? |
| Notes/Bio | alumni.prospect_notes (staff) |
Champion bio (self-written) | ✅ DECIDED: Different purposes, keep separate |
| Email Preferences | Not tracked | Champion contact preferences | N/A — Champion Portal only |
| Feature | Lookup Portal | Champion Portal | Decision |
|---|---|---|---|
| Activity History | engagement_activities |
Portal activity logs | ✅ DECIDED: Keep strictly separate |
| Event Attendance | registrants (planned) |
cp_events (champion-created) |
Different concepts, keep separate |
| Data | Location | Who Edits | Notes |
|---|---|---|---|
| Name fields | alumni |
Staff OR Champion (if verified) | Shared source of truth |
| Photo (ID) | alumni.photo |
Staff only | For internal identification |
| Photo (profile) | cp_champions.photo |
Champion only | Self-representation, never shows staff photo |
| Contact (CRM) | alumni.email/phone |
Staff only | CRM data, not exposed to Champion Portal |
| Contact (portal) | cp_champions.* |
Champion only | For Champion Portal use |
| Address | cp_champions.* |
Champion only | Champion owns their address |
| Bio | cp_champions.bio |
Champion only | Different from prospect_notes |
| Prospect notes | alumni.prospect_notes |
Staff only | Internal notes, never shown |
When you encounter overlapping functionality, work through this decision tree:
┌─────────────────────────────────────────────────────────────────┐
│ Is this the SAME concept or DIFFERENT concepts? │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
SAME CONCEPT DIFFERENT CONCEPTS
(e.g., preferred name) (e.g., staff photo vs profile photo)
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Who is the SOURCE OF │ │ Document why separate │
│ TRUTH? │ │ Keep in different │
│ │ │ tables/locations │
│ □ Staff (Lookup) │ └─────────────────────────┘
│ □ Champion (self) │
│ □ Shared (complex) │
└─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ UNIFICATION PATTERN │
├─────────────────────────────────────────────────────────────────┤
│ A. Single Location — One table, both portals read/write │
│ B. Primary + Override — Lookup is default, Champion overrides │
│ C. Champion + Sync — Champion owns, Staff can sync back │
│ D. Staff + Display — Staff owns, Champion can only view │
└─────────────────────────────────────────────────────────────────┘
Use when: Data should never diverge. One source of truth for everyone.
Example: Profile Photo (Alternative Approach)
┌─────────────┐
│ alumni │
│ .photo │◄──── Staff uploads (Lookup)
│ (attachment)│◄──── Champion uploads (Champion Portal)
└─────────────┘
│
▼
Both portals display the same photo
Implementation:
alumni table or shared attachment)Use when: Staff data is authoritative, but Champions can personalize.
Example: Preferred Name
┌─────────────┐ ┌──────────────┐
│ alumni │ │ cp_champions │
│ .pref_name │ │.preferred_ │
│ (from CRM) │ │ name │
└─────────────┘ └──────────────┘
│ │
└────────┬──────────┘
▼
Display Logic:
champion.preferred_name || alumni.pref_name || alumni.first_name
Implementation:
Use when: Champions should control their own data, but Staff may want to sync verified info.
Example: Contact Information
┌──────────────┐ ┌─────────────┐
│ cp_champions │ ──────► │ alumni │
│ .phone │ Staff │ .phone │
│ .city │ syncs │ .city │
│ .state │ after │ .state │
└──────────────┘ review └─────────────┘
│
▼
Champion Portal shows cp_champions data
Lookup Portal shows alumni data (may be older)
Implementation:
cp_championsalumniUse when: Data must be authoritative and Champions shouldn’t change it.
Example: Degree Information
┌─────────────┐
│ degrees │◄──── Staff imports from Registrar
└─────────────┘
│
▼
Champion Portal displays (read-only)
"Your degrees on file..."
Implementation:
alumni associationIs this the same concept?
→ Decided: NO. Staff photo for ID purposes; Champion photo for self-representation.
Who is the source of truth?
→ Decided: SEPARATE. Each portal owns its own photo.
# Champion owns their photo
class Cp::Champion < ApplicationRecord
has_one_attached :photo # Champion uploads here
end
# Staff photo stays on alumni record
class Alumni < ApplicationRecord
has_one_attached :photo # Staff uploads here (Lookup Portal)
end
Key Rule: Staff-uploaded photos NEVER appear in Champion Portal.
Before starting any new feature, complete this checklist:
## Pre-Implementation Review for: [Feature Name]
### 1. Overlap Check
- [ ] Does this feature/data exist in Lookup Portal?
- [ ] Will both portals need to read this data?
- [ ] Will both portals need to write this data?
- [ ] Is this the SAME concept or DIFFERENT concepts?
### 2. If Same Concept — Unification Decision
- [ ] Who is the source of truth? (Staff / Champion / Shared)
- [ ] Which pattern applies?
- [ ] A. Single Location
- [ ] B. Primary + Override
- [ ] C. Champion + Sync
- [ ] D. Staff + Display Only
- [ ] Document the decision in DECISIONS.md
### 3. If Different Concepts — Document Why
- [ ] Added comment explaining why separate
- [ ] Named clearly to distinguish (e.g., `prospect_notes` vs `bio`)
### 4. Code/Service Reuse
- [ ] Searched for existing implementation
- [ ] Evaluated extraction to shared service
- [ ] Checked for UI components to reuse
### 5. Data Architecture
- [ ] Table uses correct prefix (`cp_` for Champion Portal)
- [ ] Foreign keys properly defined
- [ ] Documented in data-model/README.md
Before building Champion Portal equivalents, review what already exists:
| Model | Purpose | Champion Portal Relevance |
|---|---|---|
Alumni |
Core alumni record | Links to Cp::Champion via BUID |
Degree |
Academic records | Display-only for Champions |
Affinity |
Master affinity list | Shared reference data |
AlumniAffinity |
Staff-managed affiliations | Separate from self-reported |
EngagementActivity |
Tracked engagement | NOT used by Champion Portal |
ChampionSignup |
Interest form submissions | Migrate to Cp::Champion? |
| Feature | Lookup Implementation | Champion Portal Plan |
|---|---|---|
| Photo upload | alumni.photo (ActiveStorage) |
✅ DECIDED: Separate on cp_champions |
| CSV export | Csv::*Exporter services |
Reuse pattern |
| Search | pg_search |
Reuse or new? |
| Email sending | ApplicationMailer |
Reuse |
| Component | Location | Reusable? |
|---|---|---|
| Flash messages | shared/_flash |
✅ Yes |
| Pagination | Various | Extract to shared |
| Empty states | Inline | Extract to shared |
| Forms | Various | May need mobile variants |
| Document | Purpose |
|---|---|
| DATA-ARCHITECTURE.md | Data ownership between portals |
| DECISIONS.md | Architectural decisions log |
| ../../development/AGENTS.md | Development guidelines |