Skip to main content

Player Intelligence

The player intelligence system provides admin-managed flags, behavioral analytics, and membership tracking to support operational decisions.

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│ Admin UI │────▶│ PlayerFlagsAdmin│────▶│ Player Model │
│ (flags panel) │ │ Controller │ │ (flags, notes) │
└─────────────────┘ └──────────────────┘ └─────────────────┘

┌─────────────────┐ ┌──────────────────┐ ▼
│ Booking Events │────▶│ PlayerStats │◀───────────┘
│ (no-shows) │ │ Repository │
└─────────────────┘ └──────────────────┘


┌──────────────────┐
│ Intelligence │
│ (no-shows, │
│ partners) │
└──────────────────┘

Sources:

  • apps/teetime/teetime-backend/src/admin/player-flags-admin.controller.ts
  • libs/prisma/tee-sheet-data/src/lib/repositories/player-stats/player-stats.repository.ts

Player Flags

Admin-managed tags for player categorization:

FlagDescriptionUse Case
VIPHigh-value playerPriority booking, perks
CORPORATECorporate clientGroup rates, invoicing
WARNINGRequires cautionBehavior notes, restrictions
SLOW_PLAYSlow play historyPace monitoring
STAFFClub staff memberStaff rates, access
NEW_VISITORFirst-time visitorWelcome messaging

Flag Management

// PUT /admin/players/:playerId/flags
{
"flags": ["VIP", "CORPORATE"],
"flagNotes": "Annual corporate client - ABC Corp"
}

Flags include audit trail:

  • flagUpdatedAt — Timestamp of last change
  • flagUpdatedBy — Admin user who made the change

Player Statistics

No-Show Tracking

Counts no-shows in rolling 12-month window:

// In PlayerStatsRepository
async countNoShows(playerId: string, months = 12): Promise<number> {
const since = subMonths(new Date(), months);

return this.prisma.booking.count({
where: {
playerId,
status: 'NO_SHOW',
createdAt: { gte: since }
}
});
}

Frequent Partners

Identifies top 3 playing partners (minimum 3 shared rounds):

interface FrequentPartner {
playerId: string;
firstName: string;
lastName: string;
roundCount: number; // Shared rounds in last 12 months
}

Query aggregates bookings where players appeared on same tee time.

Player Profile DTO

Admin profile endpoint returns unified intelligence:

// GET /admin/players/:playerId/profile
{
"id": "player-123",
"firstName": "John",
"lastName": "Smith",
"email": "john@example.com",
"phone": "+1234567890",
"memberNumber": "M-001",
"handicap": 12.5,
"flags": ["VIP", "CORPORATE"],
"flagNotes": "Annual corporate client",
"noShowCount": 1,
"frequentPartners": [
{
"playerId": "player-456",
"firstName": "Jane",
"lastName": "Doe",
"roundCount": 8
}
]
}

Membership Tracking

Club Membership

interface PlayerClubMembership {
id: string;
playerId: string;
clubId: string;
membershipType: string; // e.g., "Full", "Social", "Junior"
memberNumber: string;
memberStatus: 'ACTIVE' | 'SUSPENDED' | 'EXPIRED';
isMainMember: boolean; // Primary club association
startDate: Date;
expiryDate?: Date;
}

Association Membership

Tracks golf association affiliations:

interface PlayerAssociation {
id: string;
playerId: string;
provider: 'GOLFRSA' | 'DOTGOLF' | 'SAGA';
memberNumber: string;
handicap?: number;
handicapUpdatedAt?: Date;
expiresAt?: Date;
}

API Endpoints

Admin Endpoints

MethodEndpointDescription
GET/admin/players/:playerId/profileGet player with intelligence
PUT/admin/players/:playerId/flagsUpdate player flags
GET/admin/players/searchSearch players

Player Endpoints

MethodEndpointDescription
GET/v1/players/meGet own profile
PUT/v1/players/meUpdate own profile
GET/v1/players/me/preferencesGet tee time preferences
PUT/v1/players/me/preferencesUpdate preferences

Social Endpoints

MethodEndpointDescription
GET/v1/players/me/buddiesGet buddy list
POST/v1/players/me/buddiesSend buddy invite
DELETE/v1/players/me/buddies/:idRemove buddy
GET/v1/players/me/favoritesGet favorite clubs
POST/v1/players/me/favoritesAdd favorite

Player Registration Flow

IDP Authentication


POST /v1/players/complete-registration


PlayerRegistrationService

├─► Create Player record

├─► Link UserIdentity

└─► Link Association (if provided)


McaImportService

└─► Enrich from provider (handicap, membership)

Intelligence Use Cases

Booking Flow

  1. Display flags in admin booking drawer
  2. Show no-show count for risk assessment
  3. Suggest frequent partners for group bookings

Check-In

  1. Highlight VIP for special treatment
  2. Show warning flags for staff awareness
  3. Track slow-play for pace management

Waitlist

  1. Priority VIP offers (if configured)
  2. Partner suggestions for partial group fills

Data Model

model Player {
id String @id @default(uuid())
firstName String
lastName String
email String?
phone String?

// Intelligence
flags PlayerFlag[]
flagNotes String?
flagUpdatedAt DateTime?
flagUpdatedBy String?

// Relations
homeClubId String?
homeClub Club? @relation(fields: [homeClubId])
memberships PlayerClubMembership[]
associations PlayerAssociation[]
bookings Booking[]
}

enum PlayerFlag {
VIP
CORPORATE
WARNING
SLOW_PLAY
STAFF
NEW_VISITOR
}

Metrics

MetricDescription
player_flags_totalFlags by type
player_noshow_rateNo-show percentage
player_registration_totalNew registrations

Troubleshooting

Missing Intelligence Data

  1. Verify player exists in database
  2. Check booking history for stats calculation
  3. Review association provider connectivity

Flag Not Displaying

  1. Verify flag update was saved
  2. Check admin user has write permissions
  3. Review audit trail for changes