Tournament player import schema, validation, and integration reference.
This document defines the player import schema for the Western Badminton Inc (WBi) tournament management system. The specification is designed to:
| 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 |
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:
| 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:
Events as single-select but WBi needs multi-category registrationcategoryCode standardizes across tournaments (critical for draw generation)| 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:
Entry Method indicates online vs offline registration| 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 |
RFC 5322 | Optional | Not in current import (optional) |
Rationale:
| 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:
clubAffiliation supports team tournaments and club championships| 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:
Rating1/2/3 are empty)| 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:
| 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:
| 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:
| 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:
// 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 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;
}
Events โ categories parsingEntry InfoMember ID| 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) |
| 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 |
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:
Middlename - rarely used, adds complexityCategories is multi-select (comma-separated)AgeGroup field instead of parsing from event nameconst 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']
};
Events โ categories parser (regex-based)Entry Info fieldMember IDDocument Version: 2.0
Last Updated: 2025-01-28
Owner: WBi Development Team
Review Cycle: Quarterly