Skip to main content

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:

CompetitionDescriptionTypical Holes
Nearest PinClosest to the holePar 3s
Longest DriveFurthest drive in fairwayPar 4/5, straight hole
2's ClubGross 2 on par 3All 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

  1. Designate NTP holes (typically all par 3s)
  2. Designate longest drive hole(s)
  3. Set up measurement equipment

During play

  1. Starter records measurements as groups pass
  2. Multiple recordings per hole (superseded by closest)
  3. Real-time updates to leaderboard

Post-competition

  1. Verify final results
  2. Announce winners
  3. Include in results presentation

API endpoints

MethodEndpointDescription
POST/competitions/:id/nearest-pinRecord NTP
GET/competitions/:id/nearest-pinGet NTP winner
POST/competitions/:id/longest-driveRecord LD
GET/competitions/:id/longest-driveGet LD winner
GET/competitions/:id/twos-clubGet 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)