Class: PostRunner::PersonalRecords

Inherits:
PEROBS::Object
  • Object
show all
Includes:
Fit4Ruby::Converters
Defined in:
lib/postrunner/PersonalRecords.rb

Overview

The PersonalRecords class stores the various records. Records are grouped by specific year or all-time records.

Defined Under Namespace

Classes: ActivityResult, Record, RecordSet, SportRecords

Constant Summary collapse

SpeedRecordDistances =

List of popular distances for each sport.

{
  'cycling' => {
    1000.0 => '1 km',
    5000.0 => '5 km',
    8000.0 => '8 km',
    9000.0 => '9 km',
    10000.0 => '10 km',
    20000.0 => '20 km',
    40000.0 => '40 km',
    80000.0 => '80 km',
    90000.0 => '90 km',
    12000.0 => '120 km',
    18000.0 => '180 km',
  },
  'running' => {
    400.0 => '400 m',
    500.0 => '500 m',
    800.0 => '800 m',
    1000.0 => '1 km',
    1609.0 => '1 mi',
    2000.0 => '2 km',
    3000.0 => '3 km',
    5000.0 => '5 km',
    10000.0 => '10 km',
    20000.0 => '20 km',
    30000.0 => '30 km',
    21097.5 => 'Half Marathon',
    42195.0 => 'Marathon'
  },
  'swimming' => {
    100.0 => '100 m',
    300.0 => '300 m',
    400.0 => '400 m',
    750.0 => '750 m',
    1500.0 => '1.5 km',
    1930.0 => '1.2 mi',
    3000.0 => '3 km',
    4000.0 => '4 km',
    3860.0 => '2.4 mi'
  },
  'walking' => {
    500.0 => '500 m',
    1000.0 => '1 km',
    1609.0 => '1 mi',
    5000.0 => '5 km',
    10000.0 => '10 km',
    21097.5 => 'Half Marathon',
    42195.0 => 'Marathon'
  }
}

Instance Method Summary collapse

Constructor Details

#initialize(p) ⇒ PersonalRecords

Returns a new instance of PersonalRecords.



327
328
329
330
331
332
# File 'lib/postrunner/PersonalRecords.rb', line 327

def initialize(p)
  super(p)

  self.sport_records = @store.new(PEROBS::Hash)
  delete_all_records
end

Instance Method Details

#activity_records(activity) ⇒ Object

Return an Array of all the records associated with the given Activity.



506
507
508
509
510
511
512
513
514
515
# File 'lib/postrunner/PersonalRecords.rb', line 506

def activity_records(activity)
  records = []
  each do |record|
    if record.activity.equal?(activity)
      records << record
    end
  end

  records
end

#delete_activity(activity) ⇒ Object



468
469
470
471
472
473
474
475
# File 'lib/postrunner/PersonalRecords.rb', line 468

def delete_activity(activity)
  record_deleted = false
  @sport_records.each_value do |r|
    record_deleted = true if r.delete_activity(activity)
  end

  record_deleted
end

#delete_all_recordsObject



461
462
463
464
465
466
# File 'lib/postrunner/PersonalRecords.rb', line 461

def delete_all_records
  @sport_records.clear
  SpeedRecordDistances.keys.each do |sport|
    @sport_records[sport] = @store.new(SportRecords, sport)
  end
end

#each(&block) ⇒ Object

Iterator for all Record objects that are stored in this data structure.



501
502
503
# File 'lib/postrunner/PersonalRecords.rb', line 501

def each(&block)
  @sport_records.each_value { |r| r.each(&block) }
end

#generate_html_reportsObject



477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/postrunner/PersonalRecords.rb', line 477

def generate_html_reports
  non_empty_records = @sport_records.select { |s, r| !r.empty? }
  max = non_empty_records.length
  i = 0
  non_empty_records.each do |sport, record|
    output_file = File.join(@store['config']['html_dir'],
                            "records-#{i}.html")
    RecordListPageView.new(@store['file_store'], record, max, i).
                           write(output_file)
    i += 1
  end
end

#register_result(activity, sport, distance, duration, start_time) ⇒ Object



449
450
451
452
453
454
455
456
457
458
459
# File 'lib/postrunner/PersonalRecords.rb', line 449

def register_result(activity, sport, distance, duration, start_time)
  unless @sport_records.include?(sport)
    Log.info "Ignoring records for activity type '#{sport}' in " +
             "#{activity.fit_file_name}"
    return false
  end

  result = ActivityResult.new(activity, sport, distance, duration,
                              start_time)
  @sport_records[sport].register_result(result)
end

#scan_activity_for_records(activity, report_update_requested = false) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/postrunner/PersonalRecords.rb', line 334

def scan_activity_for_records(activity, report_update_requested = false)
  # If we have the @norecord flag set, we ignore this Activity for the
  # record collection.
  return if activity.norecord

  activity.load_fit_file

  distance_record = 0.0
  distance_record_sport = nil
  # Array with popular distances (in meters) in ascending order.
  record_distances = nil
  # Speed records for popular distances (seconds hashed by distance in
  # meters)
  speed_records = {}

  # Ignore FIT files that don't have an activity or session
  return unless activity.fit_activity && activity.fit_activity.sessions

  segment_start_time = activity.fit_activity.sessions[0].start_time
  segment_start_distance = 0.0

  sport = nil
  last_timestamp = nil
  last_distance = nil

  activity.fit_activity.records.each do |record|
    if record.distance.nil?
      # All records must have a valid distance mark or the activity does
      # not qualify for a personal record.
      Log.warn "Found a record without a valid distance"
      return
    end
    if record.timestamp.nil?
      Log.warn "Found a record without a valid timestamp"
      return
    end

    unless sport
      # If the Activity has sport set to 'multisport' or 'all' we pick up
      # the sport from the FIT records. Otherwise, we just use whatever
      # sport the Activity provides.
      if activity.sport == 'multisport' || activity.sport == 'all'
        sport = record.activity_type
      else
        sport = activity.sport
      end
      return unless SpeedRecordDistances.include?(sport)

      record_distances = SpeedRecordDistances[sport].
        keys.sort
    end

    segment_start_distance = record.distance unless segment_start_distance
    segment_start_time = record.timestamp unless segment_start_time

    # Total distance covered in this segment so far
    segment_distance = record.distance - segment_start_distance
    # Check if we have reached the next popular distance.
    if record_distances.first &&
       segment_distance >= record_distances.first
      segment_duration = record.timestamp - segment_start_time
      # The distance may be somewhat larger than a popular distance. We
      # normalize the time to the norm distance.
      norm_duration = segment_duration / segment_distance *
        record_distances.first
      # Save the time for this distance.
      speed_records[record_distances.first] = {
        :time => norm_duration, :sport => sport
      }
      # Switch to the next popular distance.
      record_distances.shift
    end

    # We've reached the end of a segment if the sport type changes, we
    # detect a pause of more than 30 seconds or when we've reached the
    # last record.
    if (record.activity_type && sport && record.activity_type != sport) ||
       (last_timestamp && (record.timestamp - last_timestamp) > 30) ||
       record.equal?(activity.fit_activity.records.last)

      # Check for a total distance record
      if segment_distance > distance_record
        distance_record = segment_distance
        distance_record_sport = sport
      end

      # Prepare for the next segment in this Activity
      segment_start_distance = nil
      segment_start_time = nil
      sport = nil
    end

    last_timestamp = record.timestamp
    last_distance = record.distance
  end

  # Store the found records
  start_time = activity.fit_activity.sessions[0].timestamp
  update_reports = false
  if distance_record_sport
    if register_result(activity, distance_record_sport, distance_record,
                       nil, start_time)
      update_reports = true
    end
  end
  speed_records.each do |dist, info|
    if register_result(activity, info[:sport], dist, info[:time],
                       start_time)
      update_reports = true
    end
  end

  generate_html_reports if update_reports && report_update_requested
end

#to_sObject



490
491
492
493
494
495
496
497
498
# File 'lib/postrunner/PersonalRecords.rb', line 490

def to_s
  str = ''
  @sport_records.each do |sport, record|
    next if record.empty?
    str += "Records for activity type #{sport}:\n\n#{record.to_s}"
  end

  str
end