def calc_pace(self, session, distance_units='miles'): """Calculate pace per mile for this split (if applicable).""" current_time = self.time current_distance = self.distance(session, units=distance_units) previous_split = session.splits.filter( time__lt=self.time, athlete=self.athlete, splitfilter__filtered=False).order_by('-time').first() if previous_split is None: if session.start_button_time is None: # This split is the first in the session, can't calculate # pace. return None else: previous_time = session.start_button_time previous_distance = 0.0 else: previous_time = previous_split.time previous_distance = previous_split.distance(session, units=distance_units) if current_distance is None or previous_distance is None: return None pace_seconds = (((current_time - previous_time) / (current_distance - previous_distance)) / 1000.0) return '{} min/{}'.format(format_total_seconds(pace_seconds), distance_units[:-1])
def notify(request): """Send notifications to all subscribers listening to a session. Each notification message includes the session name, athlete name, and total cumulative time. """ split = Split.objects.filter(pk=request.data.get('split'), splitfilter__filtered=False).first() if split is not None: subscriptions = Subscription.objects.filter( session__in=split.timingsession_set.all(), athlete=split.athlete) for subscription in subscriptions: # Athlete must be in results since we are receiving a split # that says so. result = subscription.session.individual_results( athlete_ids=[subscription.athlete_id])[0] text = _message_template.format(name=result.name, session=subscription.session.name, time=format_total_seconds( result.total)) message, created = Message.objects.get_or_create( subscription=subscription, message=text) if created: message.send() return Response(status=status.HTTP_200_OK)
def test_calc_pace_one_checkpoint_with_start(self): """Test calculating pace with a start and one checkpoint.""" self.session.start_button_time = 0 self.session.save() checkpoint1 = Checkpoint.objects.create(session=self.session, name='A', distance=1) checkpoint1.readers.add(self.reader1) SplitFilter.objects.create(timingsession=self.session, split=self.split1) correct_pace = '{} min/mile'.format(format_total_seconds(300)) pace = self.split1.calc_pace(self.session) results = self.session._calc_athlete_splits(self.athlete.id, calc_paces=True) self.assertEqual(pace, correct_pace) self.assertEqual(results.paces[0], correct_pace)
def test_calc_pace_unit_conversion(self): """Test calculating pace with a unit conversion.""" checkpoint1 = Checkpoint.objects.create(session=self.session, name='A', distance=1609.34, distance_units='m') checkpoint2 = Checkpoint.objects.create(session=self.session, name='B', distance=3218.68, distance_units='m') checkpoint1.readers.add(self.reader1) checkpoint2.readers.add(self.reader2) SplitFilter.objects.create(timingsession=self.session, split=self.split1) SplitFilter.objects.create(timingsession=self.session, split=self.split2) pace = self.split2.calc_pace(self.session) self.assertEqual(pace, '{} min/mile'.format(format_total_seconds(300.001)))
def test_calc_pace_multiple_checkpoints(self): """Test calculating pace in a session with multiple checkpoints.""" checkpoint1 = Checkpoint.objects.create(session=self.session, name='A', distance=1) checkpoint2 = Checkpoint.objects.create(session=self.session, name='B', distance=2) checkpoint1.readers.add(self.reader1) checkpoint2.readers.add(self.reader2) SplitFilter.objects.create(timingsession=self.session, split=self.split1) SplitFilter.objects.create(timingsession=self.session, split=self.split2) correct_pace = '{} min/mile'.format(format_total_seconds(300)) pace = self.split2.calc_pace(self.session) results = self.session._calc_athlete_splits(self.athlete.id, calc_paces=True) self.assertEqual(pace, correct_pace) self.assertEqual(results.paces[0], correct_pace)
def export_results(self, request, pk=None): """Get a link to a text results file. The file will be formatted as a CSV with one column for name and one for total time. NOTE: This creates a public link to the results. Anyone with this link will be able to download the file. --- omit_serializer: true omit_parameters: - query parameters_strategy: form: replace parameters: - name: file_format description: Type of file to write ("csv", "pdf", or "tfrrs") - name: results_type description: Whether to return all splits ("splits") or final results only ("final"). Only applied when `file_format` is "csv", otherwise this setting is ignored. type: uri: required: true type: url description: Link to downloadable results file """ session = self.get_object() file_format = request.data.get('file_format', 'csv') if file_format not in ('csv', 'pdf', 'tfrrs'): return Response('Invalid file format', status=status.HTTP_400_BAD_REQUEST) results_type = request.data.get('results_type', 'final') if results_type not in ('splits', 'final'): return Response('Invalid results type', status=status.HTTP_400_BAD_REQUEST) if file_format == 'tfrrs': modifier = '-tfrrs' extension = 'csv' elif file_format == 'csv': modifier = '-splits' if results_type == 'splits' else '' extension = 'csv' else: modifier = '' extension = 'pdf' storage_path = '/'.join( (settings.GCS_RESULTS_DIR, str(session.pk), 'individual{modifier}.{extension}'.format(extension=extension, modifier=modifier))) if file_format == 'tfrrs': results_to_write = tfrrs.format_tfrrs_results(session) header = tfrrs._TFRRS_FIELDS else: results = session.individual_results() if results_type == 'final': results_to_write = (OrderedDict( (('Name', result.name), ('Gender', result.gender), ('Age', result.age), ('Time', format_total_seconds(result.total)), ('Team', result.team.name))) for result in results) header = ('Name', 'Gender', 'Age', 'Time', 'Team') else: max_splits = max(len(result.splits) for result in results) header = list( itertools.chain(('Name', ), ('Gender', ), ('Age', ), ('Interval {}'.format(i + 1) for i in xrange(max_splits)), ('Total', ), ('Team', ))) results_to_write = (OrderedDict( list( itertools.chain( (('Name', result.name), ), (('Gender', result.gender), ), (('Age', result.age), ), (('Interval {}'.format(num + 1), split) for num, split in enumerate(result.splits)), (('Total', format_total_seconds(result.total)), ), (('Team', result.team.name), )))) for result in results) #print results_to_write with gcs_writer(settings.GCS_RESULTS_BUCKET, storage_path, make_public=True) as _results: if file_format in ('csv', 'tfrrs'): writer = csv.DictWriter(_results, fieldnames=header) writer.writeheader() for result in results_to_write: writer.writerow(result) elif file_format == 'pdf': write_pdf_results(_results, results_to_write) log.debug('Saved results to %s', storage_path) return Response({ 'uri': get_public_link(settings.GCS_RESULTS_BUCKET, storage_path) })
def team_csv_results(self, request, pk=None): session = self.get_object() file_format = request.data.get('file_format', 'csv') if file_format not in ('csv'): return Response('Invalid file format', status=status.HTTP_400_BAD_REQUEST) results_type = request.data.get('results_type', 'teams') if results_type not in ('teams'): return Response('Invalid results type', status=status.HTTP_400_BAD_REQUEST) if file_format == 'csv': modifier = '-teams7' if results_type == 'teams' else '' extension = 'csv' storage_path = '/'.join( (settings.GCS_RESULTS_DIR, str(session.pk), 'individual{modifier}.{extension}'.format(extension=extension, modifier=modifier))) raw_results = session.team_results() results = [] with gcs_writer(settings.GCS_RESULTS_BUCKET, storage_path, make_public=True) as _results: if file_format in ('csv'): writer = csv.writer(_results) writer.writerow(['Team Name', 'Team Score', 'Team Place']) counter = 1 for place, result in enumerate(raw_results): team_result = result if team_result['score'] != 0: team_result['place'] = place + counter else: team_result['place'] = 0 counter -= 1 print team_result results.append(team_result) if file_format in ('csv'): writer = csv.writer(_results) writer.writerow([ team_result['name'], team_result['score'], team_result['place'] ]) writer.writerow(['']) writer.writerow( ['', 'Athlete Name', 'Athlete Time', 'Athlete Place']) tmp = 0 for atl in team_result['athletes']: tmp += 1 time = format_total_seconds(atl['total']) if tmp > team_result['num_scorers']: writer.writerow([ '', atl['name'], time, '(' + str(atl['place']) + ')' ]) else: writer.writerow( [' ', atl['name'], time, atl['place']]) writer.writerow(['']) log.debug('Saved results to %s', storage_path) return Response({ 'uri': get_public_link(settings.GCS_RESULTS_BUCKET, storage_path) }) return Response("team CSV")
def export_age_results(self, request, pk=None): session = self.get_object() file_format = request.data.get('file_format', 'csv') if file_format not in ('csv'): return Response('Invalid file format', status=status.HTTP_400_BAD_REQUEST) results_type = request.data.get('results_type', 'age') if results_type not in ('age'): return Response('Invalid results type', status=status.HTTP_400_BAD_REQUEST) if file_format == 'csv': modifier = '-age19' if results_type == 'age' else '' extension = 'csv' storage_path = '/'.join( (settings.GCS_RESULTS_DIR, str(session.pk), 'age{modifier}.{extension}'.format(extension=extension, modifier=modifier))) raw_results = session.individual_results() results = [] male_list = [] female_list = [] unknown_list = [] for runner in raw_results: if runner.gender == 'M': male_list.append(runner) elif runner.gender == 'F': female_list.append(runner) else: unknown_list.append(runner) male_list.sort(key=lambda x: x.age, reverse=True) female_list.sort(key=lambda x: x.age, reverse=True) with gcs_writer(settings.GCS_RESULTS_BUCKET, storage_path, make_public=True) as _results: if file_format in ('csv'): writer = csv.writer(_results) writer.writerow(['Gender']) writer.writerow(['']) writer.writerow(['M']) writer.writerow(['', 'Age Range']) written_row = False written_athlete_row = False BIG_ARRAY = [[0, 19], [20, 29], [30, 39], [40, 49], [50, 59], [60, 69], [70, 120]] while male_list: athlete_in_question = male_list.pop() age = athlete_in_question.age for row in BIG_ARRAY: if age >= row[0] and age <= row[1]: row.append(athlete_in_question) for array in BIG_ARRAY: writer.writerow(['', str(array[0]) + '-' + str(array[1])]) array.pop(0) array.pop(0) array.sort(key=lambda x: x.total) if array: writer.writerow([ '', '', 'Athlete Name', 'Athlete Time', 'Athlete Age', 'Athlete Team' ]) else: pass for element in array: writer.writerow([ '', '', element.name, format_total_seconds(element.total), element.age, element.team.name ]) writer.writerow(['F']) writer.writerow(['']) writer.writerow(['', 'Age Range']) BIG_ARRAY = [[0, 19], [20, 29], [30, 39], [40, 49], [50, 59], [60, 69], [70, 120]] while female_list: athlete_in_question = female_list.pop() age = athlete_in_question.age for row in BIG_ARRAY: if age >= row[0] and age <= row[1]: row.append(athlete_in_question) for array in BIG_ARRAY: writer.writerow(['', str(array[0]) + '-' + str(array[1])]) array.pop(0) array.pop(0) array.sort(key=lambda x: x.total) if array: writer.writerow([ '', '', 'Athlete Name', 'Athlete Time', 'Athlete Age', 'Athlete Team' ]) else: pass for element in array: writer.writerow([ '', '', element.name, format_total_seconds(element.total), element.age, element.team.name ]) return Response({ 'uri': get_public_link(settings.GCS_RESULTS_BUCKET, storage_path) }) return "Age CSV"
def _calc_athlete_splits(self, athlete_id, use_cache=True, apply_filter=True, calc_paces=False): """Calculate splits for a single athlete. First try to read results from the cache. If results are not found, get the tag and all its times. Iterate through times to calculate splits. Save new results to cache. Returns namedtuple of (user id, name, team, splits, total, first_seen, paces) """ # Try to read from the cache. Note that results are cached on a per # tag basis. if use_cache: results = cache.get( ('ts_%i_athlete_%i_results' % (self.id, athlete_id))) else: results = None Results = namedtuple( 'Results', 'user_id name team splits total first_seen last_seen paces bib gender age info' ) if not results: athlete = Athlete.objects.get(id=athlete_id) name = athlete.user.get_full_name() or athlete.user.username filter_ = (models.Q( splitfilter__filtered=False) if apply_filter else models.Q()) raw_splits = self.splits.filter( filter_, athlete_id=athlete.id).order_by('time') times = list(raw_splits.values_list('time', flat=True)) # Offset for start time if needed. if self.start_button_time is not None: times.insert(0, self.start_button_time) splits = [ round((t2 - t1) / 1000.0, 3) for t1, t2 in zip(times, times[1:]) ] if len(times) > 0: raw_time = timezone.datetime.utcfromtimestamp(times[0] / 1000.0) first_seen = raw_time.strftime("%Y/%m/%d %H:%M:%S.%f")[:-3] last_split = timezone.datetime.utcfromtimestamp(times[-1] / 1000.0) last_seen = last_split.strftime("%Y/%m/%d %H:%M:%S.%f")[:-3] else: first_seen = None last_seen = None if calc_paces: distances = [split.distance(self) for split in raw_splits] if self.start_button_time is not None: distances.insert(0, 0.0) paces = [] for i, diff in enumerate(zip(distances, distances[1:])): if diff[0] is not None and diff[1] is not None: pace = '{} min/mile'.format( format_total_seconds(splits[i] / (diff[1] - diff[0]))) else: pace = None paces.append(pace) else: paces = None try: bib = athlete.tag.bib except ObjectDoesNotExist: bib = None try: gender = athlete.gender except ObjectDoesNotExist: gender = None try: age = athlete.age() except ObjectDoesNotExist: age = None try: info = athlete.info_set.filter( timingsession_id=self.id).first().info except AttributeError: info = None results = (athlete_id, name, athlete.team, splits, sum(splits), first_seen, last_seen, paces, bib, gender, age, info) if use_cache: cache.set(('ts_%i_athlete_%i_results' % (self.id, athlete_id)), results) return Results(*results)