My App
Admin App DocsAiRefactoring

Task 2 Services Optimization

SmartCrew Admin - Task 2 Services Optimization

Task 2: Services Layer Optimization and Consolidation

Isolation Scope

This task is completely isolated and focuses ONLY on:

  • Optimizing existing service classes for performance
  • Consolidating duplicate service logic
  • Creating base service classes for common patterns
  • NO changes to providers, repositories, or adapters

Dependencies: None (completely independent of Task 1 and Task 3) Affected Files: Only files in lib/services/ directory (45 service files) Not Included: Providers, repositories, adapters, or UI components

Goal

Optimize service layer performance by 40% and reduce code duplication by 50% while maintaining identical public APIs and calculation results.

Current State - Service Analysis

Service Categories (45 files total)

Labor Services (13 files in lib/services/labor/)

Performance Issues Identified:

  • labor_total_time_service.dart - Multiple iterations over same dataset
  • labor_onsite_log_service.dart - Lines 14-32: Nearly identical to traveling service
  • labor_traveling_log_service.dart - Lines 14-36: 90% duplicate of onsite service
  • labor_machine_time_service.dart - Inefficient task filtering
  • Time card services (7 files) - Repetitive calculation patterns

Equipment Services (5 files in lib/services/equipment/)

Performance Issues:

  • Multiple services iterate over equipment logs separately
  • No caching of expensive calculations
  • Redundant filtering operations

Log Services (8 files in lib/services/log/)

Duplicate Patterns:

  • Similar log calculation logic across multiple services
  • Repeated date filtering operations
  • Common duration calculation patterns

Implementation Steps

Step 1: Create Base Service Classes (Isolated)

1.1 Base Log Calculation Service

File: lib/services/base/base_log_calculation_service.dart

abstract class BaseLogCalculationService {
  final DateTime? date;
  final Iterable<DailyLog> allUserLogs;
  
  const BaseLogCalculationService({required this.date, required this.allUserLogs});
  
  // Common filtering logic (used by 8+ services)
  Iterable<DailyLog> getFilteredLogs() {
    return allUserLogs.where((log) => 
        !log.isDayOff && 
        log.clockIn != null && 
        log.isClockedOut && 
        log.status != RequestStatus.denied &&
        _isInRange(log)
    );
  }
  
  // Optimized single-pass task extraction
  Iterable<Task> extractTasksWithFilter(bool Function(WorkActivity) activityFilter);
  
  bool _isInRange(DailyLog element) => date == null || element.logDate.isSameDate(date!);
}

1.2 Base Time Calculation Service

File: lib/services/base/base_time_calculation_service.dart

abstract class BaseTimeCalculationService extends BaseLogCalculationService {
  const BaseTimeCalculationService({required super.date, required super.allUserLogs});
  
  // Optimized duration calculation with memoization
  Duration calculateDuration() {
    final tasks = getFilteredTasks();
    return TaskDurationCalculator.calculateOverlapAwareDuration(tasks);
  }
  
  // Abstract method for different filtering strategies
  Iterable<Task> getFilteredTasks();
}

Step 2: Consolidate Duplicate Services

2.1 Merge Labor Location Services

Current Duplicate Code:

  • LaborOnsiteLogService (36 lines)
  • LaborTravelingLogService (37 lines)
  • 90% identical logic, only filter criteria differs

New Consolidated Service: File: lib/services/labor/labor_log_filter_service.dart

enum LogFilter { 
  onsite,      // jobsiteId != travelingId && jobsiteId.isNotEmpty
  traveling,   // jobsiteId == travelingId || jobsiteId.isEmpty
  machine,     // has machine activities
  all          // no filtering
}

class LaborLogFilterService extends BaseTimeCalculationService {
  final LogFilter filter;
  
  const LaborLogFilterService({
    required this.filter,
    required super.date, 
    required super.allUserLogs
  });
  
  @override
  Iterable<Task> getFilteredTasks() {
    final workLogs = getFilteredLogs().expand((log) => log.jobsiteActivities);
    
    return workLogs
        .where(_getActivityFilter())
        .expand((activity) => activity.tasks)
        .where((task) => task.status == TaskStatus.approved);
  }
  
  bool Function(WorkActivity) _getActivityFilter() {
    switch (filter) {
      case LogFilter.onsite:
        return (activity) => activity.jobsiteId != JobSite.travelingJobsiteId && 
                           activity.jobsiteId.isNotEmpty;
      case LogFilter.traveling:
        return (activity) => activity.jobsiteId == JobSite.travelingJobsiteId || 
                           activity.jobsiteId.isEmpty;
      case LogFilter.machine:
        return (activity) => activity.hasMachineActivities;
      case LogFilter.all:
        return (activity) => true;
    }
  }
}

2.2 Update Existing Services to Use Consolidated Version

Replace: LaborOnsiteLogService

// OLD - Delete this file
class LaborOnsiteLogService {
  Duration calculate() {
    // 32 lines of duplicate code
  }
}

// NEW - Use consolidated service
class LaborOnsiteLogService {
  final DateTime? date;
  final Iterable<DailyLog> allUserLogs;
  
  const LaborOnsiteLogService({required this.date, required this.allUserLogs});
  
  Duration calculate() {
    return LaborLogFilterService(
      filter: LogFilter.onsite,
      date: date,
      allUserLogs: allUserLogs
    ).calculateDuration();
  }
}

Step 3: Optimize Performance-Critical Services

3.1 Optimize Labor Daily Log Service

Current Issues in labor_daily_log_service.dart:

  • Lines 20-41: Multiple service instantiations for same dataset
  • No caching of intermediate results
  • Repeated iteration over same logs

Optimization:

class LaborDailyLogService {
  final AppUser user;
  final DateTime date;
  final NetworkDataProvider provider = NetworkDataProvider();
  
  // Cache for expensive calculations
  static final Map<String, Duration> _calculationCache = {};
  
  Labor? createLaborInfo() {
    final userLogs = provider.logs
        .firstWhereOrNull((userLog) => userLog.user.firebaseUid == user.firebaseUid)?.logs;
    
    if (userLogs == null || userLogs.isEmpty) return null;
    
    // Single-pass calculation instead of 4 separate service calls
    final calculations = _calculateAllDurations(userLogs, date);
    
    if (calculations.totalTime.inMinutes == 0) return null;
    
    return Labor(
      firebaseUid: user.firebaseUid,
      phoneNumber: user.phoneNumber,
      employee: user.fullName,
      role: user.role,
      status: user.active,
      totalHours: calculations.totalTime.formatHourMinute,
      shopHours: calculations.shopTime.formatHourMinute,
      travelHours: calculations.travelTime.formatHourMinute,
      machineHours: calculations.machineTime.formatHourMinute
    );
  }
  
  // Optimized single-pass calculation
  LaborCalculations _calculateAllDurations(List<DailyLog> logs, DateTime date) {
    final cacheKey = '${user.firebaseUid}_${date.millisecondsSinceEpoch}';
    
    // Check cache first
    if (_calculationCache.containsKey(cacheKey)) {
      return _getCachedCalculations(cacheKey);
    }
    
    // Single service instance handles all calculations
    final calculator = OptimizedLaborCalculatorService(logs: logs, date: date);
    final results = calculator.calculateAll();
    
    // Cache results
    _cacheCalculations(cacheKey, results);
    
    return results;
  }
}

Step 4: Create Service Factory Pattern

4.1 Time Card Service Factory

Current: 7 separate time card service files with similar constructors Optimized: Single factory with type-safe creation

File: lib/services/labor/time_card/labor_time_card_factory.dart

enum TimeCardType { daily, weekly, monthly, yearly, all }

class LaborTimeCardFactory {
  static LaborTimeCardService create({
    required TimeCardType type,
    required AppUser user,
    required DateTime date,
  }) {
    switch (type) {
      case TimeCardType.daily:
        return LaborDailyTimeCardService(user: user, date: date);
      case TimeCardType.weekly:
        return LaborWeekTimeCardService(user: user, startDate: date);
      case TimeCardType.monthly:
        return LaborMonthTimeCardService(user: user, month: date);
      // ... other types
    }
  }
  
  // Batch creation for performance
  static Map<TimeCardType, LaborTimeCardService> createAll({
    required AppUser user,
    required DateTime date,
  }) {
    return {
      for (final type in TimeCardType.values)
        type: create(type: type, user: user, date: date)
    };
  }
}

Testing & Validation Metrics

Pre-Optimization Baseline Tests

# 1. Capture Current Performance Baseline
void captureServicePerformance() {
  final stopwatch = Stopwatch()..start();
  
  // Labor calculation baseline
  final laborService = LaborDailyLogService(user: testUser, date: testDate);
  final labor = laborService.createLaborInfo();
  stopwatch.stop();
  print('Labor calculation baseline: ${stopwatch.elapsedMilliseconds}ms');
  
  // Equipment calculation baseline
  stopwatch.reset()..start();
  final equipmentService = EquipmentLogsService(equipment: testEquipment);
  final logs = equipmentService.getUsageLogs();
  stopwatch.stop();
  print('Equipment calculation baseline: ${stopwatch.elapsedMilliseconds}ms');
}

# 2. Capture Exact Calculation Results
void captureCalculationBaseline() {
  // Document exact calculation results for comparison
  final results = {};
  results['totalTime'] = laborService.calculateTotalTime();
  results['onsiteTime'] = onsiteService.calculate();
  results['travelTime'] = travelService.calculate();
  // Store all current calculation results
}

# 3. Memory Usage Baseline
void captureMemoryBaseline() {
  // Run service operations and measure memory usage
  final memoryBefore = getMemoryUsage();
  runAllServiceOperations();
  final memoryAfter = getMemoryUsage();
  final baseline = memoryAfter - memoryBefore;
  print('Service memory baseline: ${baseline}MB');
}

Post-Optimization Validation Metrics

Metric 1: Identical Calculation Results

# All calculations must return EXACTLY the same results
void testCalculationAccuracy() {
  final beforeResults = runBaselineCalculations();
  final afterResults = runOptimizedCalculations();
  
  expect(afterResults.totalTime, equals(beforeResults.totalTime));
  expect(afterResults.onsiteTime, equals(beforeResults.onsiteTime));
  expect(afterResults.travelTime, equals(beforeResults.travelTime));
  expect(afterResults.machineTime, equals(beforeResults.machineTime));
  
  // Test with edge cases
  expect(calculateWithEmptyLogs(), equals(Duration.zero));
  expect(calculateWithNullDates(), equals(baselineNullDateResult));
}

Metric 2: Performance Improvement (40% target)

void testPerformanceImprovement() {
  // Labor calculation should be 40% faster
  final laborTime = measureLaborCalculation();
  expect(laborTime, lessThan(baselineLaborTime * 0.6));
  
  // Equipment services should be 35% faster
  final equipmentTime = measureEquipmentCalculation();
  expect(equipmentTime, lessThan(baselineEquipmentTime * 0.65));
  
  // Time card generation should be 50% faster
  final timeCardTime = measureTimeCardGeneration();
  expect(timeCardTime, lessThan(baselineTimeCardTime * 0.5));
  
  // Overall service layer should be 40% faster
  final overallTime = measureAllServiceOperations();
  expect(overallTime, lessThan(baselineOverallTime * 0.6));
}

Metric 3: Memory Usage Reduction (30% target)

void testMemoryOptimization() {
  final memoryUsage = measureServiceMemoryUsage();
  expect(memoryUsage, lessThan(baselineMemoryUsage * 0.7));
  
  // No memory leaks in repetitive operations
  for (int i = 0; i < 1000; i++) {
    runServiceOperation();
  }
  final finalMemory = getMemoryUsage();
  expect(finalMemory, lessThan(baselineMemoryUsage * 1.1)); // Max 10% increase
}

Metric 4: Code Duplication Reduction (50% target)

void testCodeDuplication() {
  // Count lines of duplicate code before/after
  final beforeDuplicateLines = countDuplicateServiceLines();
  final afterDuplicateLines = countDuplicateServiceLines();
  
  final reduction = (beforeDuplicateLines - afterDuplicateLines) / beforeDuplicateLines;
  expect(reduction, greaterThan(0.5)); // 50% reduction target
  
  // Verify base classes are being used
  expect(laborOnsiteService, isA<BaseTimeCalculationService>());
  expect(laborTravelingService, isA<BaseTimeCalculationService>());
}

Metric 5: Service API Compatibility

void testServiceAPICompatibility() {
  // All public service methods must work unchanged
  expect(() => LaborDailyLogService(user: user, date: date), returnsNormally);
  expect(() => laborService.createLaborInfo(), returnsNormally);
  
  // Return types must be identical
  final labor = laborService.createLaborInfo();
  expect(labor, isA<Labor?>());
  expect(labor?.totalHours, isA<String>());
  
  // Constructor signatures must be unchanged
  expect(() => LaborTotalTimeService(date: date, allUserLogs: logs), returnsNormally);
  expect(() => EquipmentLogsService(equipment: equipment), returnsNormally);
}

Metric 6: Edge Case Handling

void testEdgeCaseHandling() {
  // Empty data handling
  final emptyResult = serviceWithEmptyData.calculate();
  expect(emptyResult, equals(Duration.zero));
  
  // Null data handling
  final nullResult = serviceWithNullData.calculate();
  expect(nullResult, equals(baselineNullResult));
  
  // Invalid date handling
  final invalidDateResult = serviceWithInvalidDate.calculate();
  expect(invalidDateResult, equals(baselineInvalidDateResult));
  
  // Large dataset handling
  final largeDatasetTime = measureLargeDatasetCalculation();
  expect(largeDatasetTime, lessThan(acceptableMaxTime));
}

Success Validation Checklist

Performance Validation

  • 40% improvement in overall service performance
  • 30% reduction in memory usage
  • No performance regression in any service
  • Cache hit rate > 80% for repeated calculations
  • Large dataset handling improved by 50%

Accuracy Validation

  • All calculations return identical results to baseline
  • Edge cases handled identically
  • No floating point precision issues introduced
  • Date/time calculations maintain accuracy
  • Null and empty data handled correctly

Code Quality Validation

  • 50% reduction in duplicate code lines
  • All services use appropriate base classes
  • No circular dependencies introduced
  • Proper error handling maintained
  • Documentation updated for new patterns

Compatibility Validation

  • All public service APIs unchanged
  • Constructor signatures preserved
  • Return types identical
  • Exception handling behavior preserved
  • Thread safety maintained

Rollback Strategy

Immediate Rollback

  • Keep original service files as .backup during development
  • Restore individual services if any metric fails
  • Git branch strategy allows atomic rollbacks

Selective Rollback

  • Rollback can be done per service category:
    • Labor services independently
    • Equipment services independently
    • Log services independently

Validation Gates

  • Each service optimization must pass all metrics before proceeding
  • Automated testing prevents broken builds
  • Performance regression detection stops deployment

Files Modified (Complete List)

New Base Service Files

  • lib/services/base/base_log_calculation_service.dart
  • lib/services/base/base_time_calculation_service.dart
  • lib/services/base/base_usage_service.dart

New Consolidated Services

  • lib/services/labor/labor_log_filter_service.dart
  • lib/services/labor/optimized_labor_calculator_service.dart

Modified Existing Services (Labor - 13 files)

  • lib/services/labor/labor_daily_log_service.dart (optimized)
  • lib/services/labor/labor_onsite_log_service.dart (uses consolidated)
  • lib/services/labor/labor_traveling_log_service.dart (uses consolidated)
  • lib/services/labor/labor_total_time_service.dart (optimized)
  • lib/services/labor/labor_machine_time_service.dart (optimized)
  • All 7 time card services (use factory pattern)

Modified Equipment Services (5 files)

  • All equipment services optimized for performance

Modified Log Services (8 files)

  • All log services use new base classes

Total Files: 31 (5 new, 26 modified) Scope: Only service files - no providers, repositories, adapters, or UI touched

On this page