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