first commit - migrated from codeberg
This commit is contained in:
commit
5ead03e1f7
567 changed files with 102721 additions and 0 deletions
383
lib/tools/bloodwork_data_generator.dart
Normal file
383
lib/tools/bloodwork_data_generator.dart
Normal file
|
@ -0,0 +1,383 @@
|
|||
// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
//
|
||||
// bloodwork_data_generator.dart
|
||||
//
|
||||
import 'dart:math' show Random;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nokken/src/core/services/database/database_service.dart';
|
||||
import 'package:nokken/src/features/bloodwork_tracker/models/bloodwork.dart';
|
||||
|
||||
/// A tool class to generate historical bloodwork data
|
||||
class BloodworkDataGenerator {
|
||||
final DatabaseService databaseService;
|
||||
|
||||
BloodworkDataGenerator(this.databaseService);
|
||||
|
||||
/// Generate bloodwork data, including:
|
||||
/// - Bloodwork labs every 70-110 days
|
||||
/// - 2-4 doctor appointments throughout the year
|
||||
/// - 1 surgery appointment
|
||||
Future<void> generateBloodworkData() async {
|
||||
final random = Random();
|
||||
|
||||
// Start date: 364 days ago from today
|
||||
final endDate = DateTime.now();
|
||||
final startDate = endDate.subtract(const Duration(days: 364));
|
||||
|
||||
debugPrint(
|
||||
'Generating bloodwork data from ${startDate.toIso8601String()} to ${endDate.toIso8601String()}');
|
||||
|
||||
// Generate bloodwork labs
|
||||
await _generateBloodworkLabs(startDate, endDate, random);
|
||||
|
||||
// Generate doctor appointments
|
||||
await _generateDoctorAppointments(startDate, endDate, random);
|
||||
|
||||
// Generate a surgery appointment
|
||||
await _generateSurgeryAppointment(startDate, endDate, random);
|
||||
|
||||
debugPrint('Successfully generated bloodwork data.');
|
||||
}
|
||||
|
||||
/// Generate bloodwork labs every 70-110 days
|
||||
Future<void> _generateBloodworkLabs(
|
||||
DateTime startDate, DateTime endDate, Random random) async {
|
||||
// Start a bit before the actual start date to ensure we have consistent coverage
|
||||
DateTime currentDate = startDate.subtract(const Duration(days: 30));
|
||||
int labCount = 0;
|
||||
|
||||
while (currentDate.isBefore(endDate)) {
|
||||
// Add a random interval between 70-110 days
|
||||
final interval = random.nextInt(41) + 70; // 70-110 days
|
||||
currentDate = currentDate.add(Duration(days: interval));
|
||||
|
||||
// Only add if it's within our target range
|
||||
if (currentDate.isAfter(startDate) && currentDate.isBefore(endDate)) {
|
||||
// Generate a bloodwork entry
|
||||
final bloodwork = _generateBloodworkEntry(currentDate, random);
|
||||
await databaseService.insertBloodwork(bloodwork);
|
||||
labCount++;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('Generated $labCount bloodwork lab records');
|
||||
}
|
||||
|
||||
/// Generate 2-4 doctor appointments throughout the year
|
||||
Future<void> _generateDoctorAppointments(
|
||||
DateTime startDate, DateTime endDate, Random random) async {
|
||||
// Decide how many appointments (2-4)
|
||||
final appointmentCount = random.nextInt(3) + 2; // 2-4
|
||||
debugPrint('Generating $appointmentCount doctor appointments');
|
||||
|
||||
// Generate appointment dates (evenly spaced with some randomness)
|
||||
final yearInDays = 364;
|
||||
final appointmentDates = <DateTime>[];
|
||||
|
||||
for (int i = 0; i < appointmentCount; i++) {
|
||||
// Calculate roughly evenly spaced dates with some randomness
|
||||
final targetDay = ((i + 1) * yearInDays / (appointmentCount + 1)).round();
|
||||
final randomOffset = random.nextInt(21) - 10; // +/- 10 days
|
||||
final dayOffset = targetDay + randomOffset;
|
||||
|
||||
final appointmentDate = startDate.add(Duration(days: dayOffset));
|
||||
appointmentDates.add(appointmentDate);
|
||||
}
|
||||
|
||||
// Sort dates and ensure no duplicates
|
||||
appointmentDates.sort((a, b) => a.compareTo(b));
|
||||
final uniqueDates = <DateTime>[];
|
||||
DateTime? lastDate;
|
||||
|
||||
for (final date in appointmentDates) {
|
||||
if (lastDate == null || date.difference(lastDate).inDays > 30) {
|
||||
// Ensure at least 30 days between appointments
|
||||
uniqueDates.add(date);
|
||||
lastDate = date;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and insert appointment records
|
||||
for (final date in uniqueDates) {
|
||||
final appointment = _generateAppointmentEntry(date, random);
|
||||
await databaseService.insertBloodwork(appointment);
|
||||
}
|
||||
|
||||
debugPrint('Generated ${uniqueDates.length} doctor appointment records');
|
||||
}
|
||||
|
||||
/// Generate a surgery appointment within the year
|
||||
Future<void> _generateSurgeryAppointment(
|
||||
DateTime startDate, DateTime endDate, Random random) async {
|
||||
// Place surgery in the middle third of the year (not too recent, not too far back)
|
||||
final yearInDays = 364;
|
||||
final targetDayStart = yearInDays ~/ 3;
|
||||
final targetDayEnd = (yearInDays * 2) ~/ 3;
|
||||
|
||||
final dayOffset =
|
||||
random.nextInt(targetDayEnd - targetDayStart) + targetDayStart;
|
||||
final surgeryDate = startDate.add(Duration(days: dayOffset));
|
||||
|
||||
final surgery = _generateSurgeryEntry(surgeryDate, random);
|
||||
await databaseService.insertBloodwork(surgery);
|
||||
|
||||
debugPrint('Generated surgery record for ${surgeryDate.toIso8601String()}');
|
||||
}
|
||||
|
||||
/// Generate a bloodwork lab entry
|
||||
Bloodwork _generateBloodworkEntry(DateTime date, Random random) {
|
||||
// Always include the four required hormones
|
||||
final hormoneReadings = <HormoneReading>[
|
||||
_generateHormoneReading('Estradiol', random),
|
||||
_generateHormoneReading('Testosterone', random),
|
||||
_generateHormoneReading('Progesterone', random),
|
||||
_generateHormoneReading('Prolactin', random),
|
||||
];
|
||||
|
||||
// Add 0-6 additional random hormones
|
||||
final additionalCount = random.nextInt(7); // 0-6
|
||||
final allHormoneTypes = HormoneTypes.getHormoneTypes();
|
||||
final requiredTypes = {
|
||||
'Estradiol',
|
||||
'Testosterone',
|
||||
'Progesterone',
|
||||
'Prolactin'
|
||||
};
|
||||
final availableTypes =
|
||||
allHormoneTypes.where((type) => !requiredTypes.contains(type)).toList();
|
||||
|
||||
// Shuffle and take random selection
|
||||
availableTypes.shuffle(random);
|
||||
for (int i = 0; i < additionalCount && i < availableTypes.length; i++) {
|
||||
hormoneReadings.add(_generateHormoneReading(availableTypes[i], random));
|
||||
}
|
||||
|
||||
// Random time
|
||||
final timeHour = random.nextInt(8) + 8; // Between a8am and 4pm
|
||||
final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
|
||||
final dateTime = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
timeHour,
|
||||
timeMinute,
|
||||
);
|
||||
|
||||
// Random location
|
||||
final locations = [
|
||||
'Main Hospital Lab',
|
||||
'Downtown Clinic',
|
||||
'Westside Medical Center',
|
||||
'University Health Services',
|
||||
'Eastside Lab',
|
||||
];
|
||||
final location = locations[random.nextInt(locations.length)];
|
||||
|
||||
// Random doctor
|
||||
final doctors = [
|
||||
'Dr. Martinez',
|
||||
'Dr. Johnson',
|
||||
'Dr. Williams',
|
||||
'Dr. Chen',
|
||||
'Dr. Taylor',
|
||||
];
|
||||
final doctor = doctors[random.nextInt(doctors.length)];
|
||||
|
||||
// Random notes
|
||||
final notesOptions = [
|
||||
'Routine bloodwork',
|
||||
'Follow-up lab work',
|
||||
'Quarterly hormone check',
|
||||
'Regular monitoring',
|
||||
'Medication adjustment labs',
|
||||
null, // Sometimes no notes
|
||||
];
|
||||
final notes = notesOptions[random.nextInt(notesOptions.length)];
|
||||
|
||||
return Bloodwork(
|
||||
date: dateTime,
|
||||
appointmentType: AppointmentType.bloodwork,
|
||||
hormoneReadings: hormoneReadings,
|
||||
location: location,
|
||||
doctor: doctor,
|
||||
notes: notes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a doctor appointment entry
|
||||
Bloodwork _generateAppointmentEntry(DateTime date, Random random) {
|
||||
// Random time
|
||||
final timeHour = random.nextInt(8) + 9; // Between 9am and 5pm
|
||||
final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
|
||||
final dateTime = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
timeHour,
|
||||
timeMinute,
|
||||
);
|
||||
|
||||
// Random location
|
||||
final locations = [
|
||||
'Primary Care Clinic',
|
||||
'Endocrinology Department',
|
||||
'Specialty Clinic',
|
||||
'Medical Center',
|
||||
'Healthcare Associates',
|
||||
];
|
||||
final location = locations[random.nextInt(locations.length)];
|
||||
|
||||
// Random doctor
|
||||
final doctors = [
|
||||
'Dr. Martinez',
|
||||
'Dr. Johnson',
|
||||
'Dr. Williams',
|
||||
'Dr. Chen',
|
||||
'Dr. Taylor',
|
||||
];
|
||||
final doctor = doctors[random.nextInt(doctors.length)];
|
||||
|
||||
// Random notes
|
||||
final notesOptions = [
|
||||
'Regular checkup',
|
||||
'Follow-up appointment',
|
||||
'Medication review',
|
||||
'Consultation',
|
||||
'Care planning',
|
||||
'Treatment discussion',
|
||||
null, // Sometimes no notes
|
||||
];
|
||||
final notes = notesOptions[random.nextInt(notesOptions.length)];
|
||||
|
||||
return Bloodwork(
|
||||
date: dateTime,
|
||||
appointmentType: AppointmentType.appointment,
|
||||
hormoneReadings: [], // No hormone readings for appointments
|
||||
location: location,
|
||||
doctor: doctor,
|
||||
notes: notes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a surgery entry
|
||||
Bloodwork _generateSurgeryEntry(DateTime date, Random random) {
|
||||
// Random time (surgeries often early)
|
||||
final timeHour = random.nextInt(5) + 7; // Between 7am and 12pm
|
||||
final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
|
||||
final dateTime = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
timeHour,
|
||||
timeMinute,
|
||||
);
|
||||
|
||||
// Random location
|
||||
final locations = [
|
||||
'University Hospital',
|
||||
'Memorial Surgery Center',
|
||||
'Medical Center',
|
||||
'Surgical Institute',
|
||||
'Regional Hospital',
|
||||
];
|
||||
final location = locations[random.nextInt(locations.length)];
|
||||
|
||||
// Random doctor
|
||||
final doctors = [
|
||||
'Dr. Garcia',
|
||||
'Dr. Smith',
|
||||
'Dr. Patel',
|
||||
'Dr. Wilson',
|
||||
'Dr. Lee',
|
||||
];
|
||||
final doctor = doctors[random.nextInt(doctors.length)];
|
||||
|
||||
// Random notes about the surgery
|
||||
final notesOptions = [
|
||||
'Scheduled procedure',
|
||||
'Outpatient surgery',
|
||||
'Routine operation',
|
||||
'Elective procedure',
|
||||
'Standard intervention',
|
||||
];
|
||||
final notes = notesOptions[random.nextInt(notesOptions.length)];
|
||||
|
||||
return Bloodwork(
|
||||
date: dateTime,
|
||||
appointmentType: AppointmentType.surgery,
|
||||
hormoneReadings: [], // No hormone readings for surgery
|
||||
location: location,
|
||||
doctor: doctor,
|
||||
notes: notes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a random hormone reading
|
||||
HormoneReading _generateHormoneReading(String hormoneName, Random random) {
|
||||
// Get the default unit for this hormone
|
||||
final unit = HormoneTypes.getDefaultUnit(hormoneName);
|
||||
|
||||
// Generate a reasonable value based on the hormone type
|
||||
double value;
|
||||
|
||||
switch (hormoneName) {
|
||||
case 'Estradiol':
|
||||
// Typical range: 30-400 pg/mL
|
||||
value = 30.0 + random.nextDouble() * 370.0;
|
||||
break;
|
||||
case 'Testosterone':
|
||||
// Typical range: 15-70 ng/dL for women, 300-1000 ng/dL for men
|
||||
value = 15.0 + random.nextDouble() * 500.0;
|
||||
break;
|
||||
case 'Progesterone':
|
||||
// Typical range: 0.1-25 ng/mL
|
||||
value = 0.1 + random.nextDouble() * 24.9;
|
||||
break;
|
||||
case 'Prolactin':
|
||||
// Typical range: 3-30 ng/mL
|
||||
value = 3.0 + random.nextDouble() * 27.0;
|
||||
break;
|
||||
case 'FSH':
|
||||
// Typical range: 4-25 mIU/mL
|
||||
value = 4.0 + random.nextDouble() * 21.0;
|
||||
break;
|
||||
case 'LH':
|
||||
// Typical range: 2-20 mIU/mL
|
||||
value = 2.0 + random.nextDouble() * 18.0;
|
||||
break;
|
||||
case 'TSH':
|
||||
// Typical range: 0.4-4.0 μIU/mL
|
||||
value = 0.4 + random.nextDouble() * 3.6;
|
||||
break;
|
||||
case 'Free T3':
|
||||
// Typical range: 2.3-4.2 pg/mL
|
||||
value = 2.3 + random.nextDouble() * 1.9;
|
||||
break;
|
||||
case 'Free T4':
|
||||
// Typical range: 0.8-1.8 ng/dL
|
||||
value = 0.8 + random.nextDouble() * 1.0;
|
||||
break;
|
||||
case 'SHBG':
|
||||
// Typical range: 20-130 nmol/L
|
||||
value = 20.0 + random.nextDouble() * 110.0;
|
||||
break;
|
||||
case 'DHT':
|
||||
// Typical range: 24-65 ng/dL
|
||||
value = 24.0 + random.nextDouble() * 41.0;
|
||||
break;
|
||||
default:
|
||||
// Default range for other hormones
|
||||
value = 10.0 + random.nextDouble() * 90.0;
|
||||
}
|
||||
|
||||
// Round to 1 decimal place
|
||||
value = (value * 10).round() / 10;
|
||||
|
||||
return HormoneReading(
|
||||
name: hormoneName,
|
||||
value: value,
|
||||
unit: unit,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue