Quick Logging & DNR Research Access System
Overview
This system enables:
- Quick Logging - One-button catch logging with automatic data capture from Bluetooth devices
- Privacy Controls - Granular control over data visibility and sharing
- DNR Research Access - Controlled access for Department of Natural Resources and researchers
- Legal Compliance - Full audit trail and consent tracking
Quick Logging Workflow
1. Start Fishing Session
var session = new FishingSession
{
UserId = userId,
Name = "Morning Trip - Lake Michigan",
StartTime = DateTime.UtcNow,
StartLatitude = 41.8781,
StartLongitude = -87.6298,
LocationName = "Lake Michigan - Port of Chicago",
GearSetupId = savedGearSetupId,
FishingMethod = FishingMethod.Trolling,
IsQuickLogSession = true,
ConnectedDeviceId = bluetoothDeviceId,
PrimaryTelemetrySource = TelemetrySource.FishHawk,
DataSharingConsent = user.DefaultDataSharingConsent,
AllowDnrAccess = user.DefaultAllowDnrAccess
};
2. Continuous Telemetry Capture
During the session, telemetry data is automatically captured:
var telemetry = new SessionTelemetry
{
FishingSessionId = session.Id,
RecordedAt = DateTime.UtcNow,
Latitude = currentLat,
Longitude = currentLon,
DepthM = fishHawkDevice.Depth,
WaterTempC = fishHawkDevice.WaterTemp,
GpsSpeedKph = gpsDevice.Speed,
HeadingDeg = gpsDevice.Heading,
Source = TelemetrySource.FishHawk
};
3. Quick Log Catch (Button Press)
When user presses quick-log button:
var logEntry = new FishingLogEntry
{
UserId = userId,
FishingSessionId = session.Id,
Date = DateOnly.FromDateTime(DateTime.UtcNow),
Time = TimeOnly.FromDateTime(DateTime.UtcNow),
CaughtAt = DateTime.UtcNow,
Latitude = currentLat, // From GPS
Longitude = currentLon, // From GPS
AccuracyMeters = gpsDevice.Accuracy,
FishingMethod = session.FishingMethod,
IsQuickLogged = true,
QuickLoggedAt = DateTime.UtcNow,
HasAutoCapturedData = true,
DataSourceDeviceId = bluetoothDeviceId,
Visibility = LogVisibility.Private, // Default to private
DataSharingConsent = session.DataSharingConsent,
AllowDnrAccess = session.AllowDnrAccess,
IncludeInMigrationAnalysis = false, // User must opt-in
IncludeInHotspotAnalysis = false, // User must opt-in
GearSetupId = session.GearSetupId
};
// Auto-capture current telemetry
var telemetry = new Telemetry
{
FishingLogEntryId = logEntry.Id,
DepthM = currentSessionTelemetry.DepthM,
WaterTempC = currentSessionTelemetry.WaterTempC,
GpsSpeedKph = currentSessionTelemetry.GpsSpeedKph,
HeadingDeg = currentSessionTelemetry.HeadingDeg,
Source = TelemetrySource.FishHawk
};
// Auto-capture current weather (from API)
var weather = new Weather
{
FishingLogEntryId = logEntry.Id,
TemperatureC = windyApi.Temperature,
WindSpeedKph = windyApi.WindSpeed,
WindDirectionDeg = windyApi.WindDirection,
PressureHpa = windyApi.Pressure,
Source = WeatherSource.Windy
};
// Catch details (needs user input or AI)
var catchDetail = new CatchDetail
{
FishingLogEntryId = logEntry.Id,
NeedsReview = true, // User must verify/complete
IsVerified = false,
// AI can attempt to identify species from photo
AiAnalyzedSpecies = aiService.IdentifySpecies(photoUrl)
};
4. User Review & Completion
User reviews quick-logged catches and completes details:
// User reviews and updates catch
catchDetail.FishSpeciesId = selectedSpeciesId;
catchDetail.LengthCm = measuredLength;
catchDetail.WeightKg = measuredWeight;
catchDetail.Condition = CatchCondition.Alive;
catchDetail.Disposition = CatchDisposition.Released;
catchDetail.NeedsReview = false;
catchDetail.IsVerified = true;
// User can update privacy settings
logEntry.Visibility = LogVisibility.Public;
logEntry.IncludeInMigrationAnalysis = true; // Opt-in for research
logEntry.IncludeInHotspotAnalysis = true; // Opt-in for hotspot data
Privacy & Data Sharing
Privacy Levels
- Public - Visible to everyone
- Private - Only visible to owner (and DNR if consent given)
- Circle - Visible to specific circle members
- Friends - Visible to friends only
Data Sharing Consent Levels
- None - No data sharing
- Anonymous - Share anonymized data only
- Aggregated - Share aggregated data for research
- Full - Full data sharing (with explicit consent)
- DNROnly - Share with DNR only
Per-Entry Override
Users can override default consent on a per-entry basis:
logEntry.DataSharingConsent = DataSharingConsent.Aggregated;
logEntry.AllowDnrAccess = true;
logEntry.IncludeInMigrationAnalysis = true;
DNR Research Access
DNR User Setup
var dnrUser = new User
{
Role = UserRole.DNR,
Email = "dnr@state.gov",
// ... other fields
};
Querying Data with DNR Access
// DNR can access all data where consent is given
var accessibleLogs = context.FishingLogEntries
.Where(fle =>
// Public logs
fle.Visibility == LogVisibility.Public ||
// Logs with DNR consent
fle.AllowDnrAccess == true ||
// Logs with data sharing consent
fle.DataSharingConsent != DataSharingConsent.None ||
// User's default consent allows DNR access
(fle.User.DefaultAllowDnrAccess && fle.DataSharingConsent == DataSharingConsent.DNROnly)
)
.ToList();
// Log the access
var accessLog = new DnrAccessLog
{
AccessorUserId = dnrUserId,
FishingLogEntryId = logEntryId,
UserId = logEntry.UserId,
AccessType = "View",
Purpose = "Migration pattern analysis",
ResearchProjectId = "2024-Salmon-Migration",
AccessedLocationData = true,
AccessedCatchData = true,
AccessedTelemetryData = true,
HasConsent = logEntry.AllowDnrAccess || logEntry.DataSharingConsent != DataSharingConsent.None,
DataSharingAgreementId = activeAgreement?.Id
};
Migration Pattern Analysis
// Query logs for migration analysis (only opted-in data)
var migrationData = context.FishingLogEntries
.Where(fle =>
fle.IncludeInMigrationAnalysis == true &&
fle.FishSpeciesId == targetSpeciesId &&
fle.Date >= startDate &&
fle.Date <= endDate
)
.Select(fle => new
{
fle.Latitude,
fle.Longitude,
fle.Date,
fle.Time,
fle.CatchDetails.FishSpecies.CommonName,
fle.Telemetry.WaterTempC,
fle.Weather.TemperatureC
})
.ToList();
// Generate migration heatmap
var heatmap = migrationData
.GroupBy(d => new { Lat = Math.Round(d.Latitude, 2), Lon = Math.Round(d.Longitude, 2) })
.Select(g => new
{
Location = g.Key,
CatchCount = g.Count(),
AvgWaterTemp = g.Average(d => d.WaterTempC),
DateRange = new { Start = g.Min(d => d.Date), End = g.Max(d => d.Date) }
})
.OrderByDescending(h => h.CatchCount)
.ToList();
Hotspot Analysis
// Query logs for hotspot analysis (only opted-in data)
var hotspotData = context.FishingLogEntries
.Where(fle =>
fle.IncludeInHotspotAnalysis == true &&
fle.Latitude >= bounds.South &&
fle.Latitude <= bounds.North &&
fle.Longitude >= bounds.West &&
fle.Longitude <= bounds.East
)
.GroupBy(fle => new
{
Lat = Math.Round(fle.Latitude, 3),
Lon = Math.Round(fle.Longitude, 3)
})
.Select(g => new
{
Location = g.Key,
TotalCatches = g.Count(),
UniqueSpecies = g.Select(c => c.CatchDetails.FishSpeciesId).Distinct().Count(),
AvgDepth = g.Average(c => c.Telemetry.DepthM),
MostCommonSpecies = g.GroupBy(c => c.CatchDetails.FishSpeciesId)
.OrderByDescending(sg => sg.Count())
.First().Key
})
.OrderByDescending(h => h.TotalCatches)
.Take(100) // Top 100 hotspots
.ToList();
Legal Compliance
Data Sharing Agreement
var agreement = new DataSharingAgreement
{
UserId = userId,
AgreementVersion = "1.0",
AgreementType = "DNR",
AgreementText = "Full agreement text...",
ConsentLevel = DataSharingConsent.DNROnly,
AllowDnrAccess = true,
AllowMigrationAnalysis = true,
AllowHotspotAnalysis = false, // User opted out of hotspot sharing
EffectiveDate = DateTime.UtcNow,
ExpirationDate = DateTime.UtcNow.AddYears(1),
Jurisdiction = "Michigan",
LegalBasis = "Conservation and fisheries management",
RequiresExplicitConsent = true,
ConsentDate = DateTime.UtcNow,
UserIpAddress = requestIpAddress,
UserAgent = requestUserAgent
};
Access Audit Trail
Every DNR access is logged:
var accessLog = new DnrAccessLog
{
AccessorUserId = dnrUserId,
FishingLogEntryId = logEntryId,
UserId = logEntry.UserId,
AccessType = "Export",
Purpose = "Annual migration report",
ResearchProjectId = "2024-Salmon-Report",
AccessedLocationData = true,
AccessedCatchData = true,
AccessedTelemetryData = true,
AccessedWeatherData = true,
HasConsent = true,
DataSharingAgreementId = agreement.Id,
AccessedAt = DateTime.UtcNow,
IpAddress = requestIpAddress,
UserAgent = requestUserAgent
};
Consent Revocation
Users can revoke consent:
var agreement = context.DataSharingAgreements
.FirstOrDefault(dsa => dsa.UserId == userId && dsa.IsActive);
if (agreement != null)
{
agreement.IsActive = false;
agreement.RevokedAt = DateTime.UtcNow;
agreement.RevocationReason = "User requested";
// Future logs won't be accessible
// Existing logs remain accessible per agreement terms
}
Best Practices
- Always check consent before allowing DNR access
- Log all access for audit trail
- Respect user privacy - default to private
- Opt-in only - never auto-enroll in data sharing
- Clear consent UI - make consent terms clear and understandable
- Regular audits - review DNR access logs regularly
- Data minimization - only access data needed for purpose
- Anonymization - when possible, use anonymized data
API Endpoints Needed
POST /api/fishing-sessions- Start fishing sessionPOST /api/fishing-sessions/{id}/quick-log- Quick log catchGET /api/fishing-sessions/{id}/catches- Get catches in sessionPUT /api/catches/{id}/review- Review/complete quick-logged catchGET /api/dnr/logs- DNR: Query accessible logs (with consent check)GET /api/dnr/migration-patterns- DNR: Get migration dataGET /api/dnr/hotspots- DNR: Get hotspot dataPOST /api/data-sharing/consent- User consent to data sharingDELETE /api/data-sharing/consent- Revoke consentGET /api/data-sharing/access-logs- User view their access logs