Skip to main content

Captain's License System Documentation

Overview​

The captain's license system allows users to track their captain's licenses, expiration dates, renewal requirements, and find renewal events (classes, tests, exams) near them.

Core Entities​

CaptainLicense​

Represents a user's captain's license.

Key Features:

  • License information (number, type, level)
  • Issuing authority (USCG, State, International)
  • Issue and expiration dates
  • Renewal requirements (class, test, medical exam, drug test)
  • Status tracking (Active, Expired, Suspended, etc.)
  • Verification support

LicenseRenewalEvent​

Represents a license renewal event (class, test, exam).

Key Features:

  • Event details (name, description, type)
  • Location (with coordinates for proximity search)
  • Dates and registration information
  • License types covered
  • Organizer information

LicenseRenewalEventRegistration​

User registration for a renewal event.

Key Features:

  • Registration status
  • Payment tracking
  • Attendance tracking
  • Completion and pass status
  • Certificate tracking

LicenseRenewalHistory​

History of license renewals.

Key Features:

  • Renewal dates
  • Renewal method (class, exam, online, etc.)
  • Requirements met
  • Documentation links

Usage Examples​

Adding a Captain's License​

var license = new CaptainLicense
{
UserId = userId,
LicenseNumber = "USCG-123456",
LicenseType = "USCG Master",
LicenseLevel = "100 Ton",
IssuingAuthority = LicenseIssuingAuthority.USCG,
IssueDate = new DateTime(2020, 1, 15),
ExpirationDate = new DateTime(2025, 1, 15),
RenewalPeriodMonths = 60, // 5 years
RequiresRenewalClass = true,
RequiresRenewalTest = false,
RequiresMedicalExam = true,
RequiresDrugTest = true,
Status = LicenseStatus.Active,
IsActive = true,
IsPrimary = true
};

context.CaptainLicenses.Add(license);
await context.SaveChangesAsync();

Finding Renewal Events Near User​

public async Task<List<LicenseRenewalEvent>> FindRenewalEventsNearUser(
Guid userId,
double userLatitude,
double userLongitude,
int radiusMiles = 50)
{
var user = await context.Users
.Include(u => u.CaptainLicenses.Where(cl => cl.IsActive))
.FirstOrDefaultAsync(u => u.Id == userId);

var userLicenses = user.CaptainLicenses
.Where(cl => cl.Status == LicenseStatus.Active || cl.Status == LicenseStatus.RenewalPending)
.ToList();

// Find events that cover user's license types
var licenseTypes = userLicenses.Select(cl => cl.LicenseType).Distinct().ToList();

// Find events within radius
var events = await context.LicenseRenewalEvents
.Where(lre => lre.IsActive
&& lre.IsPublic
&& lre.Status == EventStatus.Scheduled
&& lre.StartDate > DateTime.UtcNow
&& lre.Latitude.HasValue
&& lre.Longitude.HasValue
&& (lre.LicenseTypes == null ||
licenseTypes.Any(lt => lre.LicenseTypes.Contains(lt))))
.ToListAsync();

// Filter by distance
var nearbyEvents = events
.Where(e => CalculateDistance(
userLatitude,
userLongitude,
e.Latitude.Value,
e.Longitude.Value) <= radiusMiles)
.OrderBy(e => CalculateDistance(
userLatitude,
userLongitude,
e.Latitude.Value,
e.Longitude.Value))
.ToList();

return nearbyEvents;
}

private double CalculateDistance(double lat1, double lon1, double lat2, double lon2)
{
// Haversine formula for distance calculation
const double R = 3959; // Earth radius in miles
var dLat = ToRadians(lat2 - lat1);
var dLon = ToRadians(lon2 - lon1);
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
return R * c;
}

private double ToRadians(double degrees) => degrees * Math.PI / 180;

Checking License Expiration​

public async Task CheckLicenseExpirations()
{
var expiringSoon = await context.CaptainLicenses
.Where(cl => cl.IsActive
&& cl.Status == LicenseStatus.Active
&& cl.ExpirationDate <= DateTime.UtcNow.AddDays(90) // Expiring in next 90 days
&& cl.ExpirationDate > DateTime.UtcNow)
.Include(cl => cl.User)
.ToListAsync();

foreach (var license in expiringSoon)
{
// Calculate days until expiration
license.DaysUntilExpiration = (license.ExpirationDate - DateTime.UtcNow).Days;

// Update status if expiring soon
if (license.DaysUntilExpiration <= 30)
{
license.Status = LicenseStatus.RenewalPending;
}

// Notify user about upcoming expiration
await NotifyUserOfExpiration(license.User, license);
}

await context.SaveChangesAsync();
}

Recording License Renewal​

public async Task RecordLicenseRenewal(
int licenseId,
int? eventId,
DateTime newExpirationDate)
{
var license = await context.CaptainLicenses
.FirstOrDefaultAsync(cl => cl.Id == licenseId);

var renewal = new LicenseRenewalHistory
{
LicenseId = licenseId,
EventId = eventId,
RenewalDate = DateTime.UtcNow,
PreviousExpirationDate = license.ExpirationDate,
NewExpirationDate = newExpirationDate,
RenewalMethod = eventId.HasValue ? RenewalMethod.Class : RenewalMethod.Online,
CompletedClass = eventId.HasValue,
PassedTest = true
};

// Update license
license.ExpirationDate = newExpirationDate;
license.RenewalDate = DateTime.UtcNow;
license.Status = LicenseStatus.Active;
license.IsExpired = false;

context.LicenseRenewalHistories.Add(renewal);
await context.SaveChangesAsync();
}

Integration with Tournament Eligibility​

The captain's license system integrates with tournament eligibility rules:

// When registering for tournament, check license status
var hasValidLicense = await context.CaptainLicenses
.AnyAsync(cl => cl.UserId == userId
&& cl.IsActive
&& cl.Status == LicenseStatus.Active
&& cl.ExpirationDate > DateTime.UtcNow);

// Use in eligibility check
if (eligibilityRule.RequiresValidCaptainLicense && hasValidLicense)
{
// Apply rule (e.g., must register for Pro division)
}

Benefits​

  1. License Tracking: Users can track all their licenses in one place
  2. Expiration Alerts: Automatic notifications for upcoming expirations
  3. Renewal Event Discovery: Find renewal classes/tests near user's location
  4. Tournament Eligibility: Automatic determination of pro/amateur status
  5. Compliance: Help users stay compliant with license requirements
  6. Event Curation: Location-based event recommendations