Side Competitions
Additional competitions run alongside the main event: nearest pin, longest drive, and 2's club.
Overview
Side competitions add engagement to events without affecting the main results:
| Competition | Description | Typical Holes |
|---|---|---|
| Nearest Pin | Closest to the hole | Par 3s |
| Longest Drive | Furthest drive in fairway | Par 4/5, straight hole |
| 2's Club | Gross 2 on par 3 | All par 3s |
Nearest Pin
Record and retrieve closest-to-the-pin results on designated holes.
Record result
await sideCompetitionsService.record({
competitionId: 'comp-123',
roundId: 'round-456',
type: 'NEAREST_PIN',
holeNumber: 7,
entryId: 'entry-abc',
distance: 2.3,
unit: 'metres',
});
Get winner
const result = await sideCompetitionsService.getNearestPin('comp-123');
Response
interface NearestPinResult {
competitionId: string;
holeNumber: number | null;
winnerId: string | null;
winnerName: string | null;
distance: number | null;
unit: 'metres' | 'feet' | 'inches';
}
Example response
{
"competitionId": "comp-123",
"holeNumber": 7,
"winnerId": "player-456",
"winnerName": "John Smith",
"distance": 2.3,
"unit": "metres"
}
Multiple holes
Record NTP for each designated hole:
// Hole 4 (Par 3)
await sideCompetitionsService.record({
competitionId: 'comp-123',
type: 'NEAREST_PIN',
holeNumber: 4,
entryId: 'entry-a',
distance: 1.8,
unit: 'metres',
});
// Hole 12 (Par 3)
await sideCompetitionsService.record({
competitionId: 'comp-123',
type: 'NEAREST_PIN',
holeNumber: 12,
entryId: 'entry-b',
distance: 3.2,
unit: 'metres',
});
Get all NTP results
const results = await sideCompetitionsService.getAllNearestPins('comp-123');
// Returns array of NearestPinResult, one per hole
Longest Drive
Record and retrieve longest drive results.
Record result
await sideCompetitionsService.record({
competitionId: 'comp-123',
roundId: 'round-456',
type: 'LONGEST_DRIVE',
holeNumber: 9,
entryId: 'entry-xyz',
distance: 285,
unit: 'yards',
});
Get winner
const result = await sideCompetitionsService.getLongestDrive('comp-123');
Response
interface LongestDriveResult {
competitionId: string;
holeNumber: number | null;
winnerId: string | null;
winnerName: string | null;
distance: number | null;
unit: 'yards' | 'metres';
}
Example response
{
"competitionId": "comp-123",
"holeNumber": 9,
"winnerId": "player-789",
"winnerName": "Jane Doe",
"distance": 285,
"unit": "yards"
}
Division-specific
Run separate longest drive per division:
// Men's longest drive
await sideCompetitionsService.record({
competitionId: 'comp-123',
type: 'LONGEST_DRIVE',
holeNumber: 9,
entryId: 'entry-m',
distance: 285,
unit: 'yards',
metadata: { division: 'mens' },
});
// Ladies' longest drive
await sideCompetitionsService.record({
competitionId: 'comp-123',
type: 'LONGEST_DRIVE',
holeNumber: 9,
entryId: 'entry-f',
distance: 235,
unit: 'yards',
metadata: { division: 'ladies' },
});
2's Club (Twos)
Automatically track gross 2s on par 3 holes.
How it works
The system automatically detects 2s when:
- Hole par = 3
- Gross strokes = 2
- Not marked as No Return
Get 2's club results
const result = await resultsService.getTwosClub('comp-123');
Response
interface TwosClubResult {
competitionId: string;
twos: TwoResult[]; // Individual 2s
twosByPlayer: { // Aggregated
playerId: string;
playerName: string;
count: number;
roundIds: string[];
}[];
}
interface TwoResult {
playerId: string;
playerName: string;
roundId: string;
roundNumber: number;
holeNumber: number;
}
Example response
{
"competitionId": "comp-123",
"twos": [
{
"playerId": "player-1",
"playerName": "John Smith",
"roundId": "round-1",
"roundNumber": 1,
"holeNumber": 4
},
{
"playerId": "player-2",
"playerName": "Jane Doe",
"roundId": "round-1",
"roundNumber": 1,
"holeNumber": 12
},
{
"playerId": "player-1",
"playerName": "John Smith",
"roundId": "round-2",
"roundNumber": 2,
"holeNumber": 7
}
],
"twosByPlayer": [
{
"playerId": "player-1",
"playerName": "John Smith",
"count": 2,
"roundIds": ["round-1", "round-2"]
},
{
"playerId": "player-2",
"playerName": "Jane Doe",
"count": 1,
"roundIds": ["round-1"]
}
]
}
2's pot calculation
The module tracks 2s but pot money is handled separately:
// Get 2s count for pot calculation
const { twos, twosByPlayer } = await resultsService.getTwosClub('comp-123');
// If pot is R200 and 4 twos were made
const potPerTwo = 20000 / twos.length; // R50 each (in cents)
No 2s scenario
When no 2s are made:
{
"competitionId": "comp-123",
"twos": [],
"twosByPlayer": []
}
Pot typically rolls over to next competition.
Data model
interface SideCompetitionResult {
id: string;
competitionId: string;
roundId: string | null;
type: 'NEAREST_PIN' | 'LONGEST_DRIVE';
holeNumber: number;
entryId: string;
distance: number | null;
unit: string | null;
createdAt: Date;
// Relationships
entry: CompetitionEntry;
}
Recording workflow
Pre-competition setup
- Designate NTP holes (typically all par 3s)
- Designate longest drive hole(s)
- Set up measurement equipment
During play
- Starter records measurements as groups pass
- Multiple recordings per hole (superseded by closest)
- Real-time updates to leaderboard
Post-competition
- Verify final results
- Announce winners
- Include in results presentation
API endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /competitions/:id/nearest-pin | Record NTP |
GET | /competitions/:id/nearest-pin | Get NTP winner |
POST | /competitions/:id/longest-drive | Record LD |
GET | /competitions/:id/longest-drive | Get LD winner |
GET | /competitions/:id/twos-club | Get 2's results |
Best practices
Nearest pin
- Use laser rangefinder for accuracy
- Mark ball position before measuring
- Record in consistent units (metres recommended)
- Multiple markers can record, closest wins
Longest drive
- Ensure ball is in fairway
- Measure from tee to ball (not just carry)
- Consider wind/elevation factors for fairness
- Separate categories for different tees if needed
2's club
- Automatic tracking reduces admin burden
- Verify gross 2 (not net 2)
- Consider hole-in-ones separately (automatic 2 on par 3)