Player Import Specification

Tournament player import schema, validation, and integration reference.

WBi Tournament Player Import Specification v2.0

Executive Summary

This document defines the player import schema for the Western Badminton Inc (WBi) tournament management system. The specification is designed to:

  1. Support current operational needs (tournament registration, partner tracking, payment management)
  2. Enable future features (ratings system, multi-tournament tracking, advanced analytics)
  3. Maintain data integrity (duplicate detection, validation rules)
  4. Integrate with existing systems (membership database, payment processors, notification system)

Import Field Categories

โœ… PHASE 1: Core Fields (MVP - Current Implementation)

1.1 Personal Information (Mandatory)

Field Type Format Validation Notes
firstName String 50 chars Required, no special chars Maps to: Firstname
lastName String 50 chars Required, no special chars Maps to: Name or last part of Name
email Email RFC 5322 Required, unique, valid format Primary identifier
phone String 04XX XXX XXX Required, Australian mobile format Maps to: Mobile preferred, fallback Phone Home
dateOfBirth Date YYYY-MM-DD Required, 10-99 years old Maps to: DOB (convert DD/MM/YYYY)
gender Enum male|female|other Required Maps to: Gender (Mโ†’male, Fโ†’female)

Rationale:

Field Type Format Validation Notes
addressLine1 String 100 chars Street number + name Maps to: Address
addressLine2 String 100 chars Unit/Apartment Maps to: Address2
suburb String 50 chars - Maps to: City
state Enum VIC|NSW|QLD|SA|WA|TAS|NT|ACT Australian states only Maps to: State
postcode String NNNN 4-digit Australian postcode Maps to: Postalcode

Rationale:

1.3 Tournament Categories (Core Business Logic)

Field Type Format Validation Notes
categories Array/CSV singles,doubles,mixed At least one required Derived from: Events column
skillLevel Enum A|B|C|D|Open - Maps to: Level1 (if grading system exists)
ageGroup Enum U15|U18|U21|Open|35+|45+ Calculated from DOB + Event Derived from: Events (e.g., "Above 40")
categoryCode String 10 chars e.g., MD-A, WS-B Parsed from: Events column
partnerName String 100 chars Required if doubles/mixed selected Parsed from: Entry Info (partner name in brackets)

Rationale:

1.4 Payment Tracking

Field Type Format Validation Notes
paymentStatus Enum pending|paid|partially_paid|refunded Default: pending Not in current import (add manually)
paymentMethod Enum cash|bank_transfer|credit_card|paypal - Maps to: Entry Method (Internetโ†’online payment)
amountPaid Decimal 0.00 Non-negative Maps to: Fee
paymentDate Date YYYY-MM-DD - Maps to: Entry Date
paymentNotes Text 500 chars - Free text for admin notes

Rationale:

1.5 Emergency Contact (Safety Critical)

Field Type Format Validation Notes
emergencyContactName String 100 chars Required Not in current import (must collect)
emergencyContactPhone String Phone format Required Not in current import (must collect)
emergencyContactRelationship Enum spouse|parent|sibling|friend|other - Not in current import (must collect)
emergencyContactEmail Email RFC 5322 Optional Not in current import (optional)

Rationale:


๐Ÿš€ PHASE 2: Enhanced Fields (6-12 Months)

2.1 Membership Integration

Field Type Format Validation Notes
memberId String 20 chars Unique per player Maps to: Member ID (e.g., VIC1294)
membershipType Enum annual|6month|casual - Not in import (fetch from member DB)
membershipExpiry Date YYYY-MM-DD - Not in import (fetch from member DB)
clubAffiliation String 50 chars - Maps to: Club (e.g., CBA)
district String 50 chars - Maps to: District

Rationale:

2.2 Player Rating System

Field Type Format Validation Notes
rating1 Integer 100-3000 Singles rating Maps to: Rating1
rating2 Integer 100-3000 Doubles rating Maps to: Rating2
rating3 Integer 100-3000 Mixed doubles rating Maps to: Rating3
ratingLastUpdated Date YYYY-MM-DD - Not in import (calculated)

Rationale:

2.3 Participation History

Field Type Format Validation Notes
memberSince Date YYYY-MM-DD - First tournament registration date
totalTournaments Integer 0-999 Auto-calculated Lifetime count
totalSocialSessions Integer 0-9999 Auto-calculated Lifetime count
lastParticipation Date YYYY-MM-DD Auto-calculated Most recent activity
preferredCategories Array - ML-derived Future: Suggest categories based on history

Rationale:


๐Ÿ”ฎ PHASE 3: Advanced Features (12+ Months)

3.1 Performance Tracking

Field Type Format Validation Notes
tournamentWins Integer - Auto-calculated Career wins
tournamentRunnerUp Integer - Auto-calculated Career finals losses
winLossRatio Decimal 0.00-1.00 Auto-calculated Win percentage
favouritePartners Array Player IDs ML-derived Players often paired with
playStyle Array Tags ML-derived e.g., aggressive, defensive, net-play

Rationale:

3.2 Medical & Accessibility

Field Type Format Validation Notes
medicalConditions Text (encrypted) - HIPAA-compliant storage Not in import (optional on-site)
accessibilityNeeds Array - e.g., wheelchair, vision-impaired Not in import (optional on-site)
dietaryRestrictions Array - For catered events Not in import (optional on-site)

Rationale:

3.3 Marketing & Communications

Field Type Format Validation Notes
communicationPreferences Object JSON Email/SMS/WhatsApp opt-in Not in import (collect at registration)
marketingOptIn Boolean - GDPR/Privacy Act compliant Not in import (collect at registration)
languagePreference Enum en|zh|hi|ta|vi - Future: Multi-lingual notifications

Rationale:


Import Mapping Strategy

Current File โ†’ WBi System Mapping

// filepath: /Users/arungopal/Documents/Arun/Badminton/Western Badminton/Website/development/admin/tournament/js/import-mapper.js

const IMPORT_FIELD_MAPPING = {
  // Direct mappings
  'Firstname': 'firstName',
  'Email': 'email',
  'Mobile': 'phone',
  'Gender': (value) => value === 'M' ? 'male' : value === 'F' ? 'female' : 'other',
  'DOB': (value) => convertDateFormat(value, 'DD/MM/YYYY', 'YYYY-MM-DD'),
  
  // Address fields
  'Address': 'addressLine1',
  'Address2': 'addressLine2',
  'City': 'suburb',
  'State': 'state',
  'Postalcode': 'postcode',
  
  // Tournament fields
  'Member ID': 'memberId',
  'Club': 'clubAffiliation',
  'District': 'district',
  'Level1': 'skillLevel',
  'Fee': 'amountPaid',
  'Entry Date': (value) => convertDateFormat(value, 'DD/MM/YYYY HH:mm', 'YYYY-MM-DD'),
  'Entry Method': (value) => value === 'Internet' ? 'online' : 'manual',
  
  // Complex mappings (require parsing)
  'Events': parseEventToCategories,  // "WBi. Mens Doubles Above 40" โ†’ categories + ageGroup
  'Entry Info': parsePartnerName,    // "WBi. Mens Doubles Above 40 Renjith Raghavan ()" โ†’ partnerName
  
  // Name parsing (current system has full name in "Name" field)
  'Name': (value, row) => {
    if (row['Firstname']) return null; // Use Firstname if available
    const parts = value.split(' ');
    return {
      firstName: parts[0],
      lastName: parts.slice(1).join(' ')
    };
  },
  
  // Fallback phone sources
  'Phone Home': (value, row) => row['Mobile'] || value,
  'Phone Work': (value, row) => row['Mobile'] || row['Phone Home'] || value
};

function parseEventToCategories(eventString) {
  // "WBi. Mens Doubles Above 40" โ†’ { categories: ['doubles'], ageGroup: '40+', gender: 'male' }
  const categories = [];
  let ageGroup = 'Open';
  
  if (/singles/i.test(eventString)) categories.push('singles');
  if (/doubles/i.test(eventString) && !/mixed/i.test(eventString)) categories.push('doubles');
  if (/mixed/i.test(eventString)) categories.push('mixed');
  
  const ageMatch = eventString.match(/above\s+(\d+)|under\s+(\d+)|u(\d+)/i);
  if (ageMatch) {
    const age = ageMatch[1] || ageMatch[2] || ageMatch[3];
    ageGroup = ageMatch[0].toLowerCase().includes('above') ? `${age}+` : `U${age}`;
  }
  
  return { categories, ageGroup };
}

function parsePartnerName(entryInfo) {
  // "WBi. Mens Doubles Above 40 Renjith Raghavan ()" โ†’ "Renjith Raghavan"
  const match = entryInfo.match(/\s+([A-Z][a-z]+\s+[A-Z][a-z]+)\s*\(/);
  return match ? match[1] : null;
}

Duplicate Detection Strategy

Multi-Factor Duplicate Checking

// Duplicate detection logic (update players.js)
async function detectDuplicates(player, existingPlayers) {
  const duplicates = {
    exactMatch: null,
    emailMatch: null,
    phoneMatch: null,
    fuzzyNameMatch: null,
    confidence: 0
  };
  
  for (const existing of existingPlayers) {
    // Level 1: Exact email match (100% duplicate)
    if (player.email.toLowerCase() === existing.email.toLowerCase()) {
      duplicates.emailMatch = existing;
      duplicates.confidence = 100;
      break;
    }
    
    // Level 2: Phone number match (95% duplicate)
    if (player.phone === existing.phone) {
      duplicates.phoneMatch = existing;
      duplicates.confidence = Math.max(duplicates.confidence, 95);
    }
    
    // Level 3: Member ID match (90% duplicate - handle club transfers)
    if (player.memberId && player.memberId === existing.memberId) {
      duplicates.exactMatch = existing;
      duplicates.confidence = Math.max(duplicates.confidence, 90);
    }
    
    // Level 4: Fuzzy name + DOB match (70% duplicate - typo tolerance)
    const nameSimilarity = levenshteinDistance(
      player.firstName + player.lastName,
      existing.firstName + existing.lastName
    );
    if (nameSimilarity > 0.85 && player.dateOfBirth === existing.dateOfBirth) {
      duplicates.fuzzyNameMatch = existing;
      duplicates.confidence = Math.max(duplicates.confidence, 70);
    }
  }
  
  return duplicates;
}

Month 1-2: Core Import (MVP)

Month 3-4: Payment Integration

Month 5-6: Membership Sync

Month 7-9: Rating System

Month 10-12: Analytics & ML


Missing Fields Analysis

โš ๏ธ Critical Gaps in Current Import

Missing Field Why It's Needed Workaround
Emergency Contact Legal requirement, safety Collect at on-site registration
Payment Status Financial tracking Default to pending, update manually
Skill Level Bracket placement Parse from Level1 or admin assigns
Category Code Standardization Parse from Events (e.g., MD-40+)
Middle Name Full legal name Use Middlename field (currently in import)

๐Ÿ”„ Fields to Deprecate

Current Field Reason Migration
Ev. (Event count) Redundant with Events array Delete
Av. (Unknown abbrev) Purpose unclear Delete if not used
Address3 Australia doesn't use 3 address lines Combine with Address2
Country All WBi tournaments are Australia-only Hardcode Australia
Phone Work Rarely provided Use Mobile as primary

Export Template (For Bulk Registration)

FirstName,LastName,Email,Mobile,DOB,Gender,Address,Suburb,State,Postcode,Categories,SkillLevel,AgeGroup,PartnerName,EmergencyName,EmergencyPhone,EmergencyRelationship,Fee,PaymentMethod
John,Smith,john@example.com,0412345678,1990-05-15,Male,123 Main St,Melbourne,VIC,3000,"singles,doubles",B,Open,Jane Smith,Mary Smith,0498765432,Mother,50,Bank Transfer

Key Changes from Current Format:

  1. โœ… No more Middlename - rarely used, adds complexity
  2. โœ… Categories is multi-select (comma-separated)
  3. โœ… Explicit AgeGroup field instead of parsing from event name
  4. โœ… Emergency contact mandatory
  5. โœ… Payment method tracked for reconciliation

Data Privacy & GDPR Compliance

Field-Level Access Control

const FIELD_PRIVACY_LEVELS = {
  // Public (visible on leaderboards)
  public: ['firstName', 'lastName', 'categoryCode', 'skillLevel'],
  
  // Admin only (tournament staff)
  admin: ['email', 'phone', 'addressLine1', 'suburb', 'postcode', 'paymentStatus', 'amountPaid'],
  
  // Emergency only (authorized personnel during events)
  emergency: ['emergencyContactName', 'emergencyContactPhone', 'emergencyContactRelationship'],
  
  // Medical staff only (encrypted at rest)
  medical: ['medicalConditions', 'accessibilityNeeds']
};

Conclusion & Next Steps

Immediate Actions (This Sprint)

  1. โœ… Update import parser to handle current CSV format
  2. โœ… Add Events โ†’ categories parser (regex-based)
  3. โœ… Extract partner name from Entry Info field
  4. โœ… Add emergency contact fields to registration form
  5. โœ… Implement email + phone duplicate detection

Future Enhancements (Backlog)

Questions for Product Owner

  1. Do we want to track club affiliations for future team tournaments?
  2. Should we build a rating system or integrate with BWF/national rankings?
  3. Privacy policy - can we use player data for marketing (opt-in required)?
  4. Multi-tournament tracking - should a player's history span multiple years/events?

Document Version: 2.0
Last Updated: 2025-01-28
Owner: WBi Development Team
Review Cycle: Quarterly