From 3040997317d5dfa285da93fa266501f80c072d24 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 5 Mar 2024 22:32:12 +0100 Subject: [PATCH 001/121] first map using microservice --- rowers/interactiveplots.py | 181 +++++++------------------------------ rowsandall_app/settings.py | 7 ++ 2 files changed, 40 insertions(+), 148 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4c03a9bb..03b8cc38 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -82,10 +82,24 @@ queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') +import requests activate(settings.TIME_ZONE) thetimezone = get_current_timezone() +def get_chart(end_point, chart_data): + + url = settings.ROWSANDALL_CHARTS_URL+end_point + headers = {'authorization':"Bearer {token}".format(token=settings.ROWSANDALL_CHARTS_TOKEN)} + response = requests.post(url, json=chart_data, headers=headers) + if response.status_code == 200: + script = response.json()['script'] + div = response.json()['div'] + else: + script = '' + div = response.reason + + return script, div def workoutname(id): try: @@ -2164,162 +2178,33 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, def course_map(course): - latmean, lonmean, coordinates = course_coord_center(course) - if course.with_cn_nav_waypoints: - latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - coordinates = course_spline(coordinates) - - scoordinates = "[" - - for index, row in coordinates.iterrows(): - scoordinates += """[{x},{y}], - """.format( - x=row['latitude'], - y=row['longitude'] - ) - - scoordinates += "]" - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - scoordinates=scoordinates, - pcoordinates=pcoordinates, - plabels=plabels - ) - - div = """ -
- """ + course_dict['polygons'] = polygons_list + script, div = get_chart("/map", course_dict) + return script, div + + def get_map_script_course( latmean, lonmean, diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index 92f7b5b4..1f1ae122 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -599,6 +599,13 @@ except KeyError: # pragma: no cover WORKOUTS_FIT_TOKEN = 'aapnootmies' WORKOUTS_FIT_URL = 'http://localhost:50053/tojson' +try: + ROWSANDALL_CHARTS_TOKEN = CFG['rowsandall_charts_token'] + ROWSANDALL_CHARTS_URL = CFG['rowsandall_charts_url'] +except KeyError: + ROWSANDALL_CHARTS_TOKEN = 'aapnootmies' + ROWSANDALL_CHARTS_URL = 'http://localhost:3000' + # Recaptcha try: From 74beae0d732500ee369317aa61856e680657db90 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 5 Mar 2024 23:52:12 +0100 Subject: [PATCH 002/121] adding sleep BokehJS example --- rowers/templates/sleep.html | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 rowers/templates/sleep.html diff --git a/rowers/templates/sleep.html b/rowers/templates/sleep.html new file mode 100644 index 00000000..0b0c3167 --- /dev/null +++ b/rowers/templates/sleep.html @@ -0,0 +1,53 @@ +{% extends "newbase.html" %} +{% load static %} +{% load rowerfilters %} + +{% block title %}Rowsandall {% endblock %} + +{% block main %} +
+ + +
+
+ + + + + + + +
+ + + + + +

Sleep

+ + + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} From 092ce85214b66111a1c098e7a0ac02a841be971e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 5 Mar 2024 23:52:49 +0100 Subject: [PATCH 003/121] adding sleep as bokehJS example --- rowers/interactiveplots.py | 35 ++++++++++++++++------------------- rowers/urls.py | 1 + rowers/views/otherviews.py | 12 ++++++++++++ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 03b8cc38..67c1996b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -83,6 +83,7 @@ queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') import requests +from rowers.serializers import * activate(settings.TIME_ZONE) thetimezone = get_current_timezone() @@ -101,6 +102,19 @@ def get_chart(end_point, chart_data): return script, div +# Example for BokehJS +def sleep(): + data = { + 'work': 8, + 'eat': 2, + 'commute': 2, + 'sport': 0, + 'tv': 1, + 'sleep': 8, + } + script, div = get_chart("/sleep", data) + return script, div + def workoutname(id): try: w = Workout.objects.get(id=id) @@ -2179,25 +2193,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, def course_map(course): - course_dict = { - 'name': course.name, - 'country': course.country, - 'notes': course.notes, - } - - - polygons_list = [] - for p in GeoPolygon.objects.filter(course=course).order_by('order_in_course'): - points = GeoPoint.objects.filter(polygon=p).order_by('order_in_poly') - polygons_list.append({ - 'name': p.name, - 'points': [{ - 'latitude': pt.latitude, 'longitude': pt.longitude, 'order_in_poly': pt.order_in_poly - } for pt in points ] - }) - - course_dict['polygons'] = polygons_list - + course_dict = GeoCourseSerializer(course).data + script, div = get_chart("/map", course_dict) return script, div diff --git a/rowers/urls.py b/rowers/urls.py index 30326a73..36d68870 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -272,6 +272,7 @@ urlpatterns = [ re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view, name='workouts_summaries_email_view'), path('failedjobs/', views.failed_queue_view, name='failed_queue_view'), + path('sleep/', views.sleep_view, name='sleep_view'), path('failedjobs/empty/', views.failed_queue_empty, name='failed_queue_empty'), re_path('^failedjobs/(?P\w+.*)/$', views.failed_job_view, name='failed_job_view'), diff --git a/rowers/views/otherviews.py b/rowers/views/otherviews.py index a88d4070..1366bc1c 100644 --- a/rowers/views/otherviews.py +++ b/rowers/views/otherviews.py @@ -1,10 +1,22 @@ from rowers.views.statements import * +from rowers.interactiveplots import sleep from rq import Queue from redis import Redis from rq.job import Job +@login_required() +def sleep_view(request): + + script, div = sleep() + + return render(request, + "sleep.html", + { + "the_div": div, + "the_script": script, + }) @login_required() def download_fit(request, filename=''): From d8a0cdc1c67a45345c6c6751e0349189f0fdcee9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 6 Mar 2024 13:10:59 +0100 Subject: [PATCH 004/121] starting map compare --- rowers/interactiveplots.py | 330 +++----------------------- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes 2 files changed, 28 insertions(+), 302 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 67c1996b..73b041c5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -92,7 +92,13 @@ def get_chart(end_point, chart_data): url = settings.ROWSANDALL_CHARTS_URL+end_point headers = {'authorization':"Bearer {token}".format(token=settings.ROWSANDALL_CHARTS_TOKEN)} - response = requests.post(url, json=chart_data, headers=headers) + try: + response = requests.post(url, json=chart_data, headers=headers) + except: + script = '' + div = 'Chart Server Error' + return script, div + if response.status_code == 200: script = response.json()['script'] div = response.json()['div'] @@ -2202,273 +2208,6 @@ def course_map(course): -def get_map_script_course( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - course, -): # pragma: no cover - latmean, lonmean, coordinates = course_coord_center(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - - coordinates = course_spline(coordinates) - - scoordinates = "[" - - for index, row in coordinates.iterrows(): - scoordinates += """[{x},{y}], - """.format( - x=row['latitude'], - y=row['longitude'] - ) - - scoordinates += "]" - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - scoordinates=scoordinates, - pcoordinates=pcoordinates, - plabels=plabels - ) - - return script - - -def get_map_script( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, -): - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - return script def leaflet_chart(lat, lon, name="", raceresult=0): @@ -2500,48 +2239,31 @@ def leaflet_chart(lat, lon, name="", raceresult=0): coordinates = zip(lat, lon) - scoordinates = "[" + data = { + 'coordinates': [{'latitude': c[0], 'longitude': c[1]} for c in list(coordinates)], + 'latmean': latmean, + 'lonmean': lonmean, + 'latbegin': latbegin, + 'latend': latend, + 'longbegin': longbegin, + 'longend': longend, + } - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - if raceresult == 0: - script = get_map_script( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - ) - else: # pragma: no cover + if raceresult != 0: record = VirtualRaceResult.objects.get(id=raceresult) course = record.course - script = get_map_script_course( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - course, - ) + course_dict = GeoCourseSerializer(course).data + data['course'] = course_dict - div = """ -

 

- """ + + coordinates = zip(lat, lon) + + script, div = get_chart("/workoutmap", data) return script, div + def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): data = [] for id in workoutids: @@ -2564,6 +2286,10 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except ValueError: # pragma: no cover df = pd.DataFrame() + mapdata = { + 'trajectories': df.reset_index().to_json() + } + latmean, lonmean, coordinates = course_coord_center(course) lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e380513e867d867d4c7b9150dbb1e0a971ee706e..fad5c58be2eec72a8a29b0f1eab8f4138b3dab35 100644 GIT binary patch literal 4001 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 Date: Wed, 6 Mar 2024 17:20:29 +0100 Subject: [PATCH 005/121] adding video charts --- rowers/interactiveplots.py | 546 ++----------------- rowers/templates/course_edit_view.html | 5 +- rowers/templates/course_replace.html | 5 +- rowers/templates/course_replace_confirm.html | 4 +- rowers/templates/course_view.html | 5 +- rowers/templates/coursemap.html | 4 +- rowers/templates/map_view.html | 5 +- rowers/templates/mapcompare.html | 4 +- rowers/templates/multicompare.html | 4 +- rowers/templates/panel_map.html | 5 +- rowers/templates/windedit.html | 4 +- rowers/templates/workout_form.html | 4 +- rowers/templates/workout_view.html | 5 +- rowers/views/workoutviews.py | 17 +- 14 files changed, 74 insertions(+), 543 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 73b041c5..ad456ad2 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2211,8 +2211,13 @@ def course_map(course): def leaflet_chart(lat, lon, name="", raceresult=0): - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] + try: + if lat.empty or lon.empty: # pragma: no cover + return [0, "invalid coordinate data"] + except AttributeError: + if not len(lat) or not len(lon): # pragma: no cover + return [0, "invalid coordinate data"] + # Throw out 0,0 df = pd.DataFrame({ @@ -2286,56 +2291,9 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except ValueError: # pragma: no cover df = pd.DataFrame() - mapdata = { - 'trajectories': df.reset_index().to_json() - } - latmean, lonmean, coordinates = course_coord_center(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - coordinates = course_spline(coordinates) - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" + course_dict = GeoCourseSerializer(course).data # Throw out 0,0 df = df.replace(0, np.nan) @@ -2357,89 +2315,7 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except AttributeError: items = zip(workoutids, colors) - script = """ - - """ - - div = """ -

 

- """ + mapdata = { + 'course': course_dict, + 'latmean': latmean, + 'lonmean': lonmean, + 'trajectories': trajectories, + } + + script, div = get_chart("/mapcompare", mapdata) return script, div -def leaflet_chart2(lat, lon, name=""): - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - # Throw out 0,0 - df = pd.DataFrame({ - 'lat': lat, - 'lon': lon - }) - - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] - - coordinates = zip(lat, lon) - - scoordinates = "[" - - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - return script, div - - -def leaflet_chart_video(lat, lon, name=""): - if not len(lat) or not len(lon): # pragma: no cover - return [0, "invalid coordinate data"] - - # Throw out 0,0 - df = pd.DataFrame({ - 'lat': lat, - 'lon': lon - }) - - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - - coordinates = zip(lat, lon) - - scoordinates = "[" - - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - - var streets = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/streets-v11', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ), - - satellite = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/satellite-v9', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ), - - outdoors = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/outdoors-v11', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ); - - - - var mymap = L.map('map_canvas', {{ - center: [{latmean}, {lonmean}], - zoom: 13, - layers: [streets, satellite] - }}).setView([{latmean},{lonmean}], 13); - - var navionics = new JNC.Leaflet.NavionicsOverlay({{ - navKey: 'Navionics_webapi_03205', - chartType: JNC.NAVIONICS_CHARTS.NAUTICAL, - isTransparent: true, - zIndex: 1 - }}); - - - var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png'; - var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png'; - - - //create two TileLayer - var nautical=new L.TileLayer(osmUrl,{{ - maxZoom:18}}); - - - L.control.layers({{ - "Streets": streets, - "Satellite": satellite, - "Outdoors": outdoors, - "Nautical": nautical, - }},{{ - "Navionics":navionics, - }}, - {{ - position:'topleft' - }}).addTo(mymap); - - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("Start"); - - var latlongs = {scoordinates} - var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap) - mymap.fitBounds(polyline.getBounds()) - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - longbegin=longbegin, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - return script, div def interactive_agegroupcpchart(age, normalized=False): @@ -4272,12 +3866,10 @@ def interactive_chart_video(videodata): data = zip(time, spm) - data2 = "[" + data2 = [] for t, s in data: - data2 += "{x: %s, y: %s}, " % (t, s) - - data2 = data2[:-2] + "]" + data2.append( {'x': t, 'y': s}) markerpoint = { 'x': time[0], @@ -4285,89 +3877,13 @@ def interactive_chart_video(videodata): 'r': 10, } - div = """ - - - """ - - script = """ - var ctx = document.getElementById("myChart").getContext('2d'); - var data = %s - - var myChart = new Chart(ctx, { - type: 'scatter', - label: 'SPM', - animationSteps: 10, - options: { - legend: { - display: false, - }, - animation: { - duration: 100, - }, - scales: { - yAxes: [{ - scaleLabel: { - display: true, - labelString: 'Stroke Rate' - } - }], - xAxes: [{ - scaleLabel: { - type: 'linear', - display: true, - labelString: 'Time (seconds)' - } - }], - } - }, - data: - { - datasets: [ - { - type: 'bubble', - label: 'now', - data: [ %s ], - backgroundColor: '#36a2eb', - }, - { - label: 'spm', - data: data, - backgroundColor: "#ff0000", - borderColor: "#ff0000", - fill: false, - borderDash: [0, 0], - pointRadius: 1, - pointHoverRadius: 1, - showLine: true, - tension: 0, - }, - - ] - }, - - }); - - var marker = { - datapoint: %s , - setLatLng: function (LatLng) { - var lat = LatLng.lat; - var lng = LatLng.lng; - this.datapoint = { - 'x': lat, - 'y': lng, - 'r': 10, - } - myChart.data.datasets[0].data[0] = this.datapoint; - myChart.update(); + chart_data = { + 'data': data2, + 'markerpoint': markerpoint, } - } - marker.setLatLng({ - 'lat': data[0]['x'], - 'lng': data[0]['y'] - }) - """ % (data2, markerpoint, markerpoint) + + script, div = get_chart("/videochart", chart_data) return [script, div] diff --git a/rowers/templates/course_edit_view.html b/rowers/templates/course_edit_view.html index 58b2cb64..29be3f99 100644 --- a/rowers/templates/course_edit_view.html +++ b/rowers/templates/course_edit_view.html @@ -32,8 +32,9 @@
{{ mapdiv|safe }} - - {{ mapscript|safe }} +
diff --git a/rowers/templates/course_replace.html b/rowers/templates/course_replace.html index 53569c1f..067c90ff 100644 --- a/rowers/templates/course_replace.html +++ b/rowers/templates/course_replace.html @@ -37,8 +37,9 @@
{{ mapdiv|safe }} - - {{ mapscript|safe }} +
diff --git a/rowers/templates/course_replace_confirm.html b/rowers/templates/course_replace_confirm.html index 965b9180..c057f2c1 100644 --- a/rowers/templates/course_replace_confirm.html +++ b/rowers/templates/course_replace_confirm.html @@ -45,7 +45,9 @@
  • {{ mapdiv|safe }} - {{ mapscript|safe }} +
  • diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html index 37cb41d9..e3f2db0b 100644 --- a/rowers/templates/course_view.html +++ b/rowers/templates/course_view.html @@ -39,8 +39,9 @@
    {{ mapdiv|safe }} - - {{ mapscript|safe }} +
    {% if records %} diff --git a/rowers/templates/coursemap.html b/rowers/templates/coursemap.html index d926f731..196ce47f 100644 --- a/rowers/templates/coursemap.html +++ b/rowers/templates/coursemap.html @@ -19,7 +19,9 @@

    {{ course.name }}

    {{ mapdiv|safe }} - {{ mapscript|safe }} +
    {% endblock %} diff --git a/rowers/templates/map_view.html b/rowers/templates/map_view.html index 17eedd47..7938fe02 100644 --- a/rowers/templates/map_view.html +++ b/rowers/templates/map_view.html @@ -26,8 +26,9 @@
    {{ mapdiv|safe }} - - {{ mapscript|safe }} + diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html index a00044a6..52b582b4 100644 --- a/rowers/templates/mapcompare.html +++ b/rowers/templates/mapcompare.html @@ -25,7 +25,9 @@
    {{ mapdiv|safe }} - {{ mapscript|safe }} +
    diff --git a/rowers/templates/multicompare.html b/rowers/templates/multicompare.html index 59ddd994..7b5a8415 100644 --- a/rowers/templates/multicompare.html +++ b/rowers/templates/multicompare.html @@ -11,8 +11,10 @@ Bokeh.set_log_level("info"); + +

    Interactive Comparison

    diff --git a/rowers/templates/panel_map.html b/rowers/templates/panel_map.html index 8e45af39..25941388 100644 --- a/rowers/templates/panel_map.html +++ b/rowers/templates/panel_map.html @@ -3,8 +3,9 @@
    {{ mapdiv|safe }} - - {{ mapscript|safe }} +
    diff --git a/rowers/templates/windedit.html b/rowers/templates/windedit.html index 25e4e74e..26322e6e 100644 --- a/rowers/templates/windedit.html +++ b/rowers/templates/windedit.html @@ -67,7 +67,9 @@
    {{ gmapdiv |safe }} - {{ gmap |safe }} +
    diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index a95424f6..f3d6c3f0 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -217,9 +217,9 @@
    {{ mapdiv|safe }}
    - + {% endif %} {% for graph in graphs %} diff --git a/rowers/templates/workout_view.html b/rowers/templates/workout_view.html index bfb12fe2..af45ac7e 100644 --- a/rowers/templates/workout_view.html +++ b/rowers/templates/workout_view.html @@ -131,8 +131,9 @@ {{ mapdiv|safe }} - - {{ mapscript|safe }} +
    {% endif %} diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index b22a7da4..61e3b86d 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -126,8 +126,7 @@ def workout_video_view_mini(request, id=''): hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -237,8 +236,7 @@ def workout_video_view(request, id=''): hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -353,8 +351,7 @@ def workout_video_create_view(request, id=0): # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -4141,7 +4138,7 @@ def workout_workflow_view(request, id): if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row): rowdata = rdata(csvfile=row.csvfilename) - mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'], + mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) else: @@ -4182,6 +4179,8 @@ def workout_workflow_view(request, id): ] + print(middleTemplates) + return render(request, 'workflow.html', { @@ -5063,7 +5062,7 @@ def workout_map_view(request, id=0): hascoordinates = 0 if hascoordinates: - mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'], + mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'], rowdata.df[' longitude'], w.name) else: @@ -5188,7 +5187,7 @@ def workout_uploadimage_view(request, id): # pragma: no cover # Generic chart creation @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_add_chart_view(request, id, plotnr=1): - + w = get_workoutuser(id, request) r = getrower(request.user) From 1fa5f64b50df1efb1d0d8d0213aff20a3e076eb7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Mar 2024 12:41:29 +0100 Subject: [PATCH 006/121] initial version of histo chart --- rowers/templates/filmdeaths.html | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 rowers/templates/filmdeaths.html diff --git a/rowers/templates/filmdeaths.html b/rowers/templates/filmdeaths.html new file mode 100644 index 00000000..a11134d2 --- /dev/null +++ b/rowers/templates/filmdeaths.html @@ -0,0 +1,71 @@ +{% extends "newbase.html" %} +{% load static %} +{% load rowerfilters %} + +{% block title %}Rowsandall {% endblock %} +{% block meta %} + +{% endblock %} + +{% block main %} + + + +

    Film Deaths

    + +
      + + +
    • +
      + + {{ the_div|safe }} +
      +
    • +
    + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} From f935db347fb78d937b69c4d4ce295492bdafacca Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Mar 2024 12:41:43 +0100 Subject: [PATCH 007/121] initial version histo chart --- rowers/interactiveplots.py | 28 ++++++++++++++++--- rowers/templates/histo.html | 31 +++++++++++----------- rowers/templates/user_analysis_select.html | 7 ++--- rowers/urls.py | 1 + rowers/utils.py | 7 +---- rowers/views/analysisviews.py | 1 + rowers/views/otherviews.py | 11 ++++++++ static/css/rowsandall2.css | 3 +++ 8 files changed, 62 insertions(+), 27 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index ad456ad2..5356755c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -88,16 +88,23 @@ from rowers.serializers import * activate(settings.TIME_ZONE) thetimezone = get_current_timezone() -def get_chart(end_point, chart_data): - +def get_chart(end_point, chart_data, debug=False): + if debug: + print(chart_data) url = settings.ROWSANDALL_CHARTS_URL+end_point headers = {'authorization':"Bearer {token}".format(token=settings.ROWSANDALL_CHARTS_TOKEN)} try: response = requests.post(url, json=chart_data, headers=headers) - except: + except ConnectionError as err: + if debug: + print("Chart Server Error") + print(err) script = '' div = 'Chart Server Error' return script, div + + if debug: + print("Status Code",response.status_code) if response.status_code == 200: script = response.json()['script'] @@ -108,6 +115,16 @@ def get_chart(end_point, chart_data): return script, div +# Example for 3D +def filmdeaths(): + data = pd.read_csv("~/Downloads/filmdeathcounts.csv") + + chart_data = data.to_dict("records") + chart_data_dict = {"data": chart_data} + + script, div = get_chart("/filmdeaths", chart_data_dict) + return script, div + # Example for BokehJS def sleep(): data = { @@ -2090,6 +2107,11 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, histopwr = histopwr[histopwr > yaxminima[histoparam]] histopwr = histopwr[histopwr < yaxmaxima[histoparam]] + data_dict = {"data": histopwr.tolist()} + script, div = get_chart("/histogram", data_dict, debug=False) + + return script, div + plot = figure(tools=TOOLS, width=900, toolbar_sticky=False, toolbar_location="above" diff --git a/rowers/templates/histo.html b/rowers/templates/histo.html index a46d9d5e..a3bf496f 100644 --- a/rowers/templates/histo.html +++ b/rowers/templates/histo.html @@ -6,6 +6,7 @@ {% block main %} + diff --git a/rowers/templates/user_analysis_select.html b/rowers/templates/user_analysis_select.html index dfc64a46..9ebbb5b9 100644 --- a/rowers/templates/user_analysis_select.html +++ b/rowers/templates/user_analysis_select.html @@ -14,6 +14,7 @@ } + - - - -{{ the_script |safe }}

    {% if workout|previousworkout:rower.user %} @@ -35,9 +25,7 @@

    • -
      - {{ the_div|safe }} -
      + {{ the_div|safe }}
    + + +{{ the_script |safe }} + {% endblock %} {% endlocaltime %} diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index 6d8ff847..49c9f361 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -101,11 +101,6 @@  Force Curve -
  • - -  Corrected Pace Plot - -
  • {% endif %} @@ -304,6 +299,11 @@  Stream +
  • + +  Corrected Pace Plot + +
  •  OTW Power diff --git a/rowers/templates/user_analysis_select.html b/rowers/templates/user_analysis_select.html index c45daa2f..840f799b 100644 --- a/rowers/templates/user_analysis_select.html +++ b/rowers/templates/user_analysis_select.html @@ -388,24 +388,6 @@ -
    -
    -
    - - - - - -
    - -
    @@ -439,144 +421,144 @@ {{ chartform.as_table }}
  • -
  • -

    {{ searchform }}

    - - {% if workouts %} - - Toggle All
    - - {{ form.as_table }} -
    - {% else %} -

    No workouts found

    - {% endif %} -
  • -
  • - {% csrf_token %} - - +
  • +

    {{ searchform }}

    + + {% if workouts %} + + Toggle All
    + + {{ form.as_table }} +
    + {% else %} +

    No workouts found

    + {% endif %}
  • - {% if worldclass %} - {% if age and sex != 'not specified' %} -
  • -

    Gold Medal Standards

    -

    The dashed lines are based on the - Concept2 - rankings for your age ({{ age }}), gender ({{ sex }}) - and weight category ({{ weightcategory }}). The Gold Medal - Standard power is derived from the - - World Record for your category. - The percentile lines are estimates of where the percentiles - of the Concept2 rankings historically are for those of exactly - your age, gender and weight class. -

    -

    - For rowing on the water, the results are corrected for the expected - difference in power between the Concept2 indoor rower and power - values on the water. -

    -
  • -
  • - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - 100m - -
    - 500m - -
    - 1000m - -
    - 2000m - -
    - 5000m - -
    - 6000m - -
    - 10000m - -
    - Half Marathon - -
    - Full Marathon - -
    - 1 minute - -
    - 4 minutes - -
    - 30 minutes - -
    - 1 hour - -
    -
  • - {% endif %} - {% endif %} +
  • + {% csrf_token %} + + +
  • +{% if worldclass %} +{% if age and sex != 'not specified' %} +
  • +

    Gold Medal Standards

    +

    The dashed lines are based on the + Concept2 + rankings for your age ({{ age }}), gender ({{ sex }}) + and weight category ({{ weightcategory }}). The Gold Medal + Standard power is derived from the + + World Record for your category. + The percentile lines are estimates of where the percentiles + of the Concept2 rankings historically are for those of exactly + your age, gender and weight class. +

    +

    + For rowing on the water, the results are corrected for the expected + difference in power between the Concept2 indoor rower and power + values on the water. +

    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + 100m + +
    + 500m + +
    + 1000m + +
    + 2000m + +
    + 5000m + +
    + 6000m + +
    + 10000m + +
    + Half Marathon + +
    + Full Marathon + +
    + 1 minute + +
    + 4 minutes + +
    + 30 minutes + +
    + 1 hour + +
    +
  • +{% endif %} +{% endif %} diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 852c308c..a09bba7b 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4367,7 +4367,7 @@ def workout_flexchart3_view(request, *args, **kwargs): # create interactive plot ( - script, div, js_resources, css_resources, workstrokesonly + script, div, workstrokesonly ) = interactive_flex_chart2( encoder.decode_hex(id), request.user.rower, xparam=xparam, yparam1=yparam1, @@ -4453,8 +4453,6 @@ def workout_flexchart3_view(request, *args, **kwargs): 'workout': row, 'chartform': flexaxesform, 'optionsform': flexoptionsform, - 'js_res': js_resources, - 'css_res': css_resources, 'teams': get_my_teams(request.user), 'id': id, 'xparam': xparam, From 34ee0d9a2077c3320698fdc3175416047d37eac2 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 19 Mar 2024 18:01:33 +0100 Subject: [PATCH 022/121] working,added save canvas --- rowers/interactiveplots.py | 66 +++-------------------------- rowers/templates/flexchart3otw.html | 1 + 2 files changed, 6 insertions(+), 61 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d3136a4e..f4a8b01d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -18,6 +18,7 @@ import rowers.metrics as metrics import rowers.dataprep as dataprep from rowers.dataprep import rdata import rowers.utils as utils +from jsmin import jsmin from scipy.interpolate import griddata from scipy.signal import savgol_filter @@ -116,6 +117,10 @@ def get_chart(end_point, chart_data, debug=False): except AttributeError: div = 'The chart server errored' + + #if not debug: + # script = jsmin(script) + return script, div # Example for 3D @@ -415,67 +420,6 @@ def interactive_boxchart(datadf, fieldname, extratitle='', script, div = get_chart("/boxplot", boxplot_data) return script, div - tooltips = [ - ('Value', '@'+fieldname), - ] - - hover = HoverTool(tooltips=tooltips) - - TOOLS = [hover] - - hv.extension('bokeh') - - try: - boxwhiskers = hv.BoxWhisker(datadf, 'date', fieldname) - boxwhiskers.opts(tools=TOOLS, outlier_color='white') - except DataError: # pragma: no cover - return "", "Invalid Data" - - plot = hv.render(boxwhiskers) - - yrange1 = Range1d(start=yaxminima[fieldname], end=yaxmaxima[fieldname]) - plot.y_range = yrange1 - #plot.sizing_mode = 'stretch_both' - - if extratitle: - plot.title.text = extratitle - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = axlabels[fieldname] - - plot.xaxis.formatter = DatetimeTickFormatter( - days=["%d %B %Y"], - months=["%d %B %Y"], - years=["%d %B %Y"], - ) - - if fieldname == 'pace': # pragma: no cover - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - plot.xaxis.major_label_orientation = pi/4 - - plot.width = 920 - plot.height = 600 - - slidertext = 'SPM: {:.0f}-{:.0f}, WpS: {:.0f}-{:.0f}'.format( - spmmin, spmmax, workmin, workmax - ) - sliderlabel = Label(x=50, y=20, x_units='screen', y_units='screen', - text=slidertext, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(sliderlabel) - - script, div = components(plot) - - return script, div - def interactive_planchart(data, startdate, enddate): hv.extension('bokeh') diff --git a/rowers/templates/flexchart3otw.html b/rowers/templates/flexchart3otw.html index 8e2c2bc4..01be6c6c 100644 --- a/rowers/templates/flexchart3otw.html +++ b/rowers/templates/flexchart3otw.html @@ -24,6 +24,7 @@

    Flexible Chart

      +
    • {{ the_div|safe }}
    • From eeef57bb66ca923ef0053b68db84b7f86837b67d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 19 Mar 2024 20:08:01 +0100 Subject: [PATCH 023/121] fix --- rowers/interactiveplots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f4a8b01d..2a8ad090 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -120,7 +120,7 @@ def get_chart(end_point, chart_data, debug=False): #if not debug: # script = jsmin(script) - + return script, div # Example for 3D From fdc9c076bd30fc0471828fdba0c5df8236ebcece Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 19 Mar 2024 20:49:42 +0100 Subject: [PATCH 024/121] restoring trend flex --- rowers/forms.py | 2 +- rowers/interactiveplots.py | 2 ++ rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rowers/forms.py b/rowers/forms.py index 984febfc..3376dc36 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1322,7 +1322,7 @@ palettechoices = tuple((p, p) for p in palettes.keys()) analysischoices = ( ('boxplot', 'Box Chart'), -# ('trendflex', 'Trend Flex'), + ('trendflex', 'Trend Flex'), ('histo', 'Histogram'), ('flexall', 'Cumulative Flex Chart'), ('stats', 'Statistics'), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 2a8ad090..4da4f96b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3770,6 +3770,8 @@ def interactive_multiflex(datadf, xparam, yparam, groupby, extratitle='', datadf.index.names = ['index'] + + source = ColumnDataSource( datadf, ) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index c7d9b24aed07da6a0fe5c13239a21aa0573def0f..48ef22c8cf5affe8abbb6bff226cc4031e518250 100644 GIT binary patch delta 37 vcmV+=0NVebAD|xxABzYG2jls%2dNGR?xgGNX>b9PwhrM4a(D{(#DD<+7%&e^ delta 36 scmZ1=KVP0*zMF%C>B6^->?`@$?o~buRu`PSo$n!s9@BfdBMpoU0PqS82LJ#7 From edd5762010139a9b4011e8b6e26bf1f7038a6442 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 20 Mar 2024 21:55:06 +0100 Subject: [PATCH 025/121] trend flex v1 --- rowers/interactiveplots.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4da4f96b..632af695 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3761,6 +3761,24 @@ def interactive_multiflex(datadf, xparam, yparam, groupby, extratitle='', yaxmax = yaxmaxima[yparam] yaxmin = yaxminima[yparam] + data_dict = datadf.to_dict("records") + + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': title, + 'x': xparam, + 'y': yparam, + 'data': data_dict, + 'metrics': metrics_list, + 'errorbars':ploterrorbars, + 'groupname': groupname, + } + + script, div = get_chart("/trendflex", chart_data) + + return script, div + x_axis_type = 'linear' y_axis_type = 'linear' if xparam == 'time': # pragma: no cover From 06cfdbc7a95d168905cceac17c47ba9f445efc43 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 20 Mar 2024 21:55:53 +0100 Subject: [PATCH 026/121] removing obsolete code --- rowers/interactiveplots.py | 152 ------------------------------------- 1 file changed, 152 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 632af695..9566a724 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3779,158 +3779,6 @@ def interactive_multiflex(datadf, xparam, yparam, groupby, extratitle='', return script, div - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': # pragma: no cover - x_axis_type = 'datetime' - if yparam == 'pace': - y_axis_type = 'datetime' - - datadf.index.names = ['index'] - - - - source = ColumnDataSource( - datadf, - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap' - - if groupby != 'date': - hover = HoverTool(tooltips=[ - (groupby, '@groupval{1.1}'), - (xparamname, '@x{1.1}'), - (yparamname, '@y') - ]) - else: # pragma: no cover - hover = HoverTool( - tooltips=[ - (groupby, '@groupval'), - (xparamname, '@x{1.1}'), - (yparamname, '@y'), - ]) - - hover.mode = 'mouse' - TOOLS = [SaveTool(), PanTool(), BoxZoomTool(), WheelZoomTool(), - ResetTool(), TapTool(), hover] - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False, width=920) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.title.text = title - plot.title.text_font_size = "1.0em" - #plot.sizing_mode = 'stretch_both' - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - errorbar(plot, xparam, yparam, source=source, - xerr=ploterrorbars, - yerr=ploterrorbars, - point_kwargs={ - 'line_color': "#969696", - 'size': "groupsize", - 'fill_color': "color", - 'fill_alpha': 1.0, - }, - ) - - for nr, gvalue, color in colorlegend: - box = BoxAnnotation(bottom=75+20*nr, left=50, top=95+20*nr, - right=70, - bottom_units='screen', - top_units='screen', - left_units='screen', - right_units='screen', - fill_color=color, - fill_alpha=1.0, - line_color=color) - legendlabel = Label(x=71, y=78+20*nr, x_units='screen', - y_units='screen', - text="{gvalue:3.0f}".format(gvalue=gvalue), - background_fill_alpha=1.0, - text_color='black', - text_font_size="0.7em") - plot.add_layout(box) - plot.add_layout(legendlabel) - - if colorlegend: - legendlabel = Label(x=322, y=250, x_units='screen', - y_units='screen', - text='group legend', - text_color='black', - text_font_size="0.7em", - angle=90, - angle_units='deg') - - if xparam == 'workoutid': # pragma: no cover - plot.xaxis.axis_label = 'Workout' - else: - plot.xaxis.axis_label = axlabels[xparam] - - if yparam == 'workoutid': # pragma: no cover - plot.xaxis.axis_label = 'Workout' - else: - plot.yaxis.axis_label = axlabels[yparam] - - binlabel = Label(x=50, y=50, x_units='screen', - y_units='screen', - text="Bin size {binsize:3.1f}".format(binsize=binsize), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - slidertext = "SPM: {:.0f}-{:.0f}, WpS: {:.0f}-{:.0f}".format( - spmmin, spmmax, workmin, workmax - ) - sliderlabel = Label(x=50, y=20, x_units='screen', y_units='screen', - text=slidertext, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(binlabel) - plot.add_layout(sliderlabel) - - yrange1 = Range1d(start=yaxmin, end=yaxmax) - plot.y_range = yrange1 - - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - script, div = components(plot) - - return [script, div] def interactive_cum_flex_chart2(theworkouts, promember=0, From 82ec4c9d7c1436bb6133a9331558cd28813c895b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 13:12:54 +0100 Subject: [PATCH 027/121] v1 chart stack --- rowers/interactiveplots.py | 60 ++++++++++++++++++++------ rowers/templates/flexchartstacked.html | 12 ++---- rowers/views/workoutviews.py | 7 +-- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9566a724..02a417d9 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -18,7 +18,8 @@ import rowers.metrics as metrics import rowers.dataprep as dataprep from rowers.dataprep import rdata import rowers.utils as utils -from jsmin import jsmin + +from rowers.rower_rules import ispromember from scipy.interpolate import griddata from scipy.signal import savgol_filter @@ -3907,15 +3908,20 @@ def interactive_flexchart_stacked(id, r, xparam='time', yparam4='spm', mode='erg'): - columns = [xparam, yparam1, yparam2, yparam3, yparam4, - 'ftime', 'distance', 'fpace', - 'power', 'hr', 'spm', 'driveenergy', - 'time', 'pace', 'workoutstate'] + columns = [name for name, d in metrics.rowingmetrics] + columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] + columns = columns + ['spm', 'driveenergy', 'distance'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance'] - comment = None - - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, - workstrokesonly=False) + rowdata = pd.DataFrame() + row = Workout.objects.get(id=id) + + if ispromember(r.user): + rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, + workstrokesonly=False, for_chart=True) + else: + rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True, + workstrokesonly=False, for_chart=True) if r.usersmooth > 1: # pragma: no cover for column in columns: @@ -3929,9 +3935,17 @@ def interactive_flexchart_stacked(id, r, xparam='time', pass if len(rowdata) < 2: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], - doclean=False, - workstrokesonly=False) + if ispromember(r.user): + rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], + doclean=False, + workstrokesonly=False, + for_chart=True) + else: + rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], + doclean=False, + workstrokesonly=False, + for_chart=True) + if rowdata.empty: return "", "No valid data", '', '', comment @@ -3969,6 +3983,28 @@ def interactive_flexchart_stacked(id, r, xparam='time', except KeyError: # pragma: no cover rowdata['y4'] = 0*rowdata.loc[:, 'time'] rowdata[yparam4] = rowdata['y4'] + + # replace nans + rowdata.fillna(value=0, inplace=True) + + data_dict = rowdata.to_dict("records") + + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': row.name, + 'x': xparam, + 'y1': yparam1, + 'y2': yparam2, + 'y3': yparam3, + 'y4': yparam4, + 'data': data_dict, + 'metrics': metrics_list, + } + + script, div = get_chart("/stacked", chart_data) + + return script, div if xparam == 'time': xaxmax = tseconds.max() diff --git a/rowers/templates/flexchartstacked.html b/rowers/templates/flexchartstacked.html index d5788f79..58aa7ffb 100644 --- a/rowers/templates/flexchartstacked.html +++ b/rowers/templates/flexchartstacked.html @@ -8,16 +8,8 @@ {% localtime on %} {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + - - - - -{{ the_script |safe }}

      {% if workout|previousworkout:rower.user %} @@ -34,6 +26,7 @@

      Chart Stack

        +
      • {{ the_div|safe }} @@ -57,6 +50,7 @@
      +{{ the_script |safe }} {% endblock %} {% endlocaltime %} diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index a09bba7b..edf0b572 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4500,7 +4500,7 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): yparam4 = cd['yaxis4'] ( - script, div, js_resources, css_resources, comment + script, div ) = interactive_flexchart_stacked( encoder.decode_hex(id), r, xparam=xparam, yparam1=yparam1, @@ -4510,9 +4510,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): mode=workout.workouttype, ) - if comment is not None: # pragma: no cover - messages.error(request, comment) - initial = { 'xaxis': xparam, 'yaxis1': yparam1, @@ -4549,8 +4546,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): 'active': 'nav-workouts', 'workout': workout, 'chartform': flexaxesform, - 'js_res': js_resources, - 'css_res': css_resources, 'id': id, 'xparam': xparam, 'yparam1': yparam1, From 3cf663850a5c998c5f51729cc1d4e356df444d53 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 13:20:17 +0100 Subject: [PATCH 028/121] removing redundant code --- rowers/interactiveplots.py | 285 ------------------------------------- 1 file changed, 285 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 02a417d9..8551c179 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -4006,291 +4006,6 @@ def interactive_flexchart_stacked(id, r, xparam='time', return script, div - if xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'distance' or xparam == 'cumdist': # pragma: no cover - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - else: # pragma: no cover - try: - xaxmax = get_yaxmaxima(r, xparam, mode) - xaxmin = get_yaxminima(r, xparam, mode) - except KeyError: - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - - x_axis_type = 'linear' - y1_axis_type = 'linear' - y2_axis_type = 'linear' - y3_axis_type = 'linear' - y4_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': # pragma: no cover - y1_axis_type = 'datetime' - - if yparam2 == 'pace': # pragma: no cover - y2_axis_type = 'datetime' - - if yparam3 == 'pace': # pragma: no cover - y3_axis_type = 'datetime' - - if yparam4 == 'pace': # pragma: no cover - y4_axis_type = 'datetime' - - try: - rowdata['xname'] = axlabels[xparam] - except KeyError: # pragma: no cover - rowdata['xname'] = xparam - - try: - rowdata['yname1'] = axlabels[yparam1] - except KeyError: # pragma: no cover - rowdata['yname1'] = yparam1 - - try: - rowdata['yname2'] = axlabels[yparam2] - except KeyError: # pragma: no cover - rowdata['yname2'] = yparam2 - - try: - rowdata['yname3'] = axlabels[yparam3] - except KeyError: # pragma: no cover - rowdata['yname3'] = yparam3 - - try: - rowdata['yname4'] = axlabels[yparam4] - except KeyError: # pragma: no cover - rowdata['yname4'] = yparam4 - - # prepare data - source = ColumnDataSource( - rowdata - ) - - TOOLS = 'box_zoom,wheel_zoom,reset,tap,hover' - TOOLS2 = 'box_zoom,hover' - - plot1 = figure(x_axis_type=x_axis_type, y_axis_type=y1_axis_type, width=920, height=150, - tools=TOOLS, toolbar_location='above') - plot2 = figure(x_axis_type=x_axis_type, y_axis_type=y2_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - plot3 = figure(x_axis_type=x_axis_type, y_axis_type=y3_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - plot4 = figure(x_axis_type=x_axis_type, y_axis_type=y4_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - - plot1.xaxis.visible = False - plot2.xaxis.visible = False - plot3.xaxis.visible = False - - #plot1.sizing_mode = 'stretch_both' - #plot2.sizing_mode = 'stretch_both' - #plot3.sizing_mode = 'stretch_both' - #plot4.sizing_mode = 'stretch_both' - - linked_crosshair = CrosshairTool(dimensions="height") - plot1.add_tools(linked_crosshair) - plot2.add_tools(linked_crosshair) - plot3.add_tools(linked_crosshair) - plot4.add_tools(linked_crosshair) - - xaxlabel = axlabels.get(xparam, xparam) - yax1label = axlabels.get(yparam1, yparam1) - - plot1.yaxis.axis_label = yax1label - - yax2label = axlabels.get(yparam2, yparam2) - - plot2.yaxis.axis_label = yax2label - - yax3label = axlabels.get(yparam3, yparam3) - - plot3.yaxis.axis_label = yax3label - - yax4label = axlabels.get(yparam4, yparam4) - - plot4.yaxis.axis_label = yax4label - - plot4.xaxis.axis_label = xaxlabel - - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot1.x_range = xrange1 - plot2.x_range = xrange1 - plot3.x_range = xrange1 - plot4.x_range = xrange1 - - if xparam == 'time': - plot4.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - hover1 = plot1.select(dict(type=HoverTool)) - hover2 = plot2.select(dict(type=HoverTool)) - hover3 = plot3.select(dict(type=HoverTool)) - hover4 = plot4.select(dict(type=HoverTool)) - - if yparam1 == 'pace': - y1tooltip = '@fpace' - elif yparam1 != 'None': # pragma: no cover - y1tooltip = '@{yparam1}'.format(yparam1=yparam1) - if metricsdicts[yparam1]['numtype'] == 'integer' or yparam1 == 'power': - y1tooltip += '{int}' - else: # pragma: no cover - y1tooltip += '{0.00}' - else: # pragma: no cover - y1tooltip = '' - comment = 'The metric in the first chart is only accessible with a Pro plan or higher' - - if yparam2 == 'pace': # pragma: no cover - y2tooltip = '@fpace' - elif yparam2 != 'None': - y2tooltip = '@{yparam2}'.format(yparam2=yparam2) - if metricsdicts[yparam2]['numtype'] == 'integer' or yparam2 == 'power': - y2tooltip += '{int}' - else: # pragma: no cover - y2tooltip += '{0.00}' - else: # pragma: no cover - y2tooltip = '' - comment = 'The metric in the second chart is only accessible with a Pro plan or higher' - - if yparam3 == 'pace': # pragma: no cover - y3tooltip = '@fpace' - elif yparam3 != 'None': - y3tooltip = '@{yparam3}'.format(yparam3=yparam3) - if metricsdicts[yparam3]['numtype'] == 'integer' or yparam3 == 'power': - y3tooltip += '{int}' - else: # pragma: no cover - y3tooltip += '{0.00}' - else: # pragma: no cover - y3tooltip = '' - comment = 'The metric in the third chart is only accessible with a Pro plan or higher' - - if yparam4 == 'pace': # pragma: no cover - y4tooltip = '@fpace' - elif yparam4 != 'None': - y4tooltip = '@{yparam4}'.format(yparam4=yparam4) - if metricsdicts[yparam4]['numtype'] == 'integer' or yparam4 == 'power': # pragma: no cover - y4tooltip += '{int}' - else: # pragma: no cover - y4tooltip += '{0.00}' - else: # pragma: no cover - y4tooltip = '' - comment = 'The metric in the fourth chart is only accessible with a Pro plan or higher' - - if yparam1 != 'None': - hover1.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - if yparam2 != 'None': - hover2.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - if yparam3 != 'None': - hover3.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - if yparam4 != 'None': - hover4.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - hover1.mode = 'vline' - hover2.mode = 'vline' - hover3.mode = 'vline' - hover4.mode = 'vline' - - y1min = get_yaxminima(r, yparam1, mode) - y2min = get_yaxminima(r, yparam2, mode) - y3min = get_yaxminima(r, yparam3, mode) - y4min = get_yaxminima(r, yparam4, mode) - - y1max = get_yaxmaxima(r, yparam1, mode) - y2max = get_yaxmaxima(r, yparam2, mode) - y3max = get_yaxmaxima(r, yparam3, mode) - y4max = get_yaxmaxima(r, yparam4, mode) - - plot1.y_range = Range1d(start=y1min, end=y1max) - plot2.y_range = Range1d(start=y2min, end=y2max) - plot3.y_range = Range1d(start=y3min, end=y3max) - plot4.y_range = Range1d(start=y4min, end=y4max) - - if yparam1 == 'pace': - plot1.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot1.y_range = Range1d(y1min, y1max) - - if yparam2 == 'pace': # pragma: no cover - plot2.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot2.y_range = Range1d(y2min, y2max) - - if yparam3 == 'pace': # pragma: no cover - plot3.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot3.y_range = Range1d(y3min, y3max) - - if yparam4 == 'pace': # pragma: no cover - plot4.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot4.y_range = Range1d(y4min, y4max) - - plot1.line('x1', 'y1', source=source, color=palette2[1]) - plot2.line('x1', 'y2', source=source, color=palette2[3]) - plot3.line('x1', 'y3', source=source, color=palette2[0]) - plot4.line('x1', 'y4', source=source, color=palette2[2]) - - mylayout = layoutcolumn([ - plot1, - plot2, - plot3, - plot4, - ]) - - #mylayout.sizing_mode = 'stretch_both' - - script, div = components(mylayout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - return script, div, js_resources, css_resources, comment def interactive_flex_chart2(id, r, promember=0, From 9a3a69a4357676c5ff3d47fd3acde0f88c12279b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 14:57:28 +0100 Subject: [PATCH 029/121] itneractive plot v1 --- rowers/interactiveplots.py | 121 +++------------------ rowers/templates/instroke_interactive.html | 11 +- rowers/templates/workout_view.html | 84 +++++++------- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes 4 files changed, 61 insertions(+), 155 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8551c179..6fa0ae28 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2089,10 +2089,6 @@ def course_map(course): return script, div - - - - def leaflet_chart(lat, lon, name="", raceresult=0): try: if lat.empty or lon.empty: # pragma: no cover @@ -3558,110 +3554,23 @@ def interactive_chart(id=0, promember=0, intervaldata={}): except KeyError: # pragma: no cover datadf['pace'] = 0 - source = ColumnDataSource( - datadf - ) + data_dict = datadf.to_dict("records") - plot = figure(x_axis_type="datetime", y_axis_type="datetime", - width=400, - height=400, - toolbar_sticky=False, - tools=TOOLS) + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': row.name, + 'x': "time", + 'y1': "pace", + 'y2': "spm", + 'data': data_dict, + 'metrics': metrics_list, + } - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkw = 184 - watermarkh = 35 - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} + script, div = get_chart("/interactive", chart_data) - plot.image_url([watermarkurl], 0.01, 0.99, - 0.5*watermarkw, 0.5*watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) + return script, div - plot.line('time', 'pace', source=source, legend_label="Pace", name="pace") - - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - #plot.sizing_mode = 'stretch_both' - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - ymax = 90. - ymin = 150. - - if row.workouttype == 'water': - ymax = 90. - ymin = 210. - - plot.y_range = Range1d(1.e3*ymin, 1.e3*ymax) - - plot.extra_y_ranges["spmax"] = Range1d(start=10, end=45) - plot.line('time', 'spm', source=source, color="red", - y_range_name="spmax", legend_label="Stroke Rate", name="spm") - plot.add_layout(LinearAxis(y_range_name="spmax", - axis_label="SPM"), 'right') - - plot.legend.location = "bottom_right" - - # add shaded bar chart areas - if intervaldata != {}: - intervaldf = pd.DataFrame(intervaldata) - intervaldf['itime'] = intervaldf['itime']*1.e3 - intervaldf['time'] = intervaldf['itime'].cumsum() - intervaldf['time'] = intervaldf['time'].shift(1) - intervaldf.loc[0, 'time'] = 0 - intervaldf['time_r'] = intervaldf['time'] + intervaldf['itime'] - intervaldf['value'] = 100 - mask = intervaldf['itype'] == 3 - intervaldf.loc[mask, 'value'] = 0 - intervaldf['bottom'] = 10 - - intervalsource = ColumnDataSource( - intervaldf - ) - - plot.quad(left='time', top='value', bottom='bottom', - right='time_r', source=intervalsource, color='mediumvioletred', - y_range_name='spmax', fill_alpha=0.2, line_alpha=0.2) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Pace', '@fpace'), - ('HR', '@hr{int}'), - ('SPM', '@spm{1.1}'), - ]) - - hover.mode = 'mouse' - # hover.name = ["spm", "pace"] - script, div = components(plot) - - return [script, div] def interactive_chart_video(videodata): @@ -3948,12 +3857,12 @@ def interactive_flexchart_stacked(id, r, xparam='time', if rowdata.empty: - return "", "No valid data", '', '', comment + return "", "No valid data" try: tseconds = rowdata.loc[:, 'time'] except KeyError: # pragma: no cover - return '', 'No time data - cannot make flex plot', '', '', comment + return '', 'No time data - cannot make flex plot' try: rowdata['x1'] = rowdata.loc[:, xparam] diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index d7d89704..6f3be39c 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -122,9 +122,6 @@ $( function() {
      {{ the_script |safe }}
      -
      - {{ ds |safe }} -

      In Stroke Metrics

        @@ -172,10 +169,16 @@ $( function() { {{ the_div|safe }} -
      • +
      • + {{ dd|safe }} +
        +
        + {{ ds |safe }} +
        +
      diff --git a/rowers/templates/workout_view.html b/rowers/templates/workout_view.html index af45ac7e..eaa3184c 100644 --- a/rowers/templates/workout_view.html +++ b/rowers/templates/workout_view.html @@ -104,59 +104,53 @@ {% endif %} - {% for course in courses %} - - - Timed Course: - - - {{ course }} - - - {% endfor %} - - -
    • -

      Workout Summary

      - -

      -

      -        {{ summary }}
      -      
      -

      -
    • - {% if mapdiv %} -
    • -
      - + {% for course in courses %} + + + Timed Course: + + + {{ course }} + + +{% endfor %} + +
    • +
    • +

      Workout Summary

      + +

      +

      +      {{ summary }}
      +    
      +

      +
    • +{% if mapdiv %} +
    • +
      + {{ mapdiv|safe }} - + -
      -
    • - {% endif %} -
    • - - - - {{ interactiveplot |safe }} - - {{ the_div|safe }} - -
    • - {% for graph in graphs %} -
    • - + +
    • +{% endif %} +
    • + + {{ the_div|safe }} + {{ interactiveplot |safe }} +
    • +{% for graph in graphs %} +
    • + {{ graph.filename }} - +
    • - {% endfor %} +{% endfor %}
    {% endblock %} diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 48ef22c8cf5affe8abbb6bff226cc4031e518250..74d4512c54218bb2e5d6196c85237a0e52864745 100644 GIT binary patch literal 3999 zcmV;Q4`A>giwFoSjQwQ-|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!zwlBQl~DC zaYP`iMFKVoD4VxcV&?BpuloLcf3bM` z_N_VYE>2f#b98pPNMCM_UM)|%&EopQ7jNk9?iBmoyKgwl{iA;MX4Sv{fJ=Ir8lK>)b;iX@kz0nQq>5qCt z=H2GyUmy7Qu7A*-oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dJ3qVV@7?`p|7v-0clTxb`q9fn0(3us{6O#=dEeqbITIamguemo0~{>U zP9OJw+@0?|>${8o`Ra6e@9tk6dwHRB=<~C_JIRMVTOOUQUv3VC{lVg^t)C8fxZ1rz zPnPM&Zqj|`d--DHr@cSoJ46fzi_1@+cDSEJtvg=+W0~&$x2|9Qe15upHT(DH>6ct? zezg91+4Y-Wmlsb~>(~AA-~E*zkxuh_ck+69h&Ekh_vfc8EUvl4m9L+4?^dU;PybIh zAnuwQxaJa9zJ9dczN-EE!|hwKOk^*&Tz!x)m+1i?66VLa`}+#Ti`DUQy0vBh>g?s= zlWzUGJ4qBj+j@2H?&!at-TijU`PpjSU)`L)NbkTmyo=l2K0oq{S3-;(eoeLq+>*dm z3go|{up@Cjg3lf>T=RUlJ6w)lo3AeW{MSFec_z4a4FB?s>74)S`aX$GJXq}h`tq8K zbkQ$=-u1VbUxe%MYN<1rB<*4q)R7HN1ABhZ6>%q55%&ZU!370(N_ta4?&0R#sV;f9 zQW1A&h*34lry+t1a*wm&?rcTeW0H3(Uc)_>ly`>NaQ78)x3nGSor1d!h6-}WTX4s! zxCf|+yCm*Os5IKVH*O-_>yr1t6>;|_cyJnh-no)y9MgQb`+B$w7$4AT?9gyotqY+EP1{Kd8&&% zRWrrYJ|t64bfz4ll=R)OosZ13R7BpIn3Q^z%#)ZBb!3eIqDsSyv7|jeC-OE_ zL_X>1OsGn}f><(2XK6;GJyt|MntwZS#CZQCb^}Pp_iNo{wOB)9|J~&kLF)@+y0t0g6U! zW;EK@B=cfTdbU6s@`#hs?OT*~n7lJyFleH*Ojy*OpA-3t?D=e=u~p6U%xL07$YY!5 zuP4tr_m%-kzdzw6!Kz#A*f_tsA$BdkspV=Hz7$pYREfoGJiwzTn~8! zBLRY{Nrva5GVhyiKMHv^Y*AG6eBh!Qo^LWg3VCNRqNrp(+M?duCiCNv-{#(`TAZ3-tx8#b-d}vnPDCE7Nh)(0Zwd{)K z+lMyVk3pVc+Y(Eo+gt03>h__<=SLw=#xX=y6FezPeohsYt|IbctT3zQc~`WSc2?x2 zBJ#l`?y3w-TehN7pvn9gE)pb8_ZHzMdKz z&bhbFBWgx_L70rpH+z1Vd^8jw1{L|(bo=Q?Y=m;R6jhW~7@BTBeVG?)LelM3GH=Uf zUB^~SI|_Mkgmn=ck+S5Q4{rqWHo-RNtt#tUTqA!|^0p%KWP;aKSBb~T#OIsL50e*T z302iq63W)qwH}z`kPn6sRh<(gmaVbzO}8I|Jc5ZxtnuDDz?OSEeVIo#5{NWWT8Gmi z&&`uFNZ#eFYgH#iM_5$1pA-3-M*C<0F=*oRj;UxBwQuqHG05lK+kipS_tse|ntMAZ z@)gGl>3dr^6EJlc{P z-mUWRnua$_o^$t8mG{;sOE9(!%*m4%&^;Z!Fe$azfcPDuo*PQOL7F z()GX$q3Eh*b0S~UJWpbRVANz_hKNNoGiXNhyswCSFp^|mQ)v-_n#@lzJ2t*=xFcS zTGV5Z-{s!w=J^ncT6J?GUy&zAl7+?uD)KQD&B<|Xo*#w0Gri5Z8t}0q^ql5;0@YlP5GI4ZwgJB;sdt6}Nj29a zmMyoL6MC))easqu)Jc8WRSO&XanR>ZsE$>cS(s+5rs2;3z2pI36|zMXDjKbylbTnN zamT@s$Dq>i(KT=0O^v%y5BfBAgUkK#H0XWNKDf8a$b@Fx4WhROfLJH>!A=G}Hsfv( zy-(gS5frb(2s&XnrM|S9RgR>xTEIH8v}h()+6f5ZjiF$ze)Y%(Tj0ji=N81 zN}H3^Lq+I=5g}CrY@}on`i6emE*+mcV_GMoGVUlT2r`lOIoOt^A4G2rdtLjSyfDUO zIwv)|ei*$s6eQ>dd=Etn>#?bMW1z<*z{-LI;Pa}Yfnbh@rtNU`X`rL!9F zsUq~wfF)f-w7Gs1^n@l7fhs|ZX*z3~H6Q*kdTRnJ zt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7Y&#WdYThX5*$80NNxch` z8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_&EpuGnm7Fc z---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO9Q16WL(-wQ zJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbvIKR!SI;w2^PnR1 zcH3G(rRGWDHcE4+oZMUA`GRbw3VAxpj24fviXBlT^-A4Jb+g0L=LFTobo@LSM! z9Q4*WL{xRvkZ7AXZ~Bg0_Qo?=RnXRfn@p-kQ}YJVCp*nL)Me>7bZx+&dTw4q&qgRa zX*B3*IwcsbjnXiBXDsAIOAUGsZNQ(tBiF`+o=8=AKYLZ4T%1`Im%94CXmtzSQko=p(a zMefpzQc}auohTam5zss1*+!L1CpawW(wP-{sR(_{YubWN>OB@6@O>-67z4eKaTbFr zODBY)mA1D+-)ioL(Mztr395|T5Q|pY-fF;ay>y1rhrFh(E^-&4=wfHH((r4^(h=CM zWDV$5j@*bc8J7;UzIlV_w<}re*13yRHfRfNt{(-x&($|d^z0|Ix@DW|2hr!1tQkq= z&5PC-t-hhwH*Xa5yj{dxW!y!HZFS3ud-F1SmkY0y=v^!-^{r9rIOt<8ywchAF+gjc zHgRuWLcd+fS{b>E5nE^M8KAc&&Gf1TPza#)$h|3gKNk8ATR(0%SUm2|FF!mz>zA9u z?@sz3e>qv6F4z6u_uEb1?{)niyz_tBqZbq`A;Wja{f`HW-2r!YXZrXX^XJP?tKIhp z4=xhNvx^V=KJC)mb8+@|)2&~2Cnwvdl|HW4$1l6SOXux&Z>9~XYulrYL7gt`B z`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yp5DiF=#_`_Vat>5Kk1U$S#b69*SoFO zX|tD4`)xN|KH4wNfABor^x<#IlV!JACJy=2hevniz78ID>*Gfc4}W=@*=+yZZT~gB z#ZO+~ERB$UgGhv{j&NS8jm^FKot>Vw39 F0RT%=7v%r| literal 4000 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 GfB^utL_q8S From 0ec34774c1d9e612dff09d40b658a90e3cf96f39 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 15:04:40 +0100 Subject: [PATCH 030/121] f --- rowers/interactiveplots.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 6fa0ae28..fa637c32 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3557,6 +3557,8 @@ def interactive_chart(id=0, promember=0, intervaldata={}): data_dict = datadf.to_dict("records") metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + print(intervaldata) chart_data = { 'title': row.name, @@ -3565,6 +3567,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): 'y2': "spm", 'data': data_dict, 'metrics': metrics_list, + 'intervals': intervaldata, } script, div = get_chart("/interactive", chart_data) From e80fa1e4f91c76dcc1b39aad03f0e83f0960b8aa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 16:27:04 +0100 Subject: [PATCH 031/121] interactive_chart op twee plaatsen --- rowers/interactiveplots.py | 23 ++++++++++++++++++---- rowers/templates/instroke_interactive.html | 23 +++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index fa637c32..cccd3486 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3558,8 +3558,23 @@ def interactive_chart(id=0, promember=0, intervaldata={}): metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] - print(intervaldata) - + + intervals = [] + # add shaded bar chart areas + if intervaldata != {}: + intervaldf = pd.DataFrame(intervaldata) + intervaldf['itime'] = intervaldf['itime']*1.e3 + intervaldf['time'] = intervaldf['itime'].cumsum() + intervaldf['time'] = intervaldf['time'].shift(1) + intervaldf.loc[0, 'time'] = 0 + intervaldf['time_r'] = intervaldf['time'] + intervaldf['itime'] + intervaldf['value'] = 100 + mask = intervaldf['itype'] == 3 + intervaldf.loc[mask, 'value'] = 0 + intervaldf['bottom'] = 0 + + intervals = intervaldf.to_dict("records") + chart_data = { 'title': row.name, 'x': "time", @@ -3567,9 +3582,9 @@ def interactive_chart(id=0, promember=0, intervaldata={}): 'y2': "spm", 'data': data_dict, 'metrics': metrics_list, - 'intervals': intervaldata, + 'intervals': intervals, } - + script, div = get_chart("/interactive", chart_data) return script, div diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index 6f3be39c..9c86fdb9 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -11,7 +11,6 @@ jQuery UI Slider - Range slider - @@ -30,11 +29,14 @@ dataType: 'json', success: function(data) { - console.log(data); - $("#id_script").replaceWith('
    '+data.script+''); - $("#id_chart").replaceWith('
    '+data.div+''); - $("#ds").replaceWith('
    '+data.ds+''); - $("#dd").replaceWith('
    '+data.dd+''); + $("#id_script").replaceWith('
    '+data.script+''); + $("#id_chart").replaceWith('
    '+data.div+''); + + + $("#dd").empty(); + $("#dd").html(data.dd); + $("#ds").empty(); + $('#ds').html(data.ds) } }); } @@ -169,11 +171,10 @@ $( function() { {{ the_div|safe }}
    -
  • -
    - - {{ dd|safe }} - + +
  • +
    + {{ dd|safe }}
    {{ ds |safe }} From 2fee1fcdb6fa3f3bf575a1938361ff5d2ae6411c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 24 Mar 2024 14:30:16 +0100 Subject: [PATCH 032/121] hr pie v1 --- rowers/interactiveplots.py | 28 +- rowers/templates/disqualification_view.html | 9 +- rowers/templates/histo_single.html | 8 +- rowers/templates/history.html | 172 ++++----- rowers/templates/splitworkout.html | 2 +- rowers/templates/summary_edit.html | 245 ++++++------ rowers/templates/withdraw_view.html | 9 +- rowers/urls.py | 5 - rowers/views/analysisviews.py | 392 -------------------- rowers/views/workoutviews.py | 44 --- 10 files changed, 226 insertions(+), 688 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index cccd3486..9946f22c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr datadict = { - '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, + '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, '{ut2}'.format(ut2=hrzones[1]): frac_ut2, '{ut1}'.format(ut1=hrzones[2]): frac_ut1, '{at}'.format(at=hrzones[3]): frac_at, @@ -299,7 +299,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['angle'] = data['value']/data['value'].sum() * 2*pi data['color'] = colors data['zone'] = [ - '<{ut2}'.format(ut2=hrzones[1]), + '<{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]), '{ut1}'.format(ut1=hrzones[2]), '{at}'.format(at=hrzones[3]), @@ -309,24 +309,14 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']]) - TOOLS = 'save,hover' - - z = figure(title="HR "+title, x_range=(-0.5, 1), height=375, - tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime", - ) - - z.wedge(x=0, y=1, radius=0.4, - start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), - line_color='white', fill_color='color', source=data, legend_group='zone') - - z.axis.axis_label = None - z.axis.visible = False - z.grid.grid_line_color = None - z.outline_line_color = None - z.toolbar_location = 'right' - - return components(z) + data_dict = data.to_dict("records") + chart_data = { + 'data': data_dict, + 'title': "HR "+ title + } + script, div = get_chart("/hrpie", chart_data, debug=True) + return script, div def pretty_timedelta(secs): hours, remainder = divmod(secs, 3600) diff --git a/rowers/templates/disqualification_view.html b/rowers/templates/disqualification_view.html index dfacbe67..5a6406cb 100644 --- a/rowers/templates/disqualification_view.html +++ b/rowers/templates/disqualification_view.html @@ -102,14 +102,11 @@
  • {% endif %}
  • - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} +
  • diff --git a/rowers/templates/histo_single.html b/rowers/templates/histo_single.html index 4d6111d3..868f2f34 100644 --- a/rowers/templates/histo_single.html +++ b/rowers/templates/histo_single.html @@ -6,12 +6,9 @@ {% block main %} - - + + -{{ interactiveplot |safe }} @@ -24,6 +21,7 @@
  • +{{ interactiveplot |safe }} {% endif %} {% endblock %} diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 358b91a0..52aaa542 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -12,6 +12,7 @@
    +
    {% endblock %} diff --git a/rowers/templates/splitworkout.html b/rowers/templates/splitworkout.html index bd7c0130..845162b2 100644 --- a/rowers/templates/splitworkout.html +++ b/rowers/templates/splitworkout.html @@ -23,7 +23,7 @@
  • - + diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 4117c89d..886194e6 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -8,26 +8,26 @@ {% block scripts %} {% include "monitorjobs.html" %} - - - + + + {% endblock %} {% block main %} @@ -42,78 +42,75 @@ {% endif %}

    - - - - - - - - - - +
    Name{{ workout.name }}
    Distance:{{ workout.distance }}m
    Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
    Public link to this workout + + + + + + + + + + -
    Name{{ workout.name }}
    Distance:{{ workout.distance }}m
    Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
    Public link to this workout https://rowsandall.com/rowers/workout/{{ workout.id|encode }} - -
    -

    +
    +
    +

    Edit Workout Interval Data

    - Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest - intervals. To save your work, press the Save button above the updated summary. + Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest + intervals. To save your work, press the Save button above the updated summary.

      -
    • -

      Updated Summary

      -
      - {% csrf_token %} - - - {% for key,value in formvalues.items %} - - {% endfor %} -

      - -

      - - Reset to last saved -   - Restore Original data - -
      -

      -

      -					{{ intervalstats }}
      -				
      -

      -
    • -
    • - - - - {{ interactiveplot |safe }} - - {{ the_div |safe }} -
    • -
    • -

      Feeling lucky?

      -

      This new, experimental feature tries to find intervals by looking for patterns in the data. It does not - autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand - section below, where you can edit it. -

      - - - {{ ruptureform.as_table }} -
      - {% csrf_token %} - -
      -

      -
    • -
    • +
    • +

      Updated Summary

      +
      + {% csrf_token %} + + + {% for key,value in formvalues.items %} + + {% endfor %} +

      + +

      + + Reset to last saved +   + Restore Original data + +
      +

      +

      +	{{ intervalstats }}
      +      
      +

      +
    • +
    • + + + {{ the_div |safe }} + {{ interactiveplot |safe }} + +
    • +
    • +

      Feeling lucky?

      +

      This new, experimental feature tries to find intervals by looking for patterns in the data. It does not + autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand + section below, where you can edit it. +

      + + + {{ ruptureform.as_table }} +
      + {% csrf_token %} + +
      +

      +
    • +
    • Interval Shorthand

      See the how-to at the bottom of this page for details on how to use this form. @@ -125,26 +122,26 @@ {% csrf_token %} -

    • -
    • +
    • +
    • Intervals by Power/Pace

      - +

      With this form, you can specify a power or pace level. Everything faster/harder than the specified pace/power will become a work interval. Everything slower will become a rest interval. Use the slider to limit the active range. Everything outside the active range will - become rest (warming up and cooling down). + become rest (warming up and cooling down).

      - +
      -
      -

      - - -

      +
      +

      + + +

      {{ powerupdateform.as_table }}
      - + {% csrf_token %}
      @@ -153,11 +150,11 @@

      Interval Shorthand How-To

      This is a quick way to enter the intervals using a special mini-language.

      You enter something like 8x500m/3min, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.

      - +

      Special characters are x (times), + and / (denotes a rest interval), as well as ( and ). Units are min (minutes), sec (seconds), m (meters) and km (km).

      - +

      A typical interval is described as "10min/5min", with the work part before the "/" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "1km" or "1000m". The basic units can be combined with "+" and "Nx". You can use parentheses as in the example below.

      - +

      Here are a few examples

      @@ -182,32 +179,32 @@
    • - {% if courses %} -
    • -

      Interval by Course

      -

      - This functionality allows you to record a time on a set course that you've rowed during the workout. - The summary will be updated to show time on course, and you can compare this with other - attempts. -

      -

      - {% if rower.share_course_results %} - You are currently sharing your course results with all Rowsandall users. - Click here to hide your course results. - {% else %} - You are currently hiding your course results (except for your participation in online challenges). - Click here to hide your course results. - {% endif %} -

      -
      - - {{ courseselectform.as_table }} -
      - {% csrf_token %} - -
      -
    • - {% endif %} + {% if courses %} +
    • +

      Interval by Course

      +

      + This functionality allows you to record a time on a set course that you've rowed during the workout. + The summary will be updated to show time on course, and you can compare this with other + attempts. +

      +

      + {% if rower.share_course_results %} + You are currently sharing your course results with all Rowsandall users. + Click here to hide your course results. + {% else %} + You are currently hiding your course results (except for your participation in online challenges). + Click here to hide your course results. + {% endif %} +

      +
      + + {{ courseselectform.as_table }} +
      + {% csrf_token %} + +
      +
    • + {% endif %}
    {% endblock %} diff --git a/rowers/templates/withdraw_view.html b/rowers/templates/withdraw_view.html index a5df85ce..2929761c 100644 --- a/rowers/templates/withdraw_view.html +++ b/rowers/templates/withdraw_view.html @@ -98,14 +98,11 @@
  • {% endif %}
  • - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} +
  • diff --git a/rowers/urls.py b/rowers/urls.py index 2ebcf93f..4fdbf66b 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -443,9 +443,6 @@ urlpatterns = [ views.trainingzones_view_data, name="trainingzones_view_data"), re_path(r'^trainingzones/data/$', views.trainingzones_view_data, name="trainingzones_view_data"), - re_path(r'^ote-bests2/user/(?P\d+)/$', - views.rankings_view2, name='rankings_view2'), - re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'), re_path(r'^analysisdata/user/(?P\d+)/$', views.analysis_view_data, name='analysis_view_data'), re_path(r'^analysisdata/$', views.analysis_view_data, @@ -470,8 +467,6 @@ urlpatterns = [ views.workout_upload_view, name='workout_upload_view'), re_path(r'^workout/upload/$', views.workout_upload_view, name='workout_upload_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view, - name='workout_histo_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view, name='workout_forcecurve_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/(?P\d+)/$', views.workout_forcecurve_view, diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 2ebdbb72..03bedb87 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1382,398 +1382,6 @@ def ajax_agegrouprecords(request, ) -# Show ranking distances including predicted paces -@login_required() -@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) -def rankings_view2(request, userid=0, - startdate=timezone.now()-datetime.timedelta(days=365), - enddate=timezone.now(), - deltadays=-1, - startdatestring="", - enddatestring=""): - - if deltadays > 0: # pragma: no cover - startdate = enddate-datetime.timedelta(days=int(deltadays)) - - if startdatestring != "": # pragma: no cover - startdate = iso8601.parse_date(startdatestring) - - if enddatestring != "": # pragma: no cover - enddate = iso8601.parse_date(enddatestring) - - if enddate < startdate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - - if userid == 0: - userid = request.user.id - else: - lastupdated = "1900-01-01" - - promember = 0 - r = getrequestrower(request, userid=userid) - theuser = r.user - - wcdurations = [] - wcpower = [] - - lastupdated = "1900-01-01" - userid = 0 - if 'options' in request.session: - options = request.session['options'] - try: - wcdurations = options['wcdurations'] - wcpower = options['wcpower'] - lastupdated = options['lastupdated'] - except KeyError: # pragma: no cover - pass - try: - userid = options['userid'] - except KeyError: # pragma: no cover - userid = 0 - else: - options = {} - - lastupdatedtime = arrow.get(lastupdated).timestamp() - current_time = arrow.utcnow().timestamp() - - deltatime_seconds = current_time - lastupdatedtime - recalc = False - if str(userid) != str(theuser) or deltatime_seconds > 3600: - recalc = True - options['lastupdated'] = arrow.utcnow().isoformat() - else: # pragma: no cover - recalc = False - - options['userid'] = theuser.id - - if r.birthdate: - age = calculate_age(r.birthdate) - else: - age = 0 - - agerecords = CalcAgePerformance.objects.filter( - age=age, - sex=r.sex, - weightcategory=r.weightcategory) - - if len(agerecords) == 0: - recalc = True - wcpower = [] - wcdurations = [] - else: - wcdurations = [] - wcpower = [] - for record in agerecords: - wcdurations.append(record.duration) - wcpower.append(record.power) - - options['wcpower'] = wcpower - options['wcdurations'] = wcdurations - if theuser: - options['userid'] = theuser.id - - request.session['options'] = options - - result = request.user.is_authenticated and ispromember(request.user) - if result: - promember = 1 - - # get all indoor rows in date range - - # process form - if request.method == 'POST' and "daterange" in request.POST: - dateform = DateRangeForm(request.POST) - deltaform = DeltaDaysForm(request.POST) - if dateform.is_valid(): - startdate = dateform.cleaned_data['startdate'] - enddate = dateform.cleaned_data['enddate'] - if startdate > enddate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover - deltaform = DeltaDaysForm(request.POST) - if deltaform.is_valid(): - deltadays = deltaform.cleaned_data['deltadays'] - if deltadays: - enddate = timezone.now() - startdate = enddate-datetime.timedelta(days=deltadays) - if startdate > enddate: - s = enddate - enddate = startdate - startdate = s - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - else: - dateform = DateRangeForm() - deltaform = DeltaDaysForm() - - else: - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - deltaform = DeltaDaysForm() - - # get all 2k (if any) - this rower, in date range - try: - r = getrower(theuser) - except Rower.DoesNotExist: # pragma: no cover - r = 0 - - uu = theuser - - # test to fix bug - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - startdate = arrow.get(startdate).datetime - enddate = arrow.get(enddate).datetime - - thedistances = [] - theworkouts = [] - thesecs = [] - - rankingdistances.sort() - rankingdurations.sort() - - for rankingdistance in rankingdistances: - - workouts = Workout.objects.filter( - user=r, distance=rankingdistance, - workouttype__in=['rower', 'dynamic', 'slides'], - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('duration') - if workouts: - thedistances.append(rankingdistance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-6*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - for rankingduration in rankingdurations: - - workouts = Workout.objects.filter( - user=r, duration=rankingduration, - workouttype='rower', - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('-distance') - if workouts: - thedistances.append(workouts[0].distance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-5*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - thedistances = np.array(thedistances) - thesecs = np.array(thesecs) - - thevelos = thedistances/thesecs - theavpower = 2.8*(thevelos**3) - - # create interactive plot - if len(thedistances) != 0: - res = interactive_cpchart( - r, thedistances, thesecs, theavpower, - theworkouts, promember=promember, - wcdurations=wcdurations, wcpower=wcpower - ) - script = res[0] - div = res[1] - paulslope = res[2] - paulintercept = res[3] - p1 = res[4] - message = res[5] - else: - script = '' - div = '

    No ranking pieces found.

    ' - paulslope = 1 - paulintercept = 1 - p1 = [1, 1, 1, 1] - message = "" - - if request.method == 'POST' and "piece" in request.POST: # pragma: no cover - form = PredictedPieceForm(request.POST) - if form.is_valid(): - value = form.cleaned_data['value'] - hourvalue, value = divmod(value, 60) - if hourvalue >= 24: - hourvalue = 23 - pieceunit = form.cleaned_data['pieceunit'] - if pieceunit == 'd': - rankingdistances.append(value) - else: - rankingdurations.append(datetime.time( - minute=int(value), hour=int(hourvalue))) - else: - form = PredictedPieceForm() - - rankingdistances.sort() - rankingdurations.sort() - - predictions = [] - cpredictions = [] - - for rankingdistance in rankingdistances: - # Paul's model - p = paulslope*np.log10(rankingdistance)+paulintercept - velo = 500./p - t = rankingdistance/velo - pwr = 2.8*(velo**3) - try: - pwr = int(pwr) - except (ValueError, AttributeError): # pragma: no cover - pwr = 0 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - - # CP model - - pwr2 = p1[0]/(1+t/p1[2]) - pwr2 += p1[1]/(1+t/p1[3]) - - if pwr2 <= 0: # pragma: no cover - pwr2 = 50. - - velo2 = (pwr2/2.8)**(1./3.) - - if np.isnan(velo2) or velo2 <= 0: # pragma: no cover - velo2 = 1.0 - - t2 = rankingdistance/velo2 - - pwr3 = p1[0]/(1+t2/p1[2]) - pwr3 += p1[1]/(1+t2/p1[3]) - - if pwr3 <= 0: # pragma: no cover - pwr3 = 50. - - velo3 = (pwr3/2.8)**(1./3.) - if np.isnan(velo3) or velo3 <= 0: # pragma: no cover - velo3 = 1.0 - - t3 = rankingdistance/velo3 - p3 = 500./velo3 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t3), - 'pace': timedeltaconv(p3), - 'power': int(pwr3)} - cpredictions.append(a) - - for rankingduration in rankingdurations: - t = 3600.*rankingduration.hour - t += 60.*rankingduration.minute - t += rankingduration.second - t += rankingduration.microsecond/1.e6 - - # Paul's model - ratio = paulintercept/paulslope - - u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope - - d = 500*t*np.log(10.) - d = d/(paulslope*lambertw(u)) - d = d.real - - velo = d/t - p = 500./velo - pwr = 2.8*(velo**3) - try: - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - except: # pragma: no cover - pass - - # CP model - pwr = p1[0] / (1 + t / p1[2]) - pwr += p1[1] / (1 + t / p1[3]) - - if pwr <= 0: # pragma: no cover - pwr = 50. - - velo = (pwr / 2.8)**(1. / 3.) - - if np.isnan(velo) or velo <= 0: # pragma: no cover - velo = 1.0 - - d = t * velo - p = 500. / velo - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - cpredictions.append(a) - - if recalc: - wcdurations = [] - wcpower = [] - durations = [1, 4, 30, 60] - distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - sex=r.sex, - weightcategory=r.weightcategory - ).values() - ) - ) - - jsondf = df.to_json() - - job = myqueue(queue, - handle_getagegrouprecords, - jsondf, distances, durations, age, r.sex, r.weightcategory) - try: - request.session['async_tasks'] += [(job.id, 'agegrouprecords')] - except KeyError: - request.session['async_tasks'] = [(job.id, 'agegrouprecords')] - - messages.error(request, message) - return render(request, 'rankings.html', - {'rankingworkouts': theworkouts, - 'interactiveplot': script, - 'the_div': div, - 'predictions': predictions, - 'cpredictions': cpredictions, - 'nrdata': len(thedistances), - 'form': form, - 'dateform': dateform, - 'deltaform': deltaform, - 'id': theuser, - 'theuser': uu, - 'rower': r, - 'active': 'nav-analysis', - 'age': age, - 'sex': r.sex, - 'recalc': recalc, - 'weightcategory': r.weightcategory, - 'startdate': startdate, - 'enddate': enddate, - 'teams': get_my_teams(request.user), - }) - - @login_required() def otecp_toadmin_view(request, theuser=0, startdate=timezone.now() - datetime.timedelta(days=365), diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index edf0b572..7fb0c248 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -630,50 +630,6 @@ def otw_use_gps(request, id=0): return HttpResponseRedirect(url) -# Show Stroke power histogram for a workout -@login_required() -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -def workout_histo_view(request, id=0): - w = get_workoutuser(id, request) - r = getrequestrower(request) - - mayedit = 0 - if w.user == r: - mayedit = 1 - - res = interactive_histoall([w], 'power', False) - script = res[0] - div = res[1] - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_histo_view', kwargs={'id': id}), - 'name': 'Histogram' - } - - ] - - return render(request, - 'histo_single.html', - {'interactiveplot': script, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'workout': w, - 'rower': r, - 'the_div': div, - 'id': id, - 'mayedit': mayedit, - 'teams': get_my_teams(request.user), - }) - # add a workout manually @login_required() From b35d080c48695ce1de143dcc7e1e9e01d9739233 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 25 Mar 2024 08:04:03 +0100 Subject: [PATCH 033/121] marker workouts --- rowers/interactiveplots.py | 29 ++++++++++++++++++++++++--- rowers/templates/goldmedalscores.html | 8 +++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9946f22c..9b436a6d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr datadict = { - '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, + '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, '{ut2}'.format(ut2=hrzones[1]): frac_ut2, '{ut1}'.format(ut1=hrzones[2]): frac_ut1, '{at}'.format(at=hrzones[3]): frac_at, @@ -299,7 +299,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['angle'] = data['value']/data['value'].sum() * 2*pi data['color'] = colors data['zone'] = [ - '<{ut2}'.format(ut2=hrzones[1]), + '<{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]), '{ut1}'.format(ut1=hrzones[2]), '{at}'.format(at=hrzones[3]), @@ -1573,7 +1573,6 @@ def goldmedalscorechart(user, startdate=None, enddate=None): enddate = arrow.get(enddate).datetime.replace( hour=0, minute=0, second=0, microsecond=0) - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' # marker workouts workouts = Workout.objects.filter(user=user.rower, date__gte=startdate, @@ -1664,6 +1663,30 @@ def goldmedalscorechart(user, startdate=None, enddate=None): idx = df.groupby(['date'])['score'].transform(max) == df['score'] df = df[idx] + df.fillna(value=0, inplace=True) + df['dat1'] = df['date'].map(lambda x: x.to_pydatetime(x).strftime("%Y-%m-%d")) + + df2 = pd.DataFrame({ + 'markerscore': df['markerscore'], + 'score': df['score'], + 'markerduration':df['markerduration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + 'duration': df['duration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + 'date': df['dat1'], + 'url':df['url'], + 'workout':df['workout'] + }) + + data_dicts = df2.to_dict("records") + chart_data = { + 'data': data_dicts + } + + script, div = get_chart("/markerworkouts", chart_data) + return script, div, outids + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' source = ColumnDataSource( data=dict( markerscore=df['markerscore'], diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index e56f3624..e623a746 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -65,9 +65,6 @@ Bokeh.set_log_level("info"); -
    - {{ chartscript |safe }} -
    @@ -136,6 +133,11 @@
  • + + +
    + {{ chartscript |safe }} +
    {% endblock %} From fa62212739f74f45c09ec865fa61e02aa00ae435 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 25 Mar 2024 08:09:43 +0100 Subject: [PATCH 034/121] removing redundant code --- rowers/interactiveplots.py | 78 --------------------------- rowers/templates/goldmedalscores.html | 9 +--- 2 files changed, 1 insertion(+), 86 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9b436a6d..8555400e 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1686,84 +1686,6 @@ def goldmedalscorechart(user, startdate=None, enddate=None): script, div = get_chart("/markerworkouts", chart_data) return script, div, outids - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - source = ColumnDataSource( - data=dict( - markerscore=df['markerscore'], - score=df['score'], - markerduration=df['markerduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - duration=df['duration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - url=df['url'], - workout=df['workout'] - ) - ) - - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=600, - toolbar_location='above', - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = 'Gold Medal Score' - - plot.circle('date', 'score', source=source, fill_color='blue', - size=10, - legend_label='Workouts') - - plot.circle('date', 'markerscore', source=source, fill_color='red', - size=10, - legend_label='Marker Workouts') - - plot.legend.location = "bottom_left" - - plot.x_range = Range1d( - startdate, enddate+datetime.timedelta(days=5), - ) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Marker', '@markerscore{int}'), - ('Test', '@markerduration'), - ('Score', '@score{int}'), - ('Duration', '@duration'), - ('Date', '@fdate'), - ('Workout', '@workout') - ]) - - taptool = plot.select(type=TapTool) - taptool.callback = OpenURL(url='@url') - - script, div = components(plot) - - return script, div, outids def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue=7, diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index e623a746..b0cb042c 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -60,14 +60,6 @@ {% block main %} - - - - - - {% if rower.user %}

    Gold Medal Scores for {{ rower.user.first_name }}

    {% else %} @@ -76,6 +68,7 @@
      +
    • Hover over the workout to see details, click on the workout to open in a separate page.

      From 02c998af3e54cb7e4618b62749e4abd91f6cd14b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 25 Mar 2024 19:57:57 +0100 Subject: [PATCH 035/121] removing obsolete code --- rowers/interactiveplots.py | 177 +++-------------------- rowers/templates/performancemanager.html | 34 +++-- 2 files changed, 39 insertions(+), 172 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8555400e..c83f0f88 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1797,174 +1797,37 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= tz=datetime.timezone.utc).replace(tzinfo=None)) df = df.loc[mask] - source = ColumnDataSource( - data=dict( - testpower=df['testpower'], - testduration=df['testduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - fitness=df['fitness'], - fatigue=df['fatigue'], - form=df['form'], - impulse=df['impulse'] - ) - ) + df2 = pd.DataFrame({ + "testpower" :df['testpower'], + "testduration":df['testduration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + "fitness":df['fitness'], + "fatigue":df['fatigue'], + "form":df['form'], + "impulse":df['impulse'], + "date": df['date'].map(lambda x: x.strftime('%Y-%m-%d')), + }) - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=300, - toolbar_location="above", - toolbar_sticky=False) - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} + df2.fillna(value=0, inplace=True) - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) + data_dict = df2.to_dict("records") - fitlabel = 'Fitness' - fatiguelabel = 'Fatigue' - formlabel = 'Freshness' - rightaxlabel = 'Freshness' - if dofatigue: # pragma: no cover - yaxlabel = 'Fitness/Fatigue' - else: # pragma: no cover - yaxlabel = 'Fitness' - - if modelchoice == 'banister': # pragma: no cover - fitlabel = 'PTE (fitness)' - fatiguelabel = 'NTE (fatigue)' - formlabel = 'Performance' - rightaxlabel = 'Performance' - if dofatigue: - yaxlabel = 'PTE/NTE' - else: - yaxlabel = 'PTE' + chart_data = { + 'data': data_dict, + 'title': 'Performance Manager '+user.first_name, + 'plotform' : doform, + 'plotfatigue': dofatigue, + } - plot.xaxis.axis_label = None - plot.yaxis.axis_label = yaxlabel - - y2rangemin = df.loc[:, ['form']].min().min() - y2rangemax = df.loc[:, ['form']].max().max() - - if dofatigue: # pragma: no cover - y1rangemax = df.loc[:, ['fitness', 'fatigue']].max().max()*1.02 - else: # pragma: no cover - y1rangemax = df.loc[:, ['fitness']].max().max()*1.02 - - if doform: # pragma: no cover - plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin, end=y2rangemax) - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=rightaxlabel), "right") - - plot.line('date', 'fitness', source=source, color='blue', - legend_label=fitlabel) - band = Band(base='date', upper='fitness', lower=0, source=source, level='underlay', - fill_alpha=0.2, fill_color='blue') - plot.add_layout(band) - - if dofatigue: # pragma: no cover - plot.line('date', 'fatigue', source=source, color='red', - legend_label=fatiguelabel) - if doform: # pragma: no cover - plot.line('date', 'form', source=source, color='green', - legend_label=formlabel, y_range_name="yax2") - - plot.legend.location = "top_left" - - #plot.sizing_mode = 'scale_both' - - startdate = datetime.datetime.combine( - startdate, datetime.datetime.min.time()) - enddate = datetime.datetime.combine(enddate, datetime.datetime.min.time()) - - xrange = Range1d( - startdate, enddate, - ) - plot.x_range = xrange - plot.y_range = Range1d( - start=0, end=y1rangemax, - ) - plot.title.text = 'Performance Manager '+user.first_name - - hover = plot.select(dict(type=HoverTool)) - - linked_crosshair = CrosshairTool(dimensions='height') - - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}') - ]) - - if showtests: - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}'), - ('Gold Medal Score', '@testpower{int}'), - ('Test', '@testduration'), - ]) - - plot2 = figure(tools=TOOLS2, x_axis_type='datetime', - width=900, height=150, - toolbar_location=None, - toolbar_sticky=False) - - plot2.x_range = xrange - plot2.y_range = Range1d(0, df['impulse'].max()) - - plot2.vbar(x=df['date'], top=df['impulse'], color='gray') - plot2.vbar(x=df['date'], top=0*df['testpower']+df['impulse'], color='red') - - #plot2.sizing_mode = 'scale_both' - plot2.yaxis.axis_label = 'Impulse' - plot2.xaxis.axis_label = 'Date' - - plot.add_tools(linked_crosshair) - plot2.add_tools(linked_crosshair) - - mylayout = layoutcolumn([plot, plot2]) - - try: - script, div = components(mylayout) - except Exception as e: # pragma: no cover - df.dropna(inplace=True, axis=0, how='any') - return ( - '', - 'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format( - nrworkouts=workouts.count(), - nrdata=len(df), - e=e, - ), 0, 0, 0, [] - ) + script, div = get_chart("/performance", chart_data) return [script, div, endfitness, endfatigue, endform, outids] + def interactive_histoall(theworkouts, histoparam, includereststrokes, spmmin=0, spmmax=55, extratitle='', diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html index c0fd3040..11b38315 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -8,6 +8,15 @@ +{% endblock %} + +{% block main %} + + + + + -{% endblock %} - -{% block main %} - - - - - -
      - {{ chartscript |safe }} -
      @@ -83,10 +82,12 @@
        +
      • {{ the_div|safe }} -
        +
    +
  • @@ -188,6 +189,9 @@
  • +
    + {{ chartscript |safe }} +
    {% endblock %} From 892d6e4a836bc3aa21524a7f20ac99a55e612c6e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 25 Mar 2024 21:31:08 +0100 Subject: [PATCH 036/121] removed tests of removed urls --- rowers/interactiveplots.py | 2 +- rowers/tests/test_analysis.py | 30 ------------- rowers/tests/test_cpchart.py | 62 -------------------------- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/test_uploads.py | 4 -- rowers/tests/test_urls.py | 3 -- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/tests/viewnames.csv | 1 - 8 files changed, 2 insertions(+), 102 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c83f0f88..f1eed721 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -315,7 +315,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): 'title': "HR "+ title } - script, div = get_chart("/hrpie", chart_data, debug=True) + script, div = get_chart("/hrpie", chart_data) return script, div def pretty_timedelta(secs): diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index 8c7bc3e0..f7101bb7 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -1356,36 +1356,6 @@ class MarkerPerformanceTest(TestCase): response = self.c.post(url,form_data) self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_ranking_view2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date() - enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date() - - url = reverse('rankings_view2') - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'startdate':startdate.strftime("%Y-%m-%d"), - 'enddate': enddate.strftime("%Y-%m-%d"), - 'doform': True, - 'dofatigue': True, - 'metricchoice':'rscore', - 'modelchoice': 'coggan', - 'daterange': '', - } - form = DateRangeForm(form_data) - if not form.is_valid(): - print(form.errors) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) class AlertTest(TestCase): def setUp(self): diff --git a/rowers/tests/test_cpchart.py b/rowers/tests/test_cpchart.py index 6752450c..6f46c0cf 100644 --- a/rowers/tests/test_cpchart.py +++ b/rowers/tests/test_cpchart.py @@ -166,65 +166,3 @@ class CPChartTest(TestCase): self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.fetchcperg', side_effect = mocked_fetchcperg) - @patch('rowers.dataprep.create_engine') - def test_rankingpieces(self, mocked_fetchcperg, mocked_sqlalchemy): - url = '/rowers/ote-bests2/' - - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - # update_records(url='rowers/tests/c2worldrecords.html',verbose=False) - - form_data = { - 'name': faker.word(), - 'date': nu.date(), - 'timezone': 'UTC', - 'duration': '00:02:00.0', - 'starttime': '10:01:43', - 'distance': 500, - 'workouttype': 'rower', - 'boattype': '1x', - 'weightcategory': 'hwt', - 'adaptiveclass': 'None', - 'notes': faker.text(), - 'rankingpiece': True, - 'duplicate': False, - 'avghr': '160', - 'avgpwr': 0, - 'avgspm': 40, - } - - url2 = '/rowers/workout/addmanual/' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 100 - form_data['duration'] = '00:00:18.5' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 2000 - form_data['duration'] = '00:06:56.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 5000 - form_data['duration'] = '00:20:48.0' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 6000 - form_data['duration'] = '00:22:17.6' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 10000 - form_data['duration'] = '00:38:16.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - response = self.c.get(url) - self.assertEqual(response.status_code, 200) diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index a4984a1a..0492f3e5 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -739,6 +739,6 @@ class InteractivePlotTests(TestCase): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id - script, div, js_res, css_res, comment = interactiveplots.interactive_flexchart_stacked(id,self.r) + script, div = interactiveplots.interactive_flexchart_stacked(id,self.r) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 68a61119..b36335c1 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -74,10 +74,6 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) - response = self.c.get('/rowers/workout/'+encoded1+'/histo/', form_data, follow=True) - self.assertEqual(response.status_code, 200) - - f.close() diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index c798445f..53a2d978 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -122,8 +122,6 @@ class URLTests(TestCase): '/rowers/me/workflowconfig2/', '/rowers/me/workflowconfig2/user/1/', '/rowers/me/workflowdefault/', - '/rowers/ote-bests2/', - '/rowers/ote-bests2/user/1/', '/rowers/partners/', '/rowers/physics/', '/rowers/planrequired/', @@ -155,7 +153,6 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/flexchart/', '/rowers/workout/'+encoded1+'/forcecurve/', '/rowers/workout/'+encoded1+'/get-thumbnails/', - '/rowers/workout/'+encoded1+'/histo/', '/rowers/workout/'+encoded1+'/image/', '/rowers/workout/'+encoded1+'/instroke/', '/rowers/workout/'+encoded1+'/interactiveotwplot/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 74d4512c54218bb2e5d6196c85237a0e52864745..929f46eab568644c34249b30a4049599aa725fc2 100644 GIT binary patch literal 4000 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 GfB^s+jzCxd literal 3999 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 F0RT%=7v%r| diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index 9fa22e20..f0f76651 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -31,7 +31,6 @@ 33,35,kill_async_job,kill job,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 34,36,post_progress,post progress,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 35,37,graphs_view,view charts,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, -38,40,rankings_view2,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, 41,43,cum_flex,flex all chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 42,44,analysis_view_data,redirects to analysis direct,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 43,47,cum_flex_data,flex all chart data (json),TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, From 14b0540042dab49249829499990c6bceae2e5ade Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 26 Mar 2024 08:01:28 +0100 Subject: [PATCH 037/121] testing testing --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes rowers/tests/viewnames.csv | 1 - rowers/views/workoutviews.py | 12 ++---------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 929f46eab568644c34249b30a4049599aa725fc2..7629a55f6cfd11306d8c23fa57a260aa2a4fcfaa 100644 GIT binary patch literal 4001 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 H#DD<+1bRFE literal 4000 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 GfB^s+jzCxd diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index f0f76651..d6d99cfb 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -42,7 +42,6 @@ 52,58,workout_toggle_ranking,toggle ranking,TRUE,302,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE, 53,59,team_workout_upload_view,upload workout for team member,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 54,60,workout_upload_view,upload a workout,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, -55,61,workout_histo_view,histogram ,TRUE,302,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 56,62,workout_forcecurve_view,force curve,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 57,63,workout_unsubscribe_view,unsubscribe from comments,TRUE,302,basic,200,302,basic,200,302,basic,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 58,64,workout_comment_view,comment on workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 7fb0c248..3cca4b75 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1620,15 +1620,10 @@ def course_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { @@ -1802,15 +1797,12 @@ def virtualevent_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) script = res[0] div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { From cccb0be36c0a4081c45bf7a69fd6b7cac5d6feb8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 07:14:34 +0100 Subject: [PATCH 038/121] running tests, fixed issues --- rowers/interactiveplots.py | 10 ++++++++-- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes rowers/views/workoutviews.py | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f1eed721..d8f455c5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -4175,10 +4175,16 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', compute=compute, workstrokesonly=workstrokesonly, for_chart=True) + + # check if dataframe not empty + if datadf.empty: # pragma: no cover + return ['

    No non-zero data in selection

    ', ''] + datadf['workoutid'] = datadf['workoutid'].astype(int) datadf.dropna(axis=1, how='all', inplace=True) datadf.dropna(axis=0, how='all', inplace=True) + nrworkouts = len(ids) try: @@ -4187,11 +4193,11 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', try: tseconds = datadf.loc[:, xparam] except: - return ['', '

    A chart data error occurred

    ', '', 'A chart data error occurred'] + return ['

    A chart data error occurred

    ', ''] # check if dataframe not empty if datadf.empty: # pragma: no cover - return ['', '

    No non-zero data in selection

    ', '', 'No non-zero data in selection'] + return ['

    No non-zero data in selection

    ', ''] if (xparam == 'time'): diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 7629a55f6cfd11306d8c23fa57a260aa2a4fcfaa..9156a4deb611e39e9dc57da148806d85e1ea78c0 100644 GIT binary patch literal 4000 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 literal 4001 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 H#DD<+1bRFE diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 3cca4b75..79a151f9 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1801,8 +1801,6 @@ def virtualevent_compare_view(request, id=0): promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] breadcrumbs = [ { From c4dc809168da0cd7183079d8fc5727a03349cba9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 07:22:49 +0100 Subject: [PATCH 039/121] tests run --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 9156a4deb611e39e9dc57da148806d85e1ea78c0..804b53755ed4b7816219c4f1afae8d7520a63c74 100644 GIT binary patch delta 3912 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 WC_Rlo(xngY{14elt2e|0fB^s^zyyN; delta 3913 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 Date: Wed, 27 Mar 2024 18:43:06 +0100 Subject: [PATCH 040/121] testing --- rowers/interactiveplots.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d8f455c5..318a12b2 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -365,6 +365,17 @@ def interactive_workouttype_piechart(workouts): except KeyError: # pragma: no cover pass + data_dict = data.to_dict("records") + + chart_data = { + "data": data_dict, + "title": "Types" + } + + + div, script = get_chart("/workouttypepie", chart_data, debug=True) + return script, div + p = figure(height=350, title="Types", toolbar_location=None, tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) From 672cfab0c46b68ebc2b14ed9ad7be7b909744a1c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 18:58:40 +0100 Subject: [PATCH 041/121] workouttype pie chart v1 --- rowers/interactiveplots.py | 17 ++--------------- rowers/templates/history.html | 7 ++++--- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 318a12b2..728f8219 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -373,23 +373,10 @@ def interactive_workouttype_piechart(workouts): } - div, script = get_chart("/workouttypepie", chart_data, debug=True) + script, div = get_chart("/workouttypepie", chart_data, debug=True) + return script, div - p = figure(height=350, title="Types", toolbar_location=None, - tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) - - p.wedge(x=0, y=1, radius=0.4, - start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), - line_color="white", fill_color='color', source=data, legend_group='type', ) - - p.axis.axis_label = None - p.axis.visible = False - p.grid.grid_line_color = None - p.outline_line_color = None - p.toolbar_location = 'right' - - return components(p) def interactive_boxchart(datadf, fieldname, extratitle='', diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 52aaa542..55ddbb5b 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -22,9 +22,6 @@
    -
    - {{ tscript|safe}} -
    @@ -91,6 +88,10 @@
  • {{ tdiv|safe }} +
    + {{ tscript|safe}} +
    +
  • From c0042445068d93d8fa3a71b98f748e1e90c19f55 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 28 Mar 2024 18:30:38 +0100 Subject: [PATCH 042/121] v1 activity_chart --- rowers/interactiveplots.py | 50 +++++++++++++++++++++++++++++------ rowers/templates/history.html | 2 ++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 728f8219..e915de3b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -373,7 +373,7 @@ def interactive_workouttype_piechart(workouts): } - script, div = get_chart("/workouttypepie", chart_data, debug=True) + script, div = get_chart("/workouttypepie", chart_data, debug=False) return script, div @@ -567,6 +567,9 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba df.sort_values('date_sorting', inplace=True) + data_dict = df.to_dict("records") + + hv.extension('bokeh') if stack == 'type': @@ -706,12 +709,9 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb while d <= enddate: dd = d.strftime('%d') - if totaldays <= 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) + dates.append(d.strftime('%Y-%m-%d')) + dates_sorting.append(d.strftime('%Y/%m/%d')) + durations.append(0) trimps.append(0) rscores.append(0) @@ -743,6 +743,41 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb df = pd.DataFrame(thedict) + data_dict = df.to_dict("records") + + if totaldays < 30: + datebin = "day" + elif totaldays < 50: + datebin = "week" + else: + datebin = "month" + + stacknames = { + 'TRIMP': 'trimp', + 'distance': 'distance', + 'time': 'duration', + 'rScore': 'rscore', + 'duration': 'duration', + } + + chart_data = { + 'data': data_dict, + 'title': 'Activity {d1} to {d2}'.format( + d1=startdate.strftime("%Y-%m-%d"), + d2=enddate.strftime("%Y-%m-%d"), + ), + 'datebin': datebin, + 'colorby': 'type', + 'stackby': stacknames[yaxis], + 'doreduce': True, + 'dosort': True, + 'colors': mytypes.color_map, + } + + script, div = get_chart("/activity_bar", chart_data, debug=True) + + return script, div + if totaldays > 30 and yaxis == 'duration': # pragma: no cover df['duration'] = df['duration']/60 elif yaxis == 'TRIMP': @@ -4739,7 +4774,6 @@ def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='we 'hours': hours, } - # print(pd.DataFrame(data).head()) return data diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 55ddbb5b..28635bc5 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -166,6 +166,8 @@ $("#total_maxhr").append(totalsdict.hrmax); $("#total_power").append(totalsdict.powermean); $("#total_maxpower").append(totalsdict.powermax); + $("#selectors").hide(); + listofdicts.forEach(function(item){ var id = "#"+item.id+"_hr"; $(id).append(item.hrmean); From 742fca831a88221792684486fc7e9fe4c14f62eb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:07:27 +0100 Subject: [PATCH 043/121] small speed up --- rowers/interactiveplots.py | 200 +++++-------------------------------- 1 file changed, 24 insertions(+), 176 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e915de3b..614a8f7a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -623,47 +623,22 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, yaxis='duration'): - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - distances = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] + print(timezone.now()) startdate = datetime.datetime( year=startdate.year, month=startdate.month, day=startdate.day) enddate = datetime.datetime( year=enddate.year, month=enddate.month, day=enddate.day) - duration = enddate-startdate + totaldays = (enddate-startdate).days - totaldays = duration.total_seconds()/(24*3600) + data_dicts = [] + aantal = 1 for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') + rr = w.user + rowersinitials = rr.user.first_name[0:aantal]+rr.user.last_name[0:aantal] + dd = w.date.strftime('%Y-%m-%d') du = w.duration.hour*60+w.duration.minute trimp = w.trimp @@ -672,78 +647,24 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb if rscore == 0: # pragma: no cover rscore = w.hrtss - if totaldays <= 30: # pragma: no cover - dates.append(dd) - dates_sorting.append(dd2) - else: - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - trimps.append(trimp) - rscores.append(rscore) - distances.append(distance) - links.append( - "{siteurl}/rowers/workout/{code}/".format( + + link = "{siteurl}/rowers/workout/{code}/".format( siteurl=settings.SITE_URL, code=encoder.encode_hex(w.id) ) - ) - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) + data_dicts.append({ + 'date': dd, + 'duration': du, + 'distance': distance, + 'trimp': trimp, + 'rscore': rscore, + 'type': w.workouttype, + 'link': link, + 'rower': rowersinitials + }) - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate - - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass - - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - dates.append(d.strftime('%Y-%m-%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - - durations.append(0) - trimps.append(0) - rscores.append(0) - distances.append(0) - links.append('') - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: # pragma: no cover - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'distance': distances, - 'link': links, - } - - df = pd.DataFrame(thedict) - - data_dict = df.to_dict("records") + if totaldays < 30: datebin = "day" @@ -761,7 +682,7 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb } chart_data = { - 'data': data_dict, + 'data': data_dicts, 'title': 'Activity {d1} to {d2}'.format( d1=startdate.strftime("%Y-%m-%d"), d2=enddate.strftime("%Y-%m-%d"), @@ -774,83 +695,10 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb 'colors': mytypes.color_map, } - script, div = get_chart("/activity_bar", chart_data, debug=True) + print(timezone.now()) + + script, div = get_chart("/activity_bar", chart_data, debug=False) - return script, div - - if totaldays > 30 and yaxis == 'duration': # pragma: no cover - df['duration'] = df['duration']/60 - elif yaxis == 'TRIMP': - df.drop('duration', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'rScore': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'distance': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') - - df['color'] = df['type'].apply(lambda x: mapcolors(x)) - - df.sort_values('date_sorting', inplace=True) - - hv.extension('bokeh') - - # table = hv.Table(df,[('date','Date'),('type','Workout Type')], - # [('duration','Minutes'),('trimp','TRIMP'),('rscore','rScore'),('link','link')]) - - types_order = mytypes.workouttypes_ordered - # bars=table.to.bars(['date',stack],[yaxis]) - bars = hv.Bars(df, kdims=['date', stack]).aggregate( - function=np.sum).redim.values(types=types_order) - - # print(mytypes.color_map) - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.xaxis.axis_label = 'Period' - if yaxis == 'duration': - p.yaxis.axis_label = 'Duration (min)' - if totaldays > 30: # pragma: no cover - p.yaxis.axis_label = 'Duration (h)' - elif yaxis == 'TRIMP': - p.yaxis.axis_label = 'TRIMP' - elif yaxis == 'distance': # pragma: no cover - p.yaxis.axis_label = 'Distance (m)' - else: # pragma: no cover - p.yaxis.axis_label = 'rScore' - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - #p.sizing_mode = 'stretch_both' - p.y_range.start = 0 - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df['link']}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) - return script, div def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', From 26ad051cf54c88bc9740aed1e7fb3b3bb3af515c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:10:02 +0100 Subject: [PATCH 044/121] fix --- rowers/interactiveplots.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 614a8f7a..09626c66 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -623,7 +623,6 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, yaxis='duration'): - print(timezone.now()) startdate = datetime.datetime( year=startdate.year, month=startdate.month, day=startdate.day) @@ -695,11 +694,10 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb 'colors': mytypes.color_map, } - print(timezone.now()) script, div = get_chart("/activity_bar", chart_data, debug=False) - + return script, div def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', spm_min=15, spm_max=45, From 7ab56936dac0e5d57394e8a36f9ca2e1acfa1165 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:57:39 +0100 Subject: [PATCH 045/121] removed activity_chart --- rowers/interactiveplots.py | 182 +--------------------------- rowers/templates/history.html | 9 -- rowers/templates/list_workouts.html | 19 +-- rowers/views/workoutviews.py | 2 +- 4 files changed, 7 insertions(+), 205 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 09626c66..905e0fe3 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -441,186 +441,8 @@ def interactive_planchart(data, startdate, enddate): return script, div -def interactive_activitychart(workouts, startdate, enddate, stack='type', toolbar_location=None, - yaxis='trimp'): - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] - - startdate = datetime.datetime( - year=startdate.year, month=startdate.month, day=startdate.day) - enddate = datetime.datetime( - year=enddate.year, month=enddate.month, day=enddate.day) - - duration = enddate-startdate - - totaldays = duration.total_seconds()/(24*3600) - - for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') - du = w.duration.hour*60+w.duration.minute - rscore = w.rscore - trimp = w.trimp - - if rscore == 0: # pragma: no cover - rscore = w.hrtss - - if totaldays < 30: - dates.append(dd) - dates_sorting.append(dd2) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - rscores.append(rscore) - trimps.append(trimp) - links.append( - "{siteurl}/rowers/workout/{code}/".format( - siteurl=settings.SITE_URL, - code=encoder.encode_hex(w.id) - ) - ) - - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) - - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate - - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass - - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - if totaldays < 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: # pragma: no cover - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) - durations.append(0) - rscores.append(0) - trimps.append(0) - links.append('') - try: - types.append(types[0]) - except IndexError: - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'link': links, - } - - df = pd.DataFrame(thedict) - - df.sort_values('date_sorting', inplace=True) - - data_dict = df.to_dict("records") - - - hv.extension('bokeh') - - if stack == 'type': - table = hv.Table(df, [('date', 'Date'), ('type', 'Workout Type')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - else: - table = hv.Table(df, [('date', 'Date'), ('rower', 'Rower')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - bars = table.to.bars(['date', stack], [yaxis]) - if stack == 'type': - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - else: - bars.opts( - opts.Bars(cmap='Category10', show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - p.y_range.start = 0 - #p.sizing_mode = 'stretch_both' - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df.link}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) - return script, div - - -def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, +def interactive_activitychart2(workouts, startdate, enddate, stack='type', yaxis='duration'): @@ -687,7 +509,7 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb d2=enddate.strftime("%Y-%m-%d"), ), 'datebin': datebin, - 'colorby': 'type', + 'colorby': stack, 'stackby': stacknames[yaxis], 'doreduce': True, 'dosort': True, diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 28635bc5..fc89929e 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -5,19 +5,10 @@ {% block title %}Rowsandall {% endblock %} {% block main %} -
    - - -
    - -
    -
    diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index beeafdad..51e40308 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -10,7 +10,7 @@ {% endblock %} @@ -229,22 +229,11 @@ Total meters: {{ totalmeters }}. Total time {{ totalhours }}:{{ totalminutes }}h. Dig deeper.

    -

    - Activity chart by - TRIMP, - rScore, - Time. -

    - - - - - - {{ interactiveplot |safe }} + {{ the_div |safe }} + {{ interactiveplot |safe }} +
  • {% if announcements %}
  • diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 79a151f9..d0e77543 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2221,7 +2221,7 @@ def workouts_view(request, message='', successmessage='', if yaxis not in ['duration', 'trimp', 'rscore']: # pragma: no cover yaxis = 'duration' - script, div = interactive_activitychart(g_workouts, + script, div = interactive_activitychart2(g_workouts, g_startdate, g_enddate, stack=stack, From 7f3fea5f37838a91d0d3b443897fa55d723d4003 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Apr 2024 12:46:29 +0200 Subject: [PATCH 046/121] v1 instroke_compare --- rowers/interactiveplots.py | 99 +++++++++---------------- rowers/templates/instroke_analysis.html | 10 +-- 2 files changed, 38 insertions(+), 71 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 905e0fe3..ba018f19 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2756,12 +2756,14 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover return (script, div) def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no cover - df_plot = pd.DataFrame() + df2 = [] ids = [analysis.id for analysis in selected] metrics = list(set([analysis.metric for analysis in selected])) maximum_values = {} + workouts = [] for metric in metrics: maximum_values[metric] = 0 + cntr = 1 for analysis in selected: #start_second, end_second, spm_min, spm_max, name activeminutesmin = int(analysis.start_second/60.) @@ -2781,79 +2783,48 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co if mean_vals.max() > maximum_values[analysis.metric]: maximum_values[analysis.metric] = mean_vals.max() xvals = np.arange(len(mean_vals)) - xname = 'x_'+str(analysis.id) - yname = 'y_'+str(analysis.id) - df_plot[xname] = pd.Series(xvals) - df_plot[yname] = pd.Series(mean_vals) - if len(metrics) > 1: - for analysis in selected: - yname = 'y_'+str(analysis.id) - df_plot[yname] = df_plot[yname] / maximum_values[analysis.metric] + data2 = pd.DataFrame({ + 'x': pd.Series(xvals), + 'y': pd.Series(mean_vals), + + }) + data2['id'] = cntr + df2.append(data2) - source = ColumnDataSource( - df_plot - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - if len(metrics)>1: - plot.yaxis.axis_label = 'Scaled' - else: - plot.yaxis.axis_label = metrics[0] - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - xname = 'x_'+str(id) - yname = 'y_'+str(id) - analysis = InStrokeAnalysis.objects.get(id=id) legendlabel = '{name} - {metric} - {workout}'.format( name = analysis.name, metric = analysis.metric, date = analysis.date, workout = str(analysis.workout) ) - plot.line(xname,yname,source=source,legend_label=legendlabel, - line_width=2, color=color) + workouts.append({'id': cntr, 'label': legendlabel}) + cntr = cntr + 1 - script, div = components(plot) + + ytitle = metrics[0] + if len(metrics) > 1: + cntr = 1 + for analysis in selected: + df2[cntr-1]['y'] = df2[cntr-1]['y'] / maximum_values[analysis.metric] + ytitle = 'Scaled' + cntr = cntr+1 - return (script, div) + df2 = pd.concat(df2, axis=0) + + data_dict = df2.to_dict("records") + + chart_data = { + 'title': '', + 'data': data_dict, + 'ytitle': ytitle, + 'workouts': workouts, + } + + script, div = get_chart("/instroke_compare", chart_data) + + return script, div + def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, activeminutesmin, activeminutesmax, diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html index 0ced8ffb..301e3ddb 100644 --- a/rowers/templates/instroke_analysis.html +++ b/rowers/templates/instroke_analysis.html @@ -6,24 +6,20 @@ {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} - - - + -{{ the_script |safe }} -

    In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

      + {% if the_div %}
    • {{ the_div|safe }} + {{ the_script |safe }}
    • {% endif %} From 3792a6f7b67d70b131277c4d631c966fa620215d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Apr 2024 16:35:20 +0200 Subject: [PATCH 047/121] v1 instroke interactive chart --- rowers/interactiveplots.py | 148 ++++----------------- rowers/templates/instroke_interactive.html | 11 +- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4001 bytes 4 files changed, 34 insertions(+), 127 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index ba018f19..3b74b7b8 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2864,136 +2864,42 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, except TypeError: pass - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - plot.title.text = str(workout) + ' - ' + metric - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - - source = ColumnDataSource( - df_plot - ) - - - TIPS = OrderedDict([ - ('x','@x'), - ('median','@median'), - ('high','@high'), - ('low','@low') - ]) - - hover = plot.select(type=HoverTool) - hover.tooltips = TIPS - - s = 'SPM: {spm_min} - {spm_max}'.format( - spm_min = spm_min, - spm_max = spm_max, - ) - - label = Label(x=50, y=450, x_units='screen',y_units='screen', - text=s, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - s2 = 'Time: {activeminutesmin} - {activeminutesmax}'.format( - activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), - activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) - ) - - label2 = Label(x=50,y=400, x_units='screen', y_units='screen', - text=s2, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - plot.add_layout(label) - plot.add_layout(label2) - - if name: - namelabel = Label(x=50, y=480, x_units='screen', y_units='screen', - text=name, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(namelabel) - - if notes: - noteslabel = Label(x=50, y=50, x_units='screen', y_units='screen', - text=notes, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(noteslabel) - - if individual_curves: - for index,row in df.iterrows(): - plot.line(xvals,row,color='lightgray',line_width=1) - else: - plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5) - - plot.line('x','median',source=source,legend_label='median',color="black", - line_width=3) - - medrange = mean_vals.max()-mean_vals.min() - yrange = Range1d(start=mean_vals.min()-0.2*medrange, - end=mean_vals.max()+0.2*medrange,) - plot.y_range = yrange - - plot.add_tools(HoverTool(tooltips=TIPS)) - + if metric == 'boat accelerator curve': - plot.yaxis.axis_label = "Boat acceleration (m/s^2)" + ytitle = "Boat acceleration (m/s^2)" elif metric == 'instroke boat speed': - plot.yaxis.axis_label = "Boat Speed (m/s)" + ytitle = "Boat Speed (m/s)" vavg = mean_vals.median() elif metric == 'oar angle velocity curve': - plot.yaxis.axis_label = "Oar Angular Velocity (degree/s)" + ytitle = "Oar Angular Velocity (degree/s)" elif metric == 'seat curve': - plot.yaxis.axis_label = "Seat Speed (m/s)" + ytitle = "Seat Speed (m/s)" + - plot.xaxis.axis_label = 'Time (%)' + lines_dict = df.to_dict("records") + data_dict = df_plot.to_dict("records") - try: - script, div = components(plot) - except ValueError: - script = "" - div = "Something went wrong with the chart" + chart_data = { + 'lines': lines_dict, + 'data': data_dict, + 'ytitle': ytitle, + 'title': str(workout) + ' - ' + metric, + 'individual_curves': individual_curves, + 'spmmin': spm_min, + 'spmmax': spm_max, + 'timemin' :'{activeminutesmin}'.format( + activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), + ), + 'timemax': '{activeminutesmax}'.format( + activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) + ), + 'analysis_name': name, + } - return (script, div) + + script, div = get_chart("/instroke", chart_data, debug=False) + return script, div def interactive_chart(id=0, promember=0, intervaldata={}): diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index 9c86fdb9..acc2a8d1 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -13,7 +13,6 @@ - -
      - {{ the_script |safe }} -

      In Stroke Metrics

        +
      • {% if user.rower|is_basic %} @@ -167,9 +164,13 @@ $( function() {

      • +
        {{ the_div|safe }}
        +
        + {{ the_script |safe }} +
      • diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 0492f3e5..195b9ec1 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -685,7 +685,7 @@ class InteractivePlotTests(TestCase): startdate = workouts[0].date-datetime.timedelta(days=5) enddate = workouts2[0].date+datetime.timedelta(days=5) - script, div = interactiveplots.interactive_activitychart(workouts,startdate,enddate) + script, div = interactiveplots.interactive_activitychart2(workouts,startdate,enddate) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 804b53755ed4b7816219c4f1afae8d7520a63c74..3c5912231fea528068d9b2afd89b73de6d3b218f 100644 GIT binary patch literal 4001 zcmV;S4_@#eiwFo2kqTx4|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVG#CP__!#t z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7WCxQ_<*_r5!NaQO17Tdh~?)5ptozr1?V_1$mXX1SMkUT+@mzv}zTgT>n~ST{1p(mV*XPfE-k#!{)q2x+>*HlQYO^}no%h+rao2Bu?VWC5Pk+=K zGVeAge}3TKyZ&)^c5$^@ZWeFw&-eem>yLN)FRw3!f9+C(fbM$e?l_ww1sk9+@t?+7s*F0Ma)+~H9YweEEJk7c_1pSphe{pI=g)$HG2reAWs z`RV%mW!GOWukG5~cGLgOBa`Qo+EYkx%BFv9*_xBBoSF6+0bZg80)y2ut z^KSjRJ4+Nl*?M#D?)bl--2HaT<;7~<-`t$PNbkU3co(<3eR||)uY?#o{F-bJxF>;| z6v%%;VMpS21fM)$xaIk7ceozCHeX!!<&VGq<(c5tG5qs0rgQ$Q>-!`&@o=&G>+@?a z(nUZ2dAHwUei3fNtEJ9hlC+CeP)9a64ea?rSHzuMMcfla1Q!(CDd|lGxre)Rr@G|b zN=4kAAx70GpN0r7$UV-6yR#K>k4fIGcn$YhQr;P6!`)ZJ-O_fPcM9${7%Ipe@4+3b z;vS$P?vl7Cq0(sc-nfZyuS?zoSH#_$;K6D1dFM)+aZK~!?(5+$V0=KUk#{dfCc?e4 zalRruPS`}SQNuk*N&9_H@*XPU?u-Khjb0a}s6Ia*c}J*7v6E<+b#7XOvgG+53jEfjmZ zHS;{#vgGZI$X67x;a~v3i-x>F$>_YjkKdk~RX0RF$**;&O2ZDdBl$Rkchw{KC}Ve-y+!JvuKGGS4Beoo{ovgfmf##S}YGoy(UA&+gI zznwhi+*<}DjVC9)C?&J5t!sY!QOJt{hM zIOpCvkEj{#1z|EW-|YEe^3hO$7*ynA)9t4pu@TDMQdCh|VQ9Mj^krVG2}!qC$-FI_ zbsbwR?I`5E5!OX)M9PwHKD-gg+XUO7x2mjbagF?4$=iy^lL=l|T_qkT6Q6G~KTKYX zB~(>cNhn)a*Lq-%Lp~TnRCP{}ShmK-H{E^=@(3m(vBrDr09)?u^kp8|NFdTgX&p|7 zJU36yAbFRwu2r279br-3eoo|T8ttP2#Gr}KJEo#l)V{^%#~`0`ZvzHR-&<#?XzuNt z$X8^~lQra!Rr9>iWO8r0)z1%-_XaJJYP5G$)SjOac}EqI&z%#9HT~_K=c4ocoXA(C z+b3;}BdS&{bAelwb_V3d0Enu~xr^MQw9_B$*&7jECqxg8{9Vacq}y9C>_z3t@n}nG zc=yV~YZ~4#dCuKWRo+{lEWy|^Fegu5jJFOou{xi0gMzU-!5P`}6~*UmFcuj#IXOPP zEp{U0YxCiSddLgrT3Xeb7G8?h&(BHbYtrz9jTb_V56?SS(r-VjdA=fhKFKdfQq|aa z?^;FW9m!V|rIl!a2~?wfaFfyPoA-8ryc_@;uk_*hSacQgS;@RqMBW+;qLTRlMc1O9 zmCU<}$a{l8pt0%#hRMi$BR@<&8kYup&1fGe6t(B)G}?!H$RinRF>11|1IH%wcO)Mx zA}D!!86pn>)pWB%dvJSnoMQuB2bNwjjt+9eCA1;EICiQnjPZgo}xkZ*o z75d;NBlVS`XIBsUWT)9cU{&a&FRJFvX~4&d&~uvW2~=}ELYNHt+6MfZq}~|@B-LDx zShn0|PUyKJ^f7DrQ783fS1oMl$3dSvp*mJ&W?`DKnub3E^pXdBRmc`msA#l)PHJ97 z#vKPk9)n85N7uZ0cQx)pJ?PWe4KDY`)1dc7`{3RyBNLi&H;CRE0Aiig2Rj+`*o?bD z^gf~2HPOh~Tt(4yLq7`oXrfiRbO>BgCEqW^3 zDs4_u4;7&gMub!iu#u8Q=o|WJyL5c+jA@;S%DAJXAjm}8=U`iweh|Gi>~-yP^1>LC z>73N;`eF3mP>`S-@I4eQtjDJ2je#DM04odbkOEGoBQCbI%t7>Q(CNBnA;qE#m(FUy zr;5-!1D13dxePp+HE!BmKa4(@D9Y*^MqAXS)8_h7&=Z8L43T{-+Bl7xT|Wx?Xegr9Y51XR_=cM|Zw&O7a{nxyH;-d%YTon* zd@BY-S$Jg;CKIo(Eg8F_psh_B8&Zra^h9m0pFVm46O{`Wqh%@@xobZBanQ4g4oQdJ z@?@4bYmVGu^xKw+UZv()vCXA3eMj!LWg>drT<>Hu30f^Ja{ztVwoC-wk~e6*G~;gS zc74b#6RilU9Jw~M>YKZw4;7&gxr3IjtA>qb!#CUp{4vlY8fI}SsTU}_Ts_Z7&4Y^2 z+ihzFm6|7o+bGSczTQ@Z-W#&Ypsg?z_2$_&;E#cxz#uWQ3cZENtZmr5dBf z*C&gh6Rs-t5q0Pt6*cZ!SB-Jdhb;A8H{g4ojMTRQe-J&R3BtN~y#!lS!*4;` zanM`i5K+}tL!xcoyy-h~*&EMfRY6+^ZZfGFP0brbpX@a2P?x3S(6s@7>bZFdJsY9y zq|u6Bo!HcG?jow1M;Ej8#lv;lwmj$9iPdLmWfm9ud&m9{NQXAr%RVXY6}IcRhJ z4A6@q(yDnLT1M{F3$H?MnTTnd4t>DMG*oG7-T?ZTTP6Zx)YIn*BrT1cj?63G7+?E z+IsINGuKlK+76=6Mb-?e@Qn|?+4VC(AB`mIPAB!zPsXmNrsfTyXEI`~E_@RpG`oHV z=p`3kfet+uMW2@nY6JcV=z{@9T|fMgllLcro|-pr2z_438ZhY4bDRwNwtoFEdNx5w z7r9F>N=XerccN(MM?mk4XB$;6o#3#fOJ`Q-r6Tk(uW1W9srOiP!1t{LV+`~{##s!i zES(UFR@&YReXF?}MlZSgCa5xULo8Zpd#?e%_0kzeAM%>Ey2xFGqKlo)O2e-uOGjY4 zk~N@LIdUV)WL!GX`sNLy->zh>TjwrP*`O`7xqcM%K3Cr;(X*e->XvP;A4H#5vSuWe zH!oUWwEBix-@H-K^L7z)m2np(w$&{s?#;{ST`s&*qIa>V)VD^da_p%aIrh!!R}0d{KowG`qOIn z{o&)Q#PQ)%x_L>$`N`ZufR2Dj$zKeDu11b#Zm$ zHFe{r((-S@9oXD8P`_m}iOrbBN$oDW-`cmGM3%+7+FpTFE~ zwN9J8eB5ul;rh{jcK*Yc>86igiwFpyxdUbb|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!-|@7sZ$rn zI3keMA^{r(l+D|!ViX@kz0nQq>5qCt z=H2GyUmy7Qu7B8_oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dKR>(Z@8A7r|7v-0clTxb`ti#{0(AF4?h(8|-nY0<&O}EX;co!@00)b- z)5ravbmx1|`|hHDu{vGezx!9mUS232`r@qbPV!;Tmq%yomzzUjf3Wy!>!$-At#)tF z(`EXxn{=P~UcT7)Y44Bt4iUq_;_}m{9Udf6>yDTISf;!Gwdds{rTw%i)$`%A zh`Z(nuDQgOuOF|suWJAPaQjv)6WPlxS0CidWqQDeg!wV<{=P!-Vs(6+Zf)7WI(vEe zv|GRKP7=k>wqD)4JNoZucfZ|oezscoS2yP`(mU`C@8Wj1&yW1#l@McxUz6_}XX;Ijt|*F4|t4ws|X=BvxT_~rL+o(ZlU!@oRZI_JN-zE5Hk4;H(>zP#ol zUG&SJcl|Bq7vVa*TIviYNxN7Db!3Cnz@8s;Mcm0%#63Yoa6!SHlHOF1d$>7ws!QIj zRK(pGVpNUtX^7y0+~aJxJ6jR=nB?7x*Km&|<(*+R+cdn!v$21@Az8>xZ#s{<-dG}&uBHSw* z=PR<~giQn+HQa-gwBP3>@1Y{@&Nv{@=yg$w>htrFcZ7-*JBfx_=cYv{OP+5*p6Vh` z)lBiU56P4hohgSXC4Dz+=Ognh6_K|lCZ%2_^CTt|Brh`}A1flC%sD|0s_={}YR9|z zkhiWL@(9LRB$do3c7iUDCDkAR;I-L^-oi91hTWF*G zDCC2QBvuoiLmx`|?`}oDjrN1&35_H0s?k1_B|o1C9ZE%39a$rQsM7FaENRcriM$OJ zkxzO$6RMK0AeM~MS(?#kj}?)RCLnmLO1o?1XF#6GxQJ0jJ}2iEBtIj+eNCf1i(v;| zGtZMPOWw|id_@r(4h8_cXvhncjLzHJ`0cq_bwlKn{91>qG%S)>(uX%I@)h~;EE`6& znkX&#q8av${7B@T0jne*U@|ga+dN-Ul-360)2pbm=OY;3G`y+L^MWRcyvm+ufT9tb z8IATe$-G#To-L4uJmO?@`xd1gChv?F44Nn{6Bf1S=S03Ddp=ueY*q6-GnzON^4RA2 z>&bJ@y=6escyiKy5_eZg}fMG2r8KuDjKnAk3yaeTNKqiAGoN7=bOxrLf#pSC@Ptcwy5{E$^1Cvx4E~f7HGC? zMdhu?WAot+k-(}8}D>cS{q}@oSZq4ucyX_ zbMCG4h?>z}5GEt@&7L179}NYFK}9|`-G2HJ8=>4SMHQtLhNjz3U*^S{kaT;M%-gbA z*Rj>ojzZoWVO_*Vq%8U7!yAFTO|T7mtIE0-*T~ zbMxd3l6N`lTGa{B5f;_$=S04y(LNeL44U}7V=7uj?OS|)4DvbmHek^7y>*s~=HAYU zd`0#=Swjw4HO~u8Cij+G{rn(#Z_pyCMtes^?fDszcT^Gi+&O_*)8F2CE;`T8iF`%6 zebUxAqH5JL7q~@fXFy&IfT+5hyT~m{JN?m~y%EuMLiEtc-;{hsy1fO%UR0hOkG7W~~oRK|WQGDJ8W06slljGCd zVkbhrHXmN7hrD2}rB$tI;iYK({G4RICJj&6cp=pI@Vs*+{r0n(=PR=3ll*cdRgI1J zu2odtkbFf^T8RdjKsDM2HyPc&d2a{E%RZp-N*|t&MOPu8mCQ>;&x z$-JwGyf+908mlf~n2gLf^26k#acQvEjP`*-QG0$)qkX7{Jd&{%qbBP*aBMPvL-MgA z@?sz;M|)dzrNUWxaw;0_(Hk$K>Xs5*(bY-k(Dz})V7m0*N=kU8Y`&s;UaixQh!7AR1tcgTV#1u zp$~2{QePQ*cJ-i7cA5YZUgQqA>< zWy@{mgq|xxAG3xZby8n;)xw5;9Q3&ps$*4V7N!}iY4|fhFL}UMg=`Upibm__q~=v* z+;K4EF{m_rbj_Q0Q{yhwgFcPj;BtRF4SHX+5ALlpGNBoFgXpaRAl6BJu#-WL&A1yx z?-P1m6OEkBRTMop^rN7UCR(LShrksz?x=b5#z3Ex^@zH%8>H;`Z&E*b^kSUXqNlR0 z(&i-fP!alIL`c;D8!1_YzM-GCOULKVnAVA?j5|sSf=r}+4z^|K2hm%@Ue`V+FN`sn z&PmO#A4cyD1qr$V-$T*DdTeUm80awxu(IF|Dd1!};$ln797N9sovv#ZQY^Y~>8u8P zstCO^U`dye%fORaKkS^nWkwiOJ^9pH{J@XG<>#Y*SVoqb2kS1oM9=btFLD%+fId=nl}o1HUb!RQt!fK z27GJ@T7&4Fv8Y?i5ZT9~jnk;v^`oGVh9X*>h9Am?Z@78$#z1c=_s`OK^Ek$)=1qUV zw_-q)g;y3~GV%J_lCdia+S;VCA;qXdPt@l6>7y4gQMqt2TBf3ryXM0m2R)nUkaXxR zPiA?u=ExmJzipZ5Rcf9U+gv)+cjRtcCZgBP^-d;}pw-ec2hfLY%S6yEd4twVGw!Bt z*N5CP(TbqTk!wS%zPTy-P!al&J80>;YS>sde8X+P9|Jw2VHT&7dV#Xb)$@$hJg5l0 z-L_Uxsd-YkjnbUz>up8oy&YW1vUM4ONs<&t*$6papG5 zK`*(X3hAWYO3}z2G&OG&^ue%T&`G_xqNy59%^L+hCF72itrfg;&5=8Gmrl$r6G6MC zt@nO1b3L`7?I8MGWX+%o-}vC0T|Wc#(MYoHbW$JvWbArsYTgifCL`AB!Z!gzv+HMo zUUK0T=+I+P^m(bEHsFtdJ{Vxs^}`Q2d4D44sd@8;(C3w`0fP=b$H|~?>(>vXXA^{U zk-PMwl+^HZCyIuC1oX~$wo&EM2@XrTbY_KKDncLgnzo>mdXGg1eBVki#y~G*oW-EZ z(g~qxrR}ZIx0<_Q^pdM@f+{07#G;k9w;J$UFP&lZA+Kqxi`+#hy4cyQH2hk!bOg35 zSp#~NBR8T<#-#(TZ{8sK?Ml|Vb?zdS4cbDR>qkNFbM=i9J^RV5ZrSGgLG*bgYerIe z^P=@dt8b|F%^L+hZx=CF8Fx`)TitTv-n@+7<-#i^dKZgIeQT6D4*Hl2uXJ{O4A7dV zP28K8&~I0=Rz~h(#MT*m2I#FxGrej76ar{Ha&L;>kA?oj){h$w7EikK%MZ`a`sL>E zyOaKhpHG&j%XPo^{dUv$dtJW=@BE)mIwv;zBlzyP|KVVAcv6C z;Qah#b=39gr(eDpm*@WKWa+z~UawAGUjE$Q()*YWz4CBAY63KKylgvg|g?#36tB@aV4G*TIu+ef;>*;m^-9o9&;w?Z2eA z_}S~zKbGmv0PX?o{y$h;`C|9vw~y)Y&KC7*ou0$PgmQWDC_Rlo(xngY{14elt2e}e F0RY^kJzD?( From 7937310822aaf5ee1d6732f54b97d33b67ac76fa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Apr 2024 17:38:54 +0200 Subject: [PATCH 048/121] incomplete force curve --- rowers/interactiveplots.py | 13 ++++++++++ rowers/templates/forcecurve_single.html | 34 +++---------------------- rowers/views/workoutviews.py | 4 +-- 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 3b74b7b8..fe4dd91a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -553,6 +553,19 @@ def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter' if rowdata.empty: return "", "No Valid Data Available", "", "" + data_dict = rowdata.to_dict("records") + + thresholdforce = 100. if 'x' in boattype else 200. + + chart_data = { + 'title': theworkouts[0].name, + 'data': data_dict, + 'thresholdforce': thresholdforce, + } + + script, div = get_chart("/forcecurve", chart_data) + return script, div + try: covariancematrix = np.cov( rowdata['peakforceangle'], y=rowdata['peakforce']) diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html index d9ea9729..f5da7253 100644 --- a/rowers/templates/forcecurve_single.html +++ b/rowers/templates/forcecurve_single.html @@ -8,16 +8,8 @@ {% localtime on %} {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + - - - - -{{ the_script |safe }}

        Empower Force Curve

        @@ -25,7 +17,8 @@
      • {{ the_div|safe }} -
        + {{ the_script |safe }} +
  • @@ -52,27 +45,6 @@ diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index d0e77543..909a6750 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -526,7 +526,7 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly plottype = 'line' - script, div, js_resources, css_resources = interactive_forcecurve( + script, div = interactive_forcecurve( [row], workstrokesonly=workstrokesonly, plottype=plottype, @@ -577,8 +577,6 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly 'work_max': work_max, 'annotation': notes, 'the_div': div, - 'js_res': js_resources, - 'css_res': css_resources, 'id': id, 'mayedit': mayedit, 'teams': get_my_teams(request.user), From 739203eeb54805648048bf3588e07109667d923c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Apr 2024 07:46:54 +0200 Subject: [PATCH 049/121] fix --- rowers/interactiveplots.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index fe4dd91a..1e14f767 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3193,9 +3193,12 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, x1 = datadf['x1'] y1 = datadf['y1'] - popt, pcov = optimize.curve_fit(func, x1, y1) - ytrend = func(x1, popt[0], popt[1]) - datadf['ytrend'] = ytrend + try: + popt, pcov = optimize.curve_fit(func, x1, y1) + ytrend = func(x1, popt[0], popt[1]) + datadf['ytrend'] = ytrend + except TypeError: + datadf['ytrend'] = y1 data_dict = datadf.to_dict("records") From 59b523de3fa66816872db6bc76301e0facba12ca Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Apr 2024 21:01:18 +0200 Subject: [PATCH 050/121] course map updates --- rowers/templates/course_edit_view.html | 2 -- rowers/templates/course_view.html | 2 -- rowers/templates/coursemap.html | 2 -- 3 files changed, 6 deletions(-) diff --git a/rowers/templates/course_edit_view.html b/rowers/templates/course_edit_view.html index 29be3f99..c281b2b7 100644 --- a/rowers/templates/course_edit_view.html +++ b/rowers/templates/course_edit_view.html @@ -32,9 +32,7 @@
    {{ mapdiv|safe }} -
  • diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html index d5f82bb4..109a1db3 100644 --- a/rowers/templates/course_view.html +++ b/rowers/templates/course_view.html @@ -54,9 +54,7 @@
    {{ mapdiv|safe }} -
    {% if records %} diff --git a/rowers/templates/coursemap.html b/rowers/templates/coursemap.html index 196ce47f..c7dd9b7a 100644 --- a/rowers/templates/coursemap.html +++ b/rowers/templates/coursemap.html @@ -19,9 +19,7 @@

    {{ course.name }}

    {{ mapdiv|safe }} -
    {% endblock %} From ac0d2bc50450c7f83cdae657a0f37d878add93d4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Apr 2024 22:26:17 +0200 Subject: [PATCH 051/121] moved some of form to chart --- rowers/forms.py | 8 -------- rowers/interactiveplots.py | 2 +- rowers/templates/forcecurve_single.html | 4 ++-- rowers/views/workoutviews.py | 5 ----- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 3376dc36..c69e3720 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1870,14 +1870,6 @@ class ForceCurveOptionsForm(forms.Form): notes = forms.CharField(initial="", label='notes', widget=HiddenInput, required=False) - plotchoices = ( - ('line', 'Force Curve Collection Plot'), - ('scatter', 'Peak Force Scatter Plot'), - ('none', 'Only aggregrate data') - ) - plottype = forms.ChoiceField(choices=plotchoices, initial='line', - label='Individual Stroke Chart Type') - name = forms.CharField(initial="", label='Name',required=False) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1e14f767..f8f8a5e0 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -534,7 +534,7 @@ def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter' columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy'] + 'workoutstate', 'driveenergy', 'cumdist'] rowdata = dataprep.getsmallrowdata_db(columns, ids=ids, workstrokesonly=workstrokesonly) diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html index f5da7253..f3251dba 100644 --- a/rowers/templates/forcecurve_single.html +++ b/rowers/templates/forcecurve_single.html @@ -25,8 +25,8 @@ {% csrf_token %} - {{ form.as_table }} -
    + {{ form.as_table }} +
    diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 909a6750..20c40276 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -448,10 +448,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly 'work_min': work_min, 'work_max': work_max, 'notes': notes, - 'plottype': 'line', 'name': name, }) - plottype = 'line' if request.method == 'POST': @@ -468,7 +466,6 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly if not name: name = row.name includereststrokes = form.cleaned_data['includereststrokes'] - plottype = form.cleaned_data['plottype'] workstrokesonly = not includereststrokes if "_save" in request.POST and "new" not in request.POST: # pragma: no cover @@ -523,13 +520,11 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly else: # pragma: no cover workstrokesonly = True - plottype = 'line' script, div = interactive_forcecurve( [row], workstrokesonly=workstrokesonly, - plottype=plottype, dist_min = dist_min, dist_max = dist_max, spm_min = spm_min, From 9bc107f427d3015befd7a7360fea1add92dd6597 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 3 Apr 2024 22:16:32 +0200 Subject: [PATCH 052/121] next version interactive plot --- rowers/forms.py | 12 ++++++++++-- rowers/interactiveplots.py | 16 ++-------------- rowers/models.py | 2 ++ rowers/templates/forcecurve_single.html | 10 +++++++++- rowers/views/workoutviews.py | 24 ++++++++++++++---------- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index c69e3720..f28ed65d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1859,12 +1859,13 @@ class FlexOptionsForm(forms.Form): class ForceCurveOptionsForm(forms.Form): includereststrokes = forms.BooleanField(initial=False, required=False, - label='Include Rest Strokes') + label='Include Rest Strokes', + widget=forms.CheckboxInput(attrs={'class':'hidden'})) spm_min = forms.FloatField(initial=15.0,label='SPM Min',widget=HiddenInput,required=False) spm_max = forms.FloatField(initial=55.0,label='SPM Max',widget=HiddenInput,required=False) dist_min = forms.IntegerField(initial=0,label='Dist Min',widget=HiddenInput,required=False) - dist_max = forms.IntegerField(initial=0,label='Dist Max',widget=HiddenInput,required=False) + dist_max = forms.IntegerField(initial=150000,label='Dist Max',widget=HiddenInput,required=False) work_min = forms.IntegerField(initial=0,label='Work Min',widget=HiddenInput,required=False) work_max = forms.IntegerField(initial=1500,label='Work Max',widget=HiddenInput,required=False) @@ -1872,6 +1873,13 @@ class ForceCurveOptionsForm(forms.Form): name = forms.CharField(initial="", label='Name',required=False) + plotcircles = forms.BooleanField(initial=False, + widget=forms.CheckboxInput(attrs={'class':'hidden'}), + required=False) + plotlines = forms.BooleanField(initial=False, + widget=forms.CheckboxInput(attrs={'class':'hidden'}), + required=False) + axchoices = list( (ax[0], ax[1]) for ax in axes if ax[0] not in ['cumdist', 'None'] diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f8f8a5e0..54ce2b5b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -521,11 +521,7 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', return script, div -def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', - spm_min=15, spm_max=45, - notes='', - dist_min=0,dist_max=0, - work_min=0,work_max=1500): +def interactive_forcecurve(theworkouts): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' ids = [int(w.id) for w in theworkouts] @@ -537,19 +533,11 @@ def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter' 'workoutstate', 'driveenergy', 'cumdist'] rowdata = dataprep.getsmallrowdata_db(columns, ids=ids, - workstrokesonly=workstrokesonly) + workstrokesonly=False) rowdata.dropna(axis=1, how='all', inplace=True) rowdata.dropna(axis=0, how='any', inplace=True) - workoutstatesrest = [3] - - if workstrokesonly: - try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] - except KeyError: # pragma: no cover - pass - if rowdata.empty: return "", "No Valid Data Available", "", "" diff --git a/rowers/models.py b/rowers/models.py index 28fce46e..b8cfeda2 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -5249,6 +5249,8 @@ class ForceCurveAnalysis(models.Model): average_spm = models.FloatField(default=23) average_boatspeed = models.FloatField(default=4.0) include_rest_strokes = models.BooleanField(default=False) + plotcircles = models.BooleanField(default=False) + plotlines = models.BooleanField(default=False) def __str__(self): s = 'Force Curve Analysis {name} ({date})'.format(name = self.name, diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html index f3251dba..1d95d9a9 100644 --- a/rowers/templates/forcecurve_single.html +++ b/rowers/templates/forcecurve_single.html @@ -14,6 +14,7 @@

    Empower Force Curve

      +
    • {{ the_div|safe }} @@ -44,7 +45,14 @@ diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 20c40276..2d871868 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -426,12 +426,14 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly work_max = forceanalysis.work_max notes = forceanalysis.notes name = forceanalysis.name + plotcircles = forceanalysis.plotcircles + plotlines = forceanalysis.plotlines includereststrokes = forceanalysis.include_rest_strokes except (ForceCurveAnalysis.DoesNotExist, ValueError): pass else: dist_min = 0 - dist_max = 0 + dist_max = row.distance spm_min = 15 spm_max = 55 work_min = 0 @@ -439,6 +441,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly notes = '' includereststrokes = False name = '' + plotcircles = True + plotlines = False form = ForceCurveOptionsForm(initial={ 'spm_min': spm_min, @@ -449,6 +453,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly 'work_max': work_max, 'notes': notes, 'name': name, + 'plotcircles': plotcircles, + 'plotlines': plotlines, }) @@ -463,6 +469,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly work_max = form.cleaned_data['work_max'] notes = form.cleaned_data['notes'] name = form.cleaned_data['name'] + plotlines = form.cleaned_data['plotlines'] + plotcircles = form.cleaned_data['plotcircles'] if not name: name = row.name includereststrokes = form.cleaned_data['includereststrokes'] @@ -482,6 +490,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly spm_min = spm_min, spm_max = spm_max, rower=row.user, + plotlines = plotlines, + plotcircles = plotcircles, include_rest_strokes = includereststrokes, ) else: @@ -495,6 +505,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly forceanalysis.work_max = work_max forceanalysis.spm_min = spm_min forceanalysis.spm_max = spm_max + forceanalysis.plotcircles = plotcircles + forceanalysis.plotlines = plotlines forceanalysis.include_rest_strokes = includereststrokes forceanalysis.save() dosave = True @@ -523,15 +535,7 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly script, div = interactive_forcecurve( - [row], - workstrokesonly=workstrokesonly, - dist_min = dist_min, - dist_max = dist_max, - spm_min = spm_min, - spm_max = spm_max, - work_min = work_min, - work_max = work_max, - notes=notes, + [row] ) breadcrumbs = [ From f2d163ad92e693d6149a97d51c0a1895756e493f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 3 Apr 2024 22:17:28 +0200 Subject: [PATCH 053/121] removing unnecessary code --- rowers/interactiveplots.py | 655 ------------------------------------- 1 file changed, 655 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 54ce2b5b..cd4cab81 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -554,661 +554,6 @@ def interactive_forcecurve(theworkouts): script, div = get_chart("/forcecurve", chart_data) return script, div - try: - covariancematrix = np.cov( - rowdata['peakforceangle'], y=rowdata['peakforce']) - eig_vals, eig_vecs = np.linalg.eig(covariancematrix) - - a = rowdata['peakforceangle']-rowdata['peakforceangle'].median() - F = rowdata['peakforce']-rowdata['peakforce'].median() - - Rinv = eig_vecs - R = np.linalg.inv(Rinv) - - x = R[0, 0]*a+R[0, 1]*F - y = R[1, 0]*a+R[1, 1]*F - - x05 = x.quantile(q=0.01) - x25 = x.quantile(q=0.15) - x75 = x.quantile(q=0.85) - x95 = x.quantile(q=0.99) - - y05 = y.quantile(q=0.01) - y25 = y.quantile(q=0.15) - y75 = y.quantile(q=0.85) - y95 = y.quantile(q=0.99) - - a25 = Rinv[0, 0]*x25 + rowdata['peakforceangle'].median() - F25 = Rinv[1, 0]*x25 + rowdata['peakforce'].median() - - a25b = Rinv[0, 1]*y25 + rowdata['peakforceangle'].median() - F25b = Rinv[1, 1]*y25 + rowdata['peakforce'].median() - - a75 = Rinv[0, 0]*x75 + rowdata['peakforceangle'].median() - F75 = Rinv[1, 0]*x75 + rowdata['peakforce'].median() - - a75b = Rinv[0, 1]*y75 + rowdata['peakforceangle'].median() - F75b = Rinv[1, 1]*y75 + rowdata['peakforce'].median() - - a05 = Rinv[0, 0]*x05 + rowdata['peakforceangle'].median() - F05 = Rinv[1, 0]*x05 + rowdata['peakforce'].median() - - a05b = Rinv[0, 1]*y05 + rowdata['peakforceangle'].median() - F05b = Rinv[1, 1]*y05 + rowdata['peakforce'].median() - - a95 = Rinv[0, 0]*x95 + rowdata['peakforceangle'].median() - F95 = Rinv[1, 0]*x95 + rowdata['peakforce'].median() - - a95b = Rinv[0, 1]*y95 + rowdata['peakforceangle'].median() - F95b = Rinv[1, 1]*y95 + rowdata['peakforce'].median() - except KeyError: # pragma: no cover - a25 = 0 - F25 = 0 - - a25b = 0 - F25b = 0 - - a75 = 0 - F75 = 0 - - a75b = 0 - F75b = 0 - - a05 = 0 - F05 = 0 - - a05b = 0 - F05b = 0 - - a95 = 0 - F95 = 0 - - a95b = 0 - F95b = 0 - - try: - catchav = rowdata['catch'].median() - catch25 = rowdata['catch'].quantile(q=0.25) - catch75 = rowdata['catch'].quantile(q=0.75) - catch05 = rowdata['catch'].quantile(q=0.05) - catch95 = rowdata['catch'].quantile(q=0.95) - except KeyError: # pragma: no cover - catchav = 0 - catch25 = 0 - catch75 = 0 - catch05 = 0 - catch95 = 0 - - try: - finishav = rowdata['finish'].median() - finish25 = rowdata['finish'].quantile(q=0.25) - finish75 = rowdata['finish'].quantile(q=0.75) - finish05 = rowdata['finish'].quantile(q=0.05) - finish95 = rowdata['finish'].quantile(q=0.95) - except KeyError: # pragma: no cover - finishav = 0 - finish25 = 0 - finish75 = 0 - finish05 = 0 - finish95 = 0 - - try: - washav = (rowdata['finish']-rowdata['wash']).median() - wash25 = (rowdata['finish']-rowdata['wash']).quantile(q=0.25) - wash75 = (rowdata['finish']-rowdata['wash']).quantile(q=0.75) - wash05 = (rowdata['finish']-rowdata['wash']).quantile(q=0.05) - wash95 = (rowdata['finish']-rowdata['wash']).quantile(q=0.95) - except KeyError: # pragma: no cover - washav = 0 - wash25 = 0 - wash75 = 0 - wash05 = 0 - wash95 = 0 - - try: - slipav = (rowdata['slip']+rowdata['catch']).median() - slip25 = (rowdata['slip']+rowdata['catch']).quantile(q=0.25) - slip75 = (rowdata['slip']+rowdata['catch']).quantile(q=0.75) - slip05 = (rowdata['slip']+rowdata['catch']).quantile(q=0.05) - slip95 = (rowdata['slip']+rowdata['catch']).quantile(q=0.95) - except KeyError: # pragma: no cover - slipav = 0 - slip25 = 0 - slip75 = 0 - slip05 = 0 - slip95 = 0 - - try: - peakforceav = rowdata['peakforce'].median() - except KeyError: # pragma: no cover - peakforceav = 0 - - try: - averageforceav = rowdata['averageforce'].median() - except KeyError: # pragma: no cover - averageforceav = 0 - - try: - peakforceangleav = rowdata['peakforceangle'].median() - except KeyError: # pragma: no cover - peakforceangleav = 0 - - # thresholdforce /= 4.45 # N to lbs - thresholdforce = 100 if 'x' in boattype else 200 - points2575 = [ - (catch25, 0), # 0 - (slip25, thresholdforce), # 1 - (a75, F75), # 4 - (a25b, F25b), # 9 - (a25, F25), # 2 - (wash75, thresholdforce), # 5 - (finish75, 0), # 6 - (finish25, 0), # 7 - (wash25, thresholdforce), # 8 - (a75b, F75b), # 3 - (slip75, thresholdforce), # 10 - (catch75, 0), # 11 - ] - - points0595 = [ - (catch05, 0), # 0 - (slip05, thresholdforce), # 1 - (a95, F95), # 4 - (a05b, F05b), # 9 - (a05, F05), # 2 - (wash95, thresholdforce), # 5 - (finish95, 0), # 6 - (finish05, 0), # 7 - (wash05, thresholdforce), # 8 - (a95b, F95b), # 3 - (slip95, thresholdforce), # 10 - (catch95, 0), # 11 - ] - - angles2575 = [] - forces2575 = [] - - for x, y in points2575: - angles2575.append(x) - forces2575.append(y) - - angles0595 = [] - forces0595 = [] - - for x, y in points0595: - angles0595.append(x) - forces0595.append(y) - - x = [catchav, - slipav, - peakforceangleav, - washav, - finishav] - - y = [0, thresholdforce, - peakforceav, - thresholdforce, 0] - - source = ColumnDataSource( - data=dict( - x=x, - y=y, - )) - - sourceslipwash = ColumnDataSource( - data=dict( - xslip=[slipav, washav], - yslip=[thresholdforce, thresholdforce] - ) - ) - - source2 = ColumnDataSource( - rowdata - ) - - if plottype == 'scatter': # pragma: no cover - try: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=rowdata['peakforceangle'], - peakforce=rowdata['peakforce'] - ) - ) - except KeyError: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=[], - peakforce=[] - ) - ) - else: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=[], - peakforce=[] - )) - - sourcerange = ColumnDataSource( - data=dict( - x2575=angles2575, - y2575=forces2575, - x0595=angles0595, - y0595=forces0595, - ) - ) - - plot = figure(tools=TOOLS, - toolbar_sticky=False, toolbar_location="above", width=800, height=600) - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - avf = Span(location=averageforceav, dimension='width', line_color='blue', - line_dash=[6, 6], line_width=2) - - plot.patch('x0595', 'y0595', source=sourcerange, color="red", alpha=0.05) - plot.patch('x2575', 'y2575', source=sourcerange, color="red", alpha=0.2) - plot.line('x', 'y', source=source, color="red") - plot.circle('xslip', 'yslip', source=sourceslipwash, color="red") - - plot.circle('peakforceangle', 'peakforce', - source=sourcepoints, color='black', alpha=0.1) - - if plottype == 'line': - multilinedatax = [] - multilinedatay = [] - for i in range(len(rowdata)): - try: - x = [ - rowdata['catch'].values[i], - rowdata['slip'].values[i]+rowdata['catch'].values[i], - rowdata['peakforceangle'].values[i], - rowdata['finish'].values[i]-rowdata['wash'].values[i], - rowdata['finish'].values[i] - ] - - y = [ - 0, - thresholdforce, - rowdata['peakforce'].values[i], - thresholdforce, - 0] - except KeyError: # pragma: no cover - x = [0, 0] - y = [0, 0] - - multilinedatax.append(x) - multilinedatay.append(y) - - sourcemultiline = ColumnDataSource(dict( - x=multilinedatax, - y=multilinedatay, - )) - - sourcemultiline2 = ColumnDataSource(dict( - x=multilinedatax, - y=multilinedatay, - )) - - glyph = MultiLine(xs='x', ys='y', line_color='black', line_alpha=0.05) - plot.add_glyph(sourcemultiline, glyph) - else: # pragma: no cover - sourcemultiline = ColumnDataSource(dict( - x=[], y=[])) - - sourcemultiline2 = ColumnDataSource(dict( - x=[], y=[])) - - plot.line('x', 'y', source=source, color="red") - - plot.add_layout(avf) - - peakflabel = Label(x=760, y=460, x_units='screen', y_units='screen', - text="Fpeak: {peakforceav:6.2f}".format( - peakforceav=peakforceav), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - avflabel = Label(x=770, y=430, x_units='screen', y_units='screen', - text="Favg: {averageforceav:6.2f}".format( - averageforceav=averageforceav), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - catchlabel = Label(x=765, y=400, x_units='screen', y_units='screen', - text="Catch: {catchav:6.2f}".format(catchav=catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - peakforceanglelabel = Label(x=725, y=370, x_units='screen', y_units='screen', - text="Peak angle: {peakforceangleav:6.2f}".format( - peakforceangleav=peakforceangleav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - finishlabel = Label(x=760, y=340, x_units='screen', y_units='screen', - text="Finish: {finishav:6.2f}".format( - finishav=finishav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - sliplabel = Label(x=775, y=310, x_units='screen', y_units='screen', - text="Slip: {slipav:6.2f}".format(slipav=slipav-catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - washlabel = Label(x=765, y=280, x_units='screen', y_units='screen', - text="Wash: {washav:6.2f}".format( - washav=finishav-washav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - lengthlabel = Label(x=755, y=250, x_units='screen', y_units='screen', - text="Length: {length:6.2f}".format( - length=finishav-catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='green' - ) - - efflengthlabel = Label(x=690, y=220, x_units='screen', y_units='screen', - text="Effective Length: {length:6.2f}".format( - length=washav-slipav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='green' - ) - - annolabel = Label(x=50, y=450, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - sliderlabel = Label(x=10, y=470, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(peakflabel) - plot.add_layout(peakforceanglelabel) - plot.add_layout(avflabel) - plot.add_layout(catchlabel) - plot.add_layout(sliplabel) - plot.add_layout(washlabel) - plot.add_layout(finishlabel) - plot.add_layout(annolabel) - plot.add_layout(sliderlabel) - plot.add_layout(lengthlabel) - plot.add_layout(efflengthlabel) - - plot.xaxis.axis_label = "Angle" - plot.yaxis.axis_label = "Force (N)" - try: - plot.title.text = theworkouts[0].name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - - yrange1 = Range1d(start=0, end=900) - plot.y_range = yrange1 - - xrange1 = Range1d(start=yaxmaxima['catch'], end=yaxmaxima['finish']) - plot.x_range = xrange1 - - callback = CustomJS(args=dict( - source=source, - source2=source2, - sourceslipwash=sourceslipwash, - sourcepoints=sourcepoints, - avf=avf, - avflabel=avflabel, - catchlabel=catchlabel, - finishlabel=finishlabel, - sliplabel=sliplabel, - washlabel=washlabel, - peakflabel=peakflabel, - peakforceanglelabel=peakforceanglelabel, - annolabel=annolabel, - sliderlabel=sliderlabel, - lengthlabel=lengthlabel, - efflengthlabel=efflengthlabel, - plottype=plottype, - sourcemultiline=sourcemultiline, - sourcemultiline2=sourcemultiline2 - ), code=""" - var data = source.data - var data2 = source2.data - var dataslipwash = sourceslipwash.data - var datapoints = sourcepoints.data - var multilines = sourcemultiline.data - var multilines2 = sourcemultiline2.data - var plottype = plottype - - var multilinesx = multilines2['x'] - var multilinesy = multilines2['y'] - - var x = data['x'] - var y = data['y'] - - var xslip = dataslipwash['xslip'] - - var spm1 = data2['spm'] - var distance1 = data2['distance'] - var driveenergy1 = data2['driveenergy'] - - var thresholdforce = y[1] - - var c = source2.data['catch'] - var finish = data2['finish'] - var slip = data2['slip'] - var wash = data2['wash'] - var peakforceangle = data2['peakforceangle'] - var peakforce = data2['peakforce'] - var averageforce = data2['averageforce'] - - var peakforcepoints = datapoints['peakforce'] - var peakforceanglepoints = datapoints['peakforceangle'] - - var annotation = annotation.value - var minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - - sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0) - sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0) - sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0) - - var catchav = 0 - var finishav = 0 - var slipav = 0 - var washav = 0 - var peakforceangleav = 0 - var averageforceav = 0 - var peakforceav = 0 - var count = 0 - - datapoints['peakforceangle'] = [] - datapoints['peakforce'] = [] - multilines['x'] = [] - multilines['y'] = [] - - for (var i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - if (plottype=='scatter') { - datapoints['peakforceangle'].push(peakforceangle[i]) - datapoints['peakforce'].push(peakforce[i]) - } - if (plottype=='line') { - multilines['x'].push(multilinesx[i]) - multilines['y'].push(multilinesy[i]) - } - catchav += c[i] - finishav += finish[i] - slipav += slip[i] - washav += wash[i] - peakforceangleav += peakforceangle[i] - averageforceav += averageforce[i] - peakforceav += peakforce[i] - count += 1 - } - } - } - } - - catchav /= count - finishav /= count - slipav /= count - washav /= count - peakforceangleav /= count - peakforceav /= count - averageforceav /= count - - data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav] - data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0] - - dataslipwash['xslip'] = [catchav+slipav,finishav-washav] - dataslipwash['yslip'] = [thresholdforce,thresholdforce] - - var length = finishav-catchav - var efflength = length-slipav-washav - - avf.location = averageforceav - avflabel.text = 'Favg: '+averageforceav.toFixed(2) - catchlabel.text = 'Catch: '+catchav.toFixed(2) - finishlabel.text = 'Finish: '+finishav.toFixed(2) - sliplabel.text = 'Slip: '+slipav.toFixed(2) - washlabel.text = 'Wash: '+washav.toFixed(2) - peakflabel.text = 'Fpeak: '+peakforceav.toFixed(2) - peakforceanglelabel.text = 'Peak angle: '+peakforceangleav.toFixed(2) - annolabel.text = annotation - lengthlabel.text = 'Length: '+length.toFixed(2) - efflengthlabel.text = 'Effective Length: '+efflength.toFixed(2) - - // console.log(count); - // console.log(multilines['x'].length); - // console.log(multilines['y'].length); - - // change DOM elements - document.getElementById("id_spm_min").value = minspm; - document.getElementById("id_spm_max").value = maxspm; - document.getElementById("id_dist_min").value = mindist; - document.getElementById("id_dist_max").value = maxdist; - document.getElementById("id_notes").value = annotation; - document.getElementById("id_work_min").value = minwork; - document.getElementById("id_work_max").value = maxwork; - - // source.trigger('change'); - source.change.emit(); - sourceslipwash.change.emit() - sourcepoints.change.emit(); - sourcemultiline.change.emit(); - """) - - annotation = TextInput( - width=140, title="Type your plot notes here", value="", name="annotation") - annotation.js_on_change('value', callback) - callback.args["annotation"] = annotation - - slider_spm_min = Slider(width=140, start=15.0, end=55, value=15, step=.1, - title="Min SPM", name="min_spm_slider") - slider_spm_min.js_on_change('value', callback) - callback.args["minspm"] = slider_spm_min - - slider_spm_max = Slider(width=140, start=15.0, end=55, value=55, step=.1, - title="Max SPM", name="max_spm_slider") - slider_spm_max.js_on_change('value', callback) - callback.args["maxspm"] = slider_spm_max - - slider_work_min = Slider(width=140, start=0, end=1500, value=0, step=10, - title="Min Work per Stroke", name="min_work_slider") - slider_work_min.js_on_change('value', callback) - callback.args["minwork"] = slider_work_min - - slider_work_max = Slider(width=140, start=0, end=1500, value=1500, step=10, - title="Max Work per Stroke", name="max_work_slider") - slider_work_max.js_on_change('value', callback) - callback.args["maxwork"] = slider_work_max - - distmax = 100+100*int(rowdata['distance'].max()/100.) - - slider_dist_min = Slider(width=140, start=0, end=distmax, value=0, step=50, - title="Min Distance", name="min_dist_slider") - slider_dist_min.js_on_change('value', callback) - callback.args["mindist"] = slider_dist_min - - if dist_max == 0: - dist_max = distmax - - slider_dist_max = Slider(width=140, start=0, end=distmax, value=distmax, - step=50, - title="Max Distance", name="max_dist_slider") - slider_dist_max.js_on_change('value', callback) - callback.args["maxdist"] = slider_dist_max - - thesliders = layoutcolumn([annotation, - slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, - slider_work_min, - slider_work_max, - ] - ) - - mylayout = layoutrow([thesliders, plot]) - - #mylayout.sizing_mode = 'stretch_both' - - script, div = components(mylayout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - return [script, div, js_resources, css_resources] def weightfromrecord(row,metricchoice): vv = row[metricchoice] From 9d2d5b30d0506aa0e2d3ed7cfa3dea1a538717fc Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 5 Apr 2024 13:35:46 +0200 Subject: [PATCH 054/121] fix --- rowers/interactiveplots.py | 158 ------------------------------------- 1 file changed, 158 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 804ebc52..cd4cab81 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -926,164 +926,6 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, def course_map(course): -<<<<<<< HEAD -======= - latmean, lonmean, coordinates = course_coord_center(course) - if course.with_cn_nav_waypoints: - latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - - coordinates = course_spline(coordinates) - - scoordinates = "[" - - for index, row in coordinates.iterrows(): - scoordinates += """[{x},{y}], - """.format( - x=row['latitude'], - y=row['longitude'] - ) - - scoordinates += "]" - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" - - script = """ - - """.format( - id=course.id, - latmean=latmean, - lonmean=lonmean, - scoordinates=scoordinates, - pcoordinates=pcoordinates, - plabels=plabels - ) - - div = """ -
      - """.format( - id=course.id, - ) ->>>>>>> develop course_dict = GeoCourseSerializer(course).data From 560800f80c9656a09e72e247ce50c5497672a78c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 5 Apr 2024 17:19:34 +0200 Subject: [PATCH 055/121] beginning of forcecuve_compare --- rowers/interactiveplots.py | 70 ++++++++++------------- rowers/serializers.py | 18 +++++- rowers/templates/forcecurve_analysis.html | 12 +--- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index cd4cab81..45e731f2 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -522,8 +522,6 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', return script, div def interactive_forcecurve(theworkouts): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - ids = [int(w.id) for w in theworkouts] boattype = theworkouts[0].boattype @@ -1990,52 +1988,42 @@ def interactive_streamchart(id=0, promember=0): def forcecurve_multi_interactive_chart(selected): # pragma: no cover df_plot = pd.DataFrame() ids = [analysis.id for analysis in selected] + workoutids = [analysis.workout.id for analysis in selected] + + selected_dict = [ForceCurveAnalysisSerializer(analysis).data for analysis in selected] columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy'] + 'workoutstate', 'driveenergy', 'cumdist'] + rowdata = dataprep.getsmallrowdata_db(columns, ids=workoutids, + workstrokesonly=False) + + + rowdata.dropna(axis=1, how='all', inplace=True) + rowdata.dropna(axis=0, how='any', inplace=True) + + if rowdata.empty: + return "", "No Valid Data Available", "", "" + + data_dict = rowdata.to_dict("records") + + thresholdforces = [] for analysis in selected: - workstrokesonly = not analysis.include_rest_strokes - spm_min = analysis.spm_min - spm_max = analysis.spm_max - dist_min = analysis.dist_min - dist_max = analysis.dist_max - work_min = analysis.work_min - work_max = analysis.work_max - rowdata = dataprep.getsmallrowdata_db(columns, ids=[analysis.workout.id], - workstrokesonly=workstrokesonly) + boattype = analysis.workout.boattype + thresholdforce = 100. if 'x' in boattype else 200. + thresholdforces.append({'id': analysis.workout.id, 'thresholdforce': thresholdforce}) - rowdata = rowdata[rowdata['spm']>spm_min] - rowdata = rowdata[rowdata['spm']work_min] - rowdata = rowdata[rowdata['driveenergy']dist_min] - - catchav = rowdata['catch'].median() - finishav = rowdata['finish'].median() - washav = (rowdata['finish']-rowdata['wash']).median() - slipav = (rowdata['slip']+rowdata['catch']).median() - peakforceav = rowdata['peakforce'].median() - peakforceangleav = rowdata['peakforceangle'].median() - thresholdforce = 100 if 'x' in analysis.workout.boattype else 200 - x = [catchav, - slipav, - peakforceangleav, - washav, - finishav] - - y = [0, thresholdforce, - peakforceav, - thresholdforce, 0] - - xname = 'x_'+str(analysis.id) - yname = 'y_'+str(analysis.id) - - df_plot[xname] = x - df_plot[yname] = y + chart_data = { + 'title': '', + 'data': data_dict, + 'thresholdforces': thresholdforces, + 'forcecurve_analyses': selected_dict, + } + script, div = get_chart("/forcecurve_compare", chart_data) + return script, div + source = ColumnDataSource( df_plot ) diff --git a/rowers/serializers.py b/rowers/serializers.py index cb9de92b..1dc594ef 100644 --- a/rowers/serializers.py +++ b/rowers/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers from rowers.models import ( Workout, Rower, FavoriteChart, VirtualRaceResult, VirtualRace, GeoCourse, StandardCollection, CourseStandard, - GeoPolygon, GeoPoint, PlannedSession, + GeoPolygon, GeoPoint, PlannedSession, ForceCurveAnalysis ) from django.core.exceptions import PermissionDenied @@ -297,7 +297,23 @@ class GeoPointSerializer(serializers.ModelSerializer): ) extra_kwargs = {'id': {'read_only': False, 'required': True}} +class ForceCurveAnalysisSerializer(serializers.ModelSerializer): + class Meta: + model = ForceCurveAnalysis + fields = ( + 'id', + 'name', + 'workout', + 'dist_min', + 'dist_max', + 'spm_min', + 'spm_max', + 'work_min', + 'work_max', + 'include_rest_strokes' + ) + class GeoPolygonSerializer(serializers.ModelSerializer): points = GeoPointSerializer(many=True) diff --git a/rowers/templates/forcecurve_analysis.html b/rowers/templates/forcecurve_analysis.html index b46f8d37..cbd51e2a 100644 --- a/rowers/templates/forcecurve_analysis.html +++ b/rowers/templates/forcecurve_analysis.html @@ -6,16 +6,9 @@ {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + + - - - - -{{ the_script |safe }}

      Force Curve Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

      @@ -25,6 +18,7 @@
    • {{ the_div|safe }} + {{ the_script |safe }}
    • {% endif %} From bc102b2ac43569364f74b60a0a10da1b840992d7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 7 Apr 2024 12:11:08 +0200 Subject: [PATCH 056/121] thumbnails --- rowers/interactiveplots.py | 89 ++++++----------------- rowers/templates/forcecurve_analysis.html | 1 + rowers/templates/panel_map.html | 4 +- rowers/templates/workflow.html | 9 +-- 4 files changed, 28 insertions(+), 75 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 45e731f2..7a6ce8f5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1994,7 +1994,7 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy', 'cumdist'] + 'workoutstate', 'workoutid', 'driveenergy', 'cumdist'] rowdata = dataprep.getsmallrowdata_db(columns, ids=workoutids, workstrokesonly=False) @@ -2024,70 +2024,6 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover script, div = get_chart("/forcecurve_compare", chart_data) return script, div - source = ColumnDataSource( - df_plot - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - xname = 'x_'+str(id) - yname = 'y_'+str(id) - analysis = ForceCurveAnalysis.objects.get(id=id) - legendlabel = '{name}'.format( - name = analysis.name, - ) - if analysis.notes: - legendlabel = '{name} - {notes}'.format( - name = analysis.name, - notes = analysis.notes - ) - plot.line(xname,yname,source=source,legend_label=legendlabel, - line_width=2, color=color) - - plot.legend.location = "top_left" - plot.xaxis.axis_label = "Angle" - plot.yaxis.axis_label = "Force (N)" - - script, div = components(plot) - - return (script, div) def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no cover df2 = [] @@ -2880,6 +2816,7 @@ def thumbnails_set(r, id, favorites): except (KeyError, TypeError): # pragma: no cover pass + plotnr = 1 for f in favorites: script, div = thumbnail_flex_chart( rowdata, @@ -2888,7 +2825,9 @@ def thumbnails_set(r, id, favorites): yparam1=f.yparam1, yparam2=f.yparam2, plottype=f.plottype, + plotnr=plotnr ) + plotnr = plotnr+1 charts.append({ 'script': script, @@ -2903,6 +2842,7 @@ def thumbnail_flex_chart(rowdata, id=0, promember=0, yparam1='pace', yparam2='hr', plottype='line', + plotnr=1, workstrokesonly=False): try: @@ -2938,6 +2878,25 @@ def thumbnail_flex_chart(rowdata, id=0, promember=0, else: rowdata['y2'] = rowdata['y1'] + data_dict = rowdata.to_dict("records") + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': '', + 'x': xparam, + 'y1': yparam1, + 'y2': yparam2, + 'data': data_dict, + 'metrics': metrics_list, + 'plottype': plottype, + 'plotnr': plotnr, + } + + script, div = get_chart("/miniflex", chart_data) + + return script, div + + if xparam == 'time': xaxmax = tseconds.max() xaxmin = tseconds.min() diff --git a/rowers/templates/forcecurve_analysis.html b/rowers/templates/forcecurve_analysis.html index cbd51e2a..2216bd01 100644 --- a/rowers/templates/forcecurve_analysis.html +++ b/rowers/templates/forcecurve_analysis.html @@ -14,6 +14,7 @@
        + {% if the_div %}
      • diff --git a/rowers/templates/panel_map.html b/rowers/templates/panel_map.html index 25941388..cfc7f384 100644 --- a/rowers/templates/panel_map.html +++ b/rowers/templates/panel_map.html @@ -3,9 +3,7 @@
        {{ mapdiv|safe }} - + {{ mapscript|safe }}
      diff --git a/rowers/templates/workflow.html b/rowers/templates/workflow.html index f61d1348..f0e331f1 100644 --- a/rowers/templates/workflow.html +++ b/rowers/templates/workflow.html @@ -14,12 +14,6 @@ {{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %} {% block meta %} -{% leaflet_js %} -{% leaflet_css %} - - {% for chart in charts %} {{ chart.script |safe }} @@ -51,7 +45,7 @@ ""+element.div+""+ ""); - $("#id_thumbscripts").append(element.script); + $("#id_thumbs").append(element.script); counter += 1; }); }); @@ -64,6 +58,7 @@ {% block main %} +

      {% if workout|previousworkout:rower.user %} Date: Sun, 7 Apr 2024 12:48:54 +0200 Subject: [PATCH 057/121] removing unnecessary code --- rowers/interactiveplots.py | 178 --------------------------------- rowers/templates/map_view.html | 2 - rowers/templates/workflow.html | 2 + 3 files changed, 2 insertions(+), 180 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 7a6ce8f5..4276704f 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -174,67 +174,6 @@ def all_goldmedalstandards(workouts, startdate, enddate): return dates, testpowers, testduration, ids -def errorbar(fig, x, y, source=ColumnDataSource(), - xerr=False, yerr=False, color='black', - point_kwargs={}, error_kwargs={}): - - xvalues = source.data[x] - yvalues = source.data[y] - - xerrvalues = source.data['xerror'] - yerrvalues = source.data['yerror'] - try: - colorvalues = source.data['color'] - except KeyError: # pragma: no cover - colorvalues = ["#%02x%02x%02x" % (255, 0, 0) for x in xvalues] - - try: - if xerr: - x_err_x = [] - x_err_y = [] - err_color = [] - for px, py, err, color in zip(xvalues, yvalues, xerrvalues, colorvalues): - x_err_x.append((px - err, px + err)) - x_err_y.append((py, py)) - (r, g, b) = tuple(int(color[i:i+2], 16) for i in (1, 3, 5)) - h, s, v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) - v = v*0.8 - r, g, b = colorsys.hsv_to_rgb(h, s, v) - color2 = "#%02x%02x%02x" % ( - int(255.*r), int(255.*g), int(255*b)) - err_color.append(color2) - - fig.multi_line(x_err_x, x_err_y, color=err_color, - name='xerr', - **error_kwargs) - except TypeError: # pragma: no cover - pass - - try: - if yerr: - y_err_x = [] - y_err_y = [] - err_color = [] - for px, py, err, color in zip(xvalues, yvalues, yerrvalues, colorvalues): - y_err_x.append((px, px)) - y_err_y.append((py - err, py + err)) - (r, g, b) = tuple(int(color[i:i+2], 16) for i in (1, 3, 5)) - h, s, v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) - v = v*0.8 - r, g, b = colorsys.hsv_to_rgb(h, s, v) - color2 = "#%02x%02x%02x" % ( - int(255.*r), int(255.*g), int(255*b)) - err_color.append(color2) - - fig.multi_line(y_err_x, y_err_y, color=err_color, - name='yerr', **error_kwargs) - except TypeError: # pragma: no cover - pass - - fig.circle(x, y, source=source, name='data', color=color, - **point_kwargs) - - def tailwind(bearing, vwind, winddir): """ Calculates head-on head/tailwind in direction of rowing @@ -2896,123 +2835,6 @@ def thumbnail_flex_chart(rowdata, id=0, promember=0, return script, div - - if xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'distance' or xparam == 'cumdist': # pragma: no cover - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - else: - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': # pragma: no cover - y_axis_type = 'datetime' - - rowdata['xname'] = axlabels[xparam] - try: - rowdata['yname1'] = axlabels[yparam1] - except KeyError: # pragma: no cover - rowdata['yname1'] = axlabels[xparam] - if yparam2 != 'None': - rowdata['yname2'] = axlabels[yparam2] - else: - rowdata['yname2'] = axlabels[yparam1] - - # prepare data - source = ColumnDataSource( - rowdata - ) - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - width=200, height=150, - ) - - -# plot.sizing_mode = 'stretch_both' - plot.sizing_mode = 'fixed' - plot.toolbar.logo = None - plot.toolbar_location = None - plot.xaxis.axis_label_text_font_size = "7pt" - plot.yaxis.axis_label_text_font_size = "7pt" - plot.xaxis.major_label_text_font_size = "7pt" - plot.yaxis.major_label_text_font_size = "7pt" - - if plottype == 'line': - plot.line('x1', 'y1', source=source) - elif plottype == 'scatter': - plot.scatter('x1', 'y1', source=source, fill_alpha=0.4, - line_color=None) - - try: - plot.xaxis.axis_label = axlabels[xparam] - except KeyError: # pragma: no cover - plot.xaxis.axis_label = 'X' - try: - plot.yaxis.axis_label = axlabels[yparam1] - except KeyError: # pragma: no cover - plot.yaxis.axis_label = 'Y' - - try: - yrange1 = Range1d(start=yaxminima[yparam1], end=yaxmaxima[yparam1]) - except KeyError: # pragma: no cover - yrange1 = Range1d(start=yparam1.min(), end=yparam1.max()) - - plot.y_range = yrange1 - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): - xrange1 = Range1d(start=yaxminima[xparam], end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - if yparam1 == 'pace': # pragma: no cover - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - if yparam2 != 'None': - yrange2 = Range1d(start=yaxminima[yparam2], end=yaxmaxima[yparam2]) - plot.extra_y_ranges["yax2"] = yrange2 - # = {"yax2": yrange2} - - if plottype == 'line': - plot.line('x1', 'y2', color="red", y_range_name="yax2", - source=source) - - elif plottype == 'scatter': # pragma: no cover - plot.scatter('x1', 'y2', source=source, - fill_alpha=0.4, - line_color=None, color="red", y_range_name="yax2") - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlabels[yparam2], - major_label_text_font_size="7pt", - axis_label_text_font_size="7pt", - ), 'right', - ) - - script, div = components(plot) - - return [script, div] - def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', promember=0, workstrokesonly=True, diff --git a/rowers/templates/map_view.html b/rowers/templates/map_view.html index 7938fe02..f4fe8ce6 100644 --- a/rowers/templates/map_view.html +++ b/rowers/templates/map_view.html @@ -26,9 +26,7 @@

    diff --git a/rowers/templates/workflow.html b/rowers/templates/workflow.html index f0e331f1..da410dcc 100644 --- a/rowers/templates/workflow.html +++ b/rowers/templates/workflow.html @@ -14,6 +14,8 @@ {{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %} {% block meta %} +{% leaflet_js %} +{% leaflet_css %} {% for chart in charts %} {{ chart.script |safe }} From 12915ad6b77e9f7ed2988d508d26b4f80f589235 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 7 Apr 2024 19:19:44 +0200 Subject: [PATCH 058/121] first routines with polars --- rowers/dataroutines.py | 363 ++++++++++++++++++++++++++++++++-- rowers/interactiveplots.py | 53 ++--- rowers/views/analysisviews.py | 7 +- 3 files changed, 382 insertions(+), 41 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 3bcd683d..822230d3 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -31,6 +31,7 @@ from zipfile import BadZipFile import zipfile import os from rowers.models import strokedatafields +import polars as pl from rowingdata import ( KinoMapParser, @@ -400,12 +401,16 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, try: _ = datadf['workoutid'].unique() except KeyError: - datadf['workoutid'] = 0 + try: + datadf['workoutid'] = 0 + except TypeError: + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) before = {} - for workoutid in datadf['workoutid'].unique(): + ids = datadf['workoutid'].unique() + for workoutid in ids: before[workoutid] = len(datadf[datadf['workoutid'] == workoutid]) - + data_orig = datadf.copy() # bring metrics which have negative values to positive domain @@ -654,6 +659,258 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, return datadf +def replace_zeros_with_nan(x): + return np.nan if x == 0 else x + +def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, + ignoreadvanced=False, for_chart=False): + # clean data remove zeros and negative values + + try: + _ = datadf['workoutid'].unique() + except KeyError: + try: + datadf['workoutid'] = 0 + except TypeError: + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) + + before = {} + ids = list(datadf['workoutid'].unique()) + for workoutid in ids: + before[workoutid] = len(datadf.filter(pl.col("workoutid")==workoutid)) + + data_orig = datadf.clone() + + # bring metrics which have negative values to positive domain + if len(datadf) == 0: + return datadf + try: + datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.with_columns((pl.col('peakforceangle')+1000).alias('peakforceangle')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) + except (KeyError, TypeError): + pass + + # protect 0 spm values from being nulled + try: + datadf = datadf.with_columns((pl.col('spm')+1.0).alias('spm')) + except (KeyError, TypeError): + pass + + # protect 0 workoutstate values from being nulled + try: + datadf = datadf.with_columns((pl.col('workoutstate')+1).alias('workoutstate')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.select(pl.all().clip(lower_bound=0)) + # datadf = datadf.clip(lower=0) + except TypeError: + pass + + # protect advanced metrics columns + advancedcols = [ + 'rhythm', + 'power', + 'drivelength', + 'forceratio', + 'drivespeed', + 'driveenergy', + 'catch', + 'finish', + 'averageforce', + 'peakforce', + 'slip', + 'wash', + 'peakforceangle', + 'effectiveangle', + ] + + for col in datadf.columns: + datadf = datadf.with_columns( + pl.when(datadf[col] == 0).then(pl.lit(np.nan)).otherwise(datadf[col]), + name=col + ) + + # datadf = datadf.map_partitions(lambda df:df.replace(to_replace=0,value=np.nan)) + + # bring spm back to real values + try: + datadf = datadf.with_columns((pl.col('spm')-1.0).alias('spm')) + except (TypeError, KeyError): + pass + + # bring workoutstate back to real values + try: + datadf = datadf.with_columns((pl.col('workoutstate')-1).alias('workoutstate')) + except (TypeError, KeyError): + pass + + # return from positive domain to negative + try: + datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.with_columns((pl.col('peakforceangle')-1000).alias('peakforceangle')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) + except (KeyError, TypeError): + pass + + # clean data for useful ranges per column + if not ignorehr: + try: + datadf = datadf.filter(pl.col("hr")>=30) + except (KeyError, TypeError): # pragma: no cover + pass + + try: + datadf = datadf.filter(pl.col("spm") >=0) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("efficiency")<=200) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("spm")>=10) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("pace")<=300*1000.) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("efficiency")>=0) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("pace")>=60*1000) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("power")<=5000) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("spm")<=120) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("wash")>=1) + except (KeyError, TypeError): + pass + + # try to guess ignoreadvanced + if not ignoreadvanced: + for metric in advancedcols: + try: + sum = datadf[metric].std() + if sum == 0 or np.isnan(sum): + ignoreadvanced = True + except KeyError: + pass + + if not ignoreadvanced: + try: + datadf = datadf.filter(pl.col("rhythm")>=0) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("rhythm")<=70) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("power")>=20) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("drivelength")>=0.5) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("forceratio")>=0.2) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("forceratio")<=1.0) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("drivespeed")>=0.5) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("drivespeed")<=4) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("driveenergy")<=2000) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("driveenergy")>=100) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.filter(pl.col("catch")<=-30) + except (KeyError, TypeError): + pass + + # workoutstateswork = [1, 4, 5, 8, 9, 6, 7] + workoutstatesrest = [3] + # workoutstatetransition = [0, 2, 10, 11, 12, 13] + + if workstrokesonly == 'True' or workstrokesonly is True: + try: + datadf = datadf.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) + except: + pass + + after = {} + + if for_chart: + return datadf + for workoutid in data_orig['workoutid'].unique(): + after[workoutid] = len(datadf.filter(pl.col("workoutid")==workoutid)) + ratio = float(after[workoutid])/float(before[workoutid]) + if ratio < 0.01 or after[workoutid] < 2: + return data_orig + + return datadf + def getpartofday(row, r): workoutstartdatetime = row.rowdatetime @@ -863,12 +1120,6 @@ def get_workoutsummaries(userid, startdate): # pragma: no cover return df - - - - - - def checkduplicates(r, workoutdate, workoutstartdatetime, workoutenddatetime): duplicate = False ws = Workout.objects.filter(user=r, date=workoutdate, duplicate=False).exclude( @@ -1244,6 +1495,84 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, # Fetch a subset of the data from the DB +def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, + debug=False, for_chart=False): + if ids: + csvfilenames = [ + 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] + else: + return pl.DataFrame() + + data = [] + columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid'] + columns = list(set(columns)) + + df = pl.DataFrame() + if len(ids) > 1: + for id, f in zip(ids, csvfilenames): + try: + df = pl.read_parquet(f, columns=columns) + data.append(df) + except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover + rowdata, row = getrowdata(id=id) + shutil.rmtree(f) + if rowdata and len(rowdata.df): + _ = dataprep(rowdata.df, id=id, + bands=True, otwpower=True, barchart=True, + polars=True) + try: + df = pl.read_parquet(f, columns=columns) + data.append(df) + except (OSError, ArrowInvalid, IndexError): + pass + try: + df = pl.concat(data, rechunk=True) + except ValueError: # pragma: no cover + return pl.DataFrame() + + else: + try: + df = pl.read_parquet(csvfilenames[0], columns=columns) + rowdata, row = getrowdata(id=ids[0]) + except (OSError, IndexError, ArrowInvalid): + rowdata, row = getrowdata(id=ids[0]) + if rowdata and len(rowdata.df): # pragma: no cover + data = dataprep( + rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) + try: + df = pd.read_parquet(csvfilenames[0], columns=columns) + except: + df = pd.DataFrame + else: + df = pd.DataFrame() + except: + rowdata, row = getrowdata(id=ids[0]) + if rowdata and len(rowdata.df): # pragma: no cover + data = dataprep( + rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) + try: + df = pd.read_parquet(csvfilenames[0], columns=columns) + except: + df = pd.DataFrame() + else: + df = pd.DataFrame() + + if compute and len(df): + data = df.clone() + if doclean: + data = clean_df_stats_pl(data, ignorehr=True, + workstrokesonly=workstrokesonly, + for_chart=for_chart) + + data = data.drop_nulls() + + return data + + + return df + + + def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, debug=False, for_chart=False): @@ -1593,7 +1922,7 @@ def add_efficiency(id=0): # pragma: no cover def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, - empower=True, inboard=0.88, forceunit='lbs', debug=False): + empower=True, inboard=0.88, forceunit='lbs', debug=False, polars=True): if rowdatadf.empty: return 0 @@ -1873,11 +2202,15 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) df = dd.from_pandas(data, npartitions=1) - try: - df.to_parquet(filename, engine='fastparquet', compression='GZIP') - except FileNotFoundError: - df2 = dd.from_pandas(df, npartitions=1) - df2.to_parquet(filename, engine='fastparquet', compression='GZIP') + if polars: + pldf = pl.from_pandas(data) + pldf.write_parquet(filename, compression='gzip') + else: + try: + df.to_parquet(filename, engine='fastparquet', compression='gzip') + except FileNotFoundError: + df2 = dd.from_pandas(df, npartitions=1) + df2.to_parquet(filename, engine='fastparquet', compression='gzip') return data diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4276704f..0178d52f 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -18,8 +18,10 @@ import rowers.metrics as metrics import rowers.dataprep as dataprep from rowers.dataprep import rdata import rowers.utils as utils +import polars as pl from rowers.rower_rules import ispromember +from polars.exceptions import ColumnNotFoundError from scipy.interpolate import griddata from scipy.signal import savgol_filter @@ -2306,29 +2308,30 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, columns = [name for name, d in metrics.rowingmetrics] columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] - columns = columns + ['spm', 'driveenergy', 'distance'] - columns_basic = columns_basic + ['spm', 'driveenergy', 'distance'] + columns = columns + ['spm', 'driveenergy', 'distance' ,'workoutstate'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance', 'workoutstate'] datadf = pd.DataFrame() if promember: - datadf = dataprep.getsmallrowdata_db(columns, ids=ids, doclean=True, + datadf = dataprep.getsmallrowdata_pl(columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) else: - datadf = dataprep.getsmallrowdata_db(columns_basic, ids=ids, doclean=True, + datadf = dataprep.getsmallrowdata_pl(columns_basic, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) try: _ = datadf[yparam2] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover yparam2 = 'None' try: _ = datadf[yparam1] - except KeyError: + except (KeyError, ColumnNotFoundError): yparam1 = 'None' - datadf.dropna(axis=1, how='all', inplace=True) - datadf.dropna(axis=0, how='any', inplace=True) + datadf.drop_nulls() + #datadf.dropna(axis=1, how='all', inplace=True) + #datadf.dropna(axis=0, how='any', inplace=True) # test if we have drive energy try: # pragma: no cover @@ -2347,42 +2350,43 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, yparamname2 = axlabels[yparam2] # check if dataframe not empty - if datadf.empty: # pragma: no cover + if datadf.is_empty(): # pragma: no cover return ['', '

    No non-zero data in selection

    ', '', ''] try: - datadf['x1'] = datadf.loc[:, xparam] + datadf = datadf.with_columns(pl.col(xparam).alias("x1")) except KeyError: # pragma: no cover try: - datadf['x1'] = datadf['distance'] + datadf = datadf.with_columns(pl.col("distance").alias("x1")) except KeyError: try: - datadf['x1'] = datadf['time'] + datadf = datadf.with_columns(pl.col('time').alias("x1")) except KeyError: # pragma: no cover return ['', '

    No non-zero data in selection

    ', '', ''] try: - datadf['y1'] = datadf.loc[:, yparam1] + datadf = datadf.with_columns(pl.col(yparam1).alias("y1")) except KeyError: try: - datadf['y1'] = datadf['pace'] + datadf = datadf.with_columns(pl.col('pace').alias("y1")) except KeyError: # pragma: no cover return ['', '

    No non-zero data in selection

    ', '', ''] if yparam2 != 'None': try: - datadf['y2'] = datadf.loc[:, yparam2] + datadf = datadf.with_columns(pl.col(yparam2).alias("y2")) except KeyError: # pragma: no cover - datadf['y2'] = datadf['y1'] + datadf = datadf.with_columns(pl.col("y1").alias("y2")) else: # pragma: no cover - datadf['y2'] = datadf['y1'] + datadf = datadf.with_columns(pl.col("y1").alias("y2")) - datadf['xname'] = axlabels[xparam] - datadf['yname1'] = axlabels[yparam1] + datadf = datadf.with_columns(xname = pl.lit(axlabels[xparam])) + datadf = datadf.with_columns(yname1 = pl.lit(axlabels[yparam1])) + if yparam2 != 'None': - datadf['yname2'] = axlabels[yparam2] + datadf = datadf.with_columns(yname2 = pl.lit(axlabels[yparam2])) else: # pragma: no cover - datadf['yname2'] = axlabels[yparam1] + datadf = datadf.with_columns(yname2 = pl.lit(axlabels[yparam1])) def func(x, a, b): return a*x+b @@ -2392,11 +2396,12 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, try: popt, pcov = optimize.curve_fit(func, x1, y1) ytrend = func(x1, popt[0], popt[1]) - datadf['ytrend'] = ytrend + datadf= datadf.with_columns(ytrend = ytrend) except TypeError: - datadf['ytrend'] = y1 + datadf = datadf.with_columns(ytrend = y1) - data_dict = datadf.to_dict("records") + + data_dict = datadf.to_dicts() metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 86e353ca..4ff7d4ca 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -296,7 +296,10 @@ def analysis_new(request, df = cpdata(tw, options) options['savedata'] = False request.session['options'] = options - response = HttpResponse(df.to_csv()) + try: + response = HttpResponse(df.to_csv()) + except AttributeError: + response = HttpResponse(df.write_csv()) code = str(uuid4()) filename = code+'.csv' chartform.fields['savedata'].initial = False @@ -568,7 +571,7 @@ def flexalldata(workouts, options): workstrokesonly = not includereststrokes columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] ids = [int(w.id) for w in workouts] - df = dataprep.getsmallrowdata_db(columns, ids=ids, + df = dataprep.getsmallrowdata_pl(columns, ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) From 69007e56be7fd6e6d6878e5fea8a79b375ce27e5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 8 Apr 2024 16:34:32 +0200 Subject: [PATCH 059/121] polars --- rowers/dataroutines.py | 7 +++++-- rowers/interactiveplots.py | 4 +++- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 822230d3..534e137a 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1515,7 +1515,10 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp data.append(df) except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) - shutil.rmtree(f) + try: + shutil.rmtree(f) + except: + pass if rowdata and len(rowdata.df): _ = dataprep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True, @@ -1564,7 +1567,7 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp workstrokesonly=workstrokesonly, for_chart=for_chart) - data = data.drop_nulls() + data = data.fill_nan(None).drop_nulls() return data diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 0178d52f..f916d7c9 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2312,6 +2312,7 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, columns_basic = columns_basic + ['spm', 'driveenergy', 'distance', 'workoutstate'] datadf = pd.DataFrame() + start = timezone.now() if promember: datadf = dataprep.getsmallrowdata_pl(columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) @@ -2319,6 +2320,7 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, datadf = dataprep.getsmallrowdata_pl(columns_basic, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) + print(timezone.now()-start) try: _ = datadf[yparam2] except (KeyError, ColumnNotFoundError): # pragma: no cover @@ -2415,7 +2417,7 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, 'trendline': trendline, } - script, div = get_chart("/dots", chart_data) + script, div = get_chart("/dots", chart_data, debug=False) return script, div diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3c5912231fea528068d9b2afd89b73de6d3b218f..e31e8ffccd1cf1fc926f11124b4ccdd456793ea8 100644 GIT binary patch literal 4000 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 Date: Mon, 8 Apr 2024 19:02:28 +0200 Subject: [PATCH 060/121] more pl --- rowers/dataroutines.py | 137 ++++++-------------------- rowers/interactiveplots.py | 8 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 3 files changed, 33 insertions(+), 112 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 534e137a..855d0831 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -773,55 +773,21 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, # clean data for useful ranges per column if not ignorehr: - try: - datadf = datadf.filter(pl.col("hr")>=30) - except (KeyError, TypeError): # pragma: no cover - pass + datadf = datadf.filter(pl.col("hr")>=30) - try: - datadf = datadf.filter(pl.col("spm") >=0) - except (KeyError, TypeError): - pass + + datadf = datadf.filter( + pl.col("spm") >=0, + pl.col("efficiency")<=200, + pl.col("spm")>=10, + pl.col("pace")<=300*1000., + pl.col("efficiency")>=0, + pl.col("pace")>=60*1000, + pl.col("power")<=5000, + pl.col("spm")<=120, + pl.col("wash")>=1 + ) - try: - datadf = datadf.filter(pl.col("efficiency")<=200) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("spm")>=10) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("pace")<=300*1000.) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("efficiency")>=0) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("pace")>=60*1000) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("power")<=5000) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("spm")<=120) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("wash")>=1) - except (KeyError, TypeError): - pass # try to guess ignoreadvanced if not ignoreadvanced: @@ -834,70 +800,25 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, pass if not ignoreadvanced: - try: - datadf = datadf.filter(pl.col("rhythm")>=0) - except (KeyError, TypeError): - pass + datadf = datadf.filter(pl.col("rhythm")>=0, + pl.col("rhythm")<=70, + pl.col("power")>=20, + pl.col("drivelength")>=0.5, + pl.col("forceratio")>=0.2, + pl.col("forceratio")<=1.0, + pl.col("drivespeed")>=0.5, + pl.col("drivespeed")<=4, + pl.col("driveenergy")<=2000, + pl.col("driveenergy")>=100, + pl.col("catch")<=-30) - try: - datadf = datadf.filter(pl.col("rhythm")<=70) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("power")>=20) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("drivelength")>=0.5) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("forceratio")>=0.2) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("forceratio")<=1.0) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("drivespeed")>=0.5) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("drivespeed")<=4) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("driveenergy")<=2000) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("driveenergy")>=100) - except (KeyError, TypeError): - pass - - try: - datadf = datadf.filter(pl.col("catch")<=-30) - except (KeyError, TypeError): - pass # workoutstateswork = [1, 4, 5, 8, 9, 6, 7] workoutstatesrest = [3] # workoutstatetransition = [0, 2, 10, 11, 12, 13] if workstrokesonly == 'True' or workstrokesonly is True: - try: - datadf = datadf.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) - except: - pass + datadf = datadf.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) after = {} @@ -1571,7 +1492,7 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp return data - + df = df.fill_nan(None).drop_nulls() return df @@ -2207,7 +2128,11 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, df = dd.from_pandas(data, npartitions=1) if polars: pldf = pl.from_pandas(data) - pldf.write_parquet(filename, compression='gzip') + try: + pldf.write_parquet(filename, compression='gzip') + except IsADirectoryError: + shutil.rmtree(filename) + pldf.write_parquet(filename, compression='gzip') else: try: df.to_parquet(filename, engine='fastparquet', compression='gzip') diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f916d7c9..bb5ce5b7 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -478,7 +478,7 @@ def interactive_forcecurve(theworkouts): rowdata.dropna(axis=0, how='any', inplace=True) if rowdata.empty: - return "", "No Valid Data Available", "", "" + return "", "No Valid Data Available" data_dict = rowdata.to_dict("records") @@ -2312,7 +2312,6 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, columns_basic = columns_basic + ['spm', 'driveenergy', 'distance', 'workoutstate'] datadf = pd.DataFrame() - start = timezone.now() if promember: datadf = dataprep.getsmallrowdata_pl(columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) @@ -2320,7 +2319,6 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, datadf = dataprep.getsmallrowdata_pl(columns_basic, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) - print(timezone.now()-start) try: _ = datadf[yparam2] except (KeyError, ColumnNotFoundError): # pragma: no cover @@ -2331,9 +2329,7 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, except (KeyError, ColumnNotFoundError): yparam1 = 'None' - datadf.drop_nulls() - #datadf.dropna(axis=1, how='all', inplace=True) - #datadf.dropna(axis=0, how='any', inplace=True) + datadf = datadf.fill_nan(None).drop_nulls() # test if we have drive energy try: # pragma: no cover diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e31e8ffccd1cf1fc926f11124b4ccdd456793ea8..b20a87644e3a3da95d688d2ee73932e04e6f81c4 100644 GIT binary patch delta 3644 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 From fd46732b6ef7b2bc735768718d461d1c162338ac Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 8 Apr 2024 20:50:08 +0200 Subject: [PATCH 061/121] histo converted to polars --- rowers/dataroutines.py | 96 +++++++++++++++++++++++++- rowers/interactiveplots.py | 40 ++++++----- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index b3e43767..2e6283da 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1414,6 +1414,37 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, return data, row +def getrowdata_pl(id=0, doclean=False, convertnewtons=True, + checkefficiency=True, for_chart=False): + data = read_df_sql(id,polars=True) + print(data) + try: + data = data.with_columns(pl.col(data['time'].diff()).alias("deltat")) # data['time'].diff() + except KeyError: # pragma: no cover + data = pl.DataFrame() + + if data.is_empty(): + rowdata, row = getrowdata(id=id) + if not rowdata.empty: # pragma: no cover + data = dataprep(rowdata.df, id=id, bands=True, + barchart=True, otwpower=True, polars=True) + else: + data = pl.DataFrame() # returning empty dataframe + else: + row = Workout.objects.get(id=id) + + if checkefficiency is True and not data.is_empty(): + try: + if data['efficiency'].mean() == 0 and data['power'].mean() != 0: # pragma: no cover + data = add_efficiency_pl(id=id, polars=True) + except KeyError: # pragma: no cover + data = add_efficiency_pl(id=id) + + if doclean: # pragma: no cover + data = clean_df_stats(data, ignorehr=True, for_chart=for_chart) + + return data, row + # Fetch a subset of the data from the DB def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, @@ -1707,7 +1738,28 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): # Read stroke data from the DB for a Workout ID. Returns a pandas dataframe -def read_df_sql(id): +def read_df_sql(id, polars=False): + if polars: + try: + f = 'media/strokedata_{id}.parquet.gz'.format(id=id) + df = pd.read_parquet(f) + except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover + rowdata, row = getrowdata(id=id) + try: + shutil.rmtree(f) + except: + pass + if rowdata and len(rowdata.df): + _ = dataprep(rowdata.df, id=id, + bands=True, otwpower=True, barchart=True, + polars=True) + try: + df = pl.read_parquet(f, columns=columns) + except (OSError, ArrowInvalid, IndexError): + pass + df = df.fill_nan(None).drop_nulls() + + return df try: f = 'media/strokedata_{id}.parquet.gz'.format(id=id) df = pd.read_parquet(f) @@ -1802,6 +1854,13 @@ def fix_newtons(id=0, limit=3000): # pragma: no cover pass +def remove_invalid_columns_pl(df): # pragma: no cover + for c in df.get_columns(): + if c not in allowedcolumns: + df = df.drop(c) + + return df + def remove_invalid_columns(df): # pragma: no cover for c in df.columns: if c not in allowedcolumns: @@ -1809,6 +1868,36 @@ def remove_invalid_columns(df): # pragma: no cover return df +def add_efficiency_pl(id=0): # pragma: no cover + rowdata, row = getrowdata_pl(id=id, + doclean=False, + convertnewtons=False, + checkefficiency=False) + power = rowdata['power'] + pace = rowdata['pace'] / 1.0e3 + velo = 500. / pace + ergpw = 2.8 * velo**3 + efficiency = 100. * ergpw / power + + efficiency = efficiency.replace([-np.inf, np.inf], np.nan) + efficiency.fillna(method='ffill') + rowdata = rowdata.with_columns(pl.col(efficiency).alias("efficiency")) # ['efficiency'] = efficiency + + rowdata = remove_invalid_columns_pl(rowdata) + rowdata = rowdata.replace([-np.inf, np.inf], np.nan) + rowdata = rowdata.fillna(method='ffill') + + delete_strokedata(id) + + + if id != 0: + rowdata = rowdata.with_column(pl.lit(id).alias("workoutid")) + filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) + rowdata.write_parquet(filename, compression='gzip') + + + return rowdata + def add_efficiency(id=0): # pragma: no cover rowdata, row = getrowdata_db(id=id, @@ -2144,7 +2233,10 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, os.remove(filename) df.to_parquet(filename, engine='fastparquet', compression='GZIP') - + if polars: + pldf = pl.from_pandas(data) + return pldf + return data diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index bb5ce5b7..22b4e606 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -469,18 +469,20 @@ def interactive_forcecurve(theworkouts): columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy', 'cumdist'] + 'workoutstate', 'driveenergy', 'cumdist', 'workoutid'] + columns = columns + [name for name, d in metrics.rowingmetrics] - rowdata = dataprep.getsmallrowdata_db(columns, ids=ids, + + rowdata = dataprep.getsmallrowdata_pl(columns, ids=ids, workstrokesonly=False) - rowdata.dropna(axis=1, how='all', inplace=True) - rowdata.dropna(axis=0, how='any', inplace=True) + rowdata = rowdata.fill_nan(None).drop_nulls() - if rowdata.empty: + + if rowdata.is_empty(): return "", "No Valid Data Available" - data_dict = rowdata.to_dict("records") + data_dict = rowdata.to_dicts() thresholdforce = 100. if 'x' in boattype else 200. @@ -490,7 +492,7 @@ def interactive_forcecurve(theworkouts): 'thresholdforce': thresholdforce, } - script, div = get_chart("/forcecurve", chart_data) + script, div = get_chart("/forcecurve", chart_data, debug=False) return script, div @@ -822,25 +824,27 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, ids = [int(w.id) for w in theworkouts] + columns = [name for name, d in metrics.rowingmetrics]+['spm', 'driveenergy', 'distance', 'workoutstate', 'workoutid'] + workstrokesonly = not includereststrokes - rowdata = dataprep.getsmallrowdata_db( - [histoparam], ids=ids, doclean=True, workstrokesonly=workstrokesonly) + rowdata = dataprep.getsmallrowdata_pl( + columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly) - rowdata.dropna(axis=0, how='any', inplace=True) + rowdata = rowdata.fill_nan(None).drop_nulls() - rowdata = dataprep.filter_df(rowdata, 'spm', spmmin, largerthan=True) - rowdata = dataprep.filter_df(rowdata, 'spm', spmmax, largerthan=False) + #rowdata = dataprep.filter_df(rowdata, 'spm', spmmin, largerthan=True) + #rowdata = dataprep.filter_df(rowdata, 'spm', spmmax, largerthan=False) - rowdata = dataprep.filter_df( - rowdata, 'driveenergy', workmin, largerthan=True) - rowdata = dataprep.filter_df( - rowdata, 'driveenergy', workmax, largerthan=False) + #rowdata = dataprep.filter_df( + # rowdata, 'driveenergy', workmin, largerthan=True) + #rowdata = dataprep.filter_df( + # rowdata, 'driveenergy', workmax, largerthan=False) - if rowdata.empty: + if rowdata.is_empty(): return "", "No Valid Data Available" try: - histopwr = rowdata[histoparam].values + histopwr = rowdata[histoparam].to_numpy() except KeyError: return "", "No data" if len(histopwr) == 0: # pragma: no cover diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index b20a87644e3a3da95d688d2ee73932e04e6f81c4..ec58e900f4ac114fc7a8c354fae66e67af9f19ea 100644 GIT binary patch delta 3644 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 delta 3643 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 Date: Mon, 8 Apr 2024 21:50:27 +0200 Subject: [PATCH 062/121] fixes after failing tests --- rowers/dataroutines.py | 18 ++++++++++-------- rowers/models.py | 7 +++++-- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4001 bytes 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 2e6283da..26a63f1d 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -796,7 +796,7 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, sum = datadf[metric].std() if sum == 0 or np.isnan(sum): ignoreadvanced = True - except KeyError: + except (KeyError, TypeError): pass if not ignoreadvanced: @@ -1495,22 +1495,22 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp data = dataprep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: - df = pd.read_parquet(csvfilenames[0], columns=columns) + df = pl.read_parquet(csvfilenames[0], columns=columns) except: - df = pd.DataFrame + df = pl.DataFrame else: - df = pd.DataFrame() + df = pl.DataFrame() except: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover data = dataprep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: - df = pd.read_parquet(csvfilenames[0], columns=columns) + df = pl.read_parquet(csvfilenames[0], columns=columns) except: - df = pd.DataFrame() + df = pl.DataFrame() else: - df = pd.DataFrame() + df = pl.DataFrame() if compute and len(df): data = df.clone() @@ -1523,7 +1523,9 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp return data - df = df.fill_nan(None).drop_nulls() + if not df.is_empty(): + df = df.fill_nan(None).drop_nulls() + return df diff --git a/rowers/models.py b/rowers/models.py index 483df158..a05de7c5 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3833,9 +3833,12 @@ def auto_delete_file_on_delete(sender, instance, **kwargs): # remove parquet file try: dirname = 'media/strokedata_{id}.parquet.gz'.format(id=instance.id) - shutil.rmtree(dirname) + os.remove(dirname) except FileNotFoundError: - pass + try: + shutil.rmtree(dirname) + except: + pass # remove parquet file try: diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index ec58e900f4ac114fc7a8c354fae66e67af9f19ea..d96a3d7749349eef1e033a4fa5f9d6018e1ea308 100644 GIT binary patch literal 4001 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 H#DD<+zT80F literal 4001 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 H#DD<+ZYoA& From 6d6acdbd1e1cf3ab4a8bb0e85468e179f9bf3385 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 9 Apr 2024 15:50:54 +0200 Subject: [PATCH 063/121] getting part --- rowers/interactiveplots.py | 41 ++++++++++++-------------- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4001 bytes rowers/views/analysisviews.py | 32 ++++++++++---------- rowers/views/statements.py | 2 +- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 22b4e606..541e46d8 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -192,11 +192,11 @@ def tailwind(bearing, vwind, winddir): def interactive_hr_piechart(df, rower, title, totalseconds=0): - if df.empty: + if df.is_empty(): return "", "Not enough data to make a chart" - df.sort_values(by='hr', inplace=True) - df['timehr'] = df['deltat']*df['hr'] + df = df.sort("hr") + df = df.with_columns((pl.col("deltat")*pl.col("hr")).alias("deltat")) sumtimehr = df['deltat'].sum() @@ -205,24 +205,23 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): hrzones = rower.hrzones - qry = 'hr < {ut2}'.format(ut2=rower.ut2) - qrydata = df.query(qry) + qrydata = df.filter(pl.col("hr") < rower.ut2) frac_lut2 = totalseconds*qrydata['deltat'].sum()/sumtimehr - qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1, ut2=rower.ut2) - frac_ut2 = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.ut2).filter(pl.col("hr") < rower.ut1) + frac_ut2 = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1, at=rower.at) - frac_ut1 = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.ut1).filter(pl.col("hr") < rower.at) + frac_ut1 = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{at} <= hr < {tr}'.format(at=rower.at, tr=rower.tr) - frac_at = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.at).filter(pl.col("hr") < rower.tr) + frac_at = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{tr} <= hr < {an}'.format(tr=rower.tr, an=rower.an) - frac_tr = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.tr).filter(pl.col("hr") < rower.an) + frac_tr = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = 'hr >= {an}'.format(an=rower.an) - frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.filter(pl.col("hr") >= rower.an) + frac_an = totalseconds*qrydata['deltat'].sum()/sumtimehr datadict = { '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, @@ -1931,7 +1930,6 @@ def interactive_streamchart(id=0, promember=0): return [script, div] def forcecurve_multi_interactive_chart(selected): # pragma: no cover - df_plot = pd.DataFrame() ids = [analysis.id for analysis in selected] workoutids = [analysis.workout.id for analysis in selected] @@ -1940,18 +1938,17 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', 'workoutstate', 'workoutid', 'driveenergy', 'cumdist'] + columns = columns + [name for name, d in metrics.rowingmetrics] - rowdata = dataprep.getsmallrowdata_db(columns, ids=workoutids, + rowdata = dataprep.getsmallrowdata_pl(columns, ids=workoutids, workstrokesonly=False) + rowdata = rowdata.fill_nan(None).drop_nulls() - rowdata.dropna(axis=1, how='all', inplace=True) - rowdata.dropna(axis=0, how='any', inplace=True) - - if rowdata.empty: + if rowdata.is_empty(): return "", "No Valid Data Available", "", "" - data_dict = rowdata.to_dict("records") + data_dict = rowdata.to_dicts() thresholdforces = [] for analysis in selected: diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index d96a3d7749349eef1e033a4fa5f9d6018e1ea308..db6565d3c122e8c9a176a06b2f7a51a8007b87cb 100644 GIT binary patch delta 3913 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 X- Date: Tue, 9 Apr 2024 17:01:47 +0200 Subject: [PATCH 064/121] add read_cols_pl --- rowers/dataroutines.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 26a63f1d..4943f296 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1659,6 +1659,44 @@ def prepmultipledata(ids, verbose=False): # pragma: no cover # Read a set of columns for a set of workout ids, returns data as a # pandas dataframe +def read_cols_pl(ids, columns): + extracols = [] + + + columns = list(columns) + ['distance', 'spm', 'workoutid'] + columns = [x for x in columns if x != 'None'] + columns = list(set(columns)) + ids = [int(id) for id in ids] + + df = pl.DataFrame() + + if len(ids) == 0: + return pl.DataFrame() + + df = getsmallrowdata_pl(columns, ids=ids, doclean=False, compute=False) + + if 'peakforce' in columns: + funits = ((w.id, w.forceunit) + for w in Workout.objects.filter(id__in=ids)) + for id, u in funits: + if u == 'lbs': + df = df.with_columns( + peakforce=pl.when(pl.col('workoutid')==id) + .then(pl.col('peakforce') * lbstoN) + .otherwise(pl.col('peakforce'))) + if 'averageforce' in columns: + funits = ((w.id, w.forceunit) + for w in Workout.objects.filter(id__in=ids)) + for id, u in funits: + if u == 'lbs': + df = df.with_columns( + averageforce=pl.when(pl.col('workoutid')==id) + .then(pl.col('averageforce') * lbstoN) + .otherwise(pl.col('averageforce'))) + + + return df, extracols + def read_cols_df_sql(ids, columns, convertnewtons=True): # drop columns that are not in offical list From 1a48e73a6f77142747a7823bdee9487a2b2fdcbb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Apr 2024 21:14:18 +0200 Subject: [PATCH 065/121] adding dataplep --- rowers/dataroutines.py | 185 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 4943f296..085c869a 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1417,7 +1417,6 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, def getrowdata_pl(id=0, doclean=False, convertnewtons=True, checkefficiency=True, for_chart=False): data = read_df_sql(id,polars=True) - print(data) try: data = data.with_columns(pl.col(data['time'].diff()).alias("deltat")) # data['time'].diff() except KeyError: # pragma: no cover @@ -1973,7 +1972,191 @@ def add_efficiency(id=0): # pragma: no cover # saves it to the stroke_data table in the database # Takes a rowingdata object's DataFrame as input +# polars +def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs'): + # rowdatadf is pd.DataFrame + if rowdatadf.empty: + return 0 + df = pl.from_pandas(rowdatadf) + + df = df.with_columns((pl.col("TimeStamp (sec)")-df[0, "TimeStamp (sec)"]).alias("TimeStamp (sec)")) + df = df.with_columns((pl.col(" Stroke500mPace (sec/500m)").clip(1,3000)).alias(" Stroke500mPace")) + if ' AverageBoatSpeed (m/s)' not in df.columns: + df = df.with_columns((500./pl.col(' Stroke500mPace (sec/500m)')).alias(' AverageBoatSpeed (m/s)')) + if ' WorkoutState' not in df.columns: + df = df.with_columns((pl.lit(0)).alias(" WorkoutState")) + df = df.with_columns((100.*pl.col(" DriveTime (ms)")/(pl.col(" DriveTime (ms)")+pl.col(" StrokeRecoveryTime (ms)"))).alias("rhythm")) + df = df.with_columns((pl.col(" AverageDriveForce (lbs)")/pl.col(" PeakDriveForce (lbs)")).alias("forceratio")) + f = df['TimeStamp (sec)'].diff().mean() + if f != 0 and not np.isinf(f): + try: + windowsize = 2 * (int(10. / (f))) + 1 + except ValueError: + windowsize = 1 + else: + windowsize = 1 + + if windowsize <= 3: + windowsize = 5 + + df.with_columns( + (pl.col(" Cadence (stokes/min)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" Cadence (stokes/min)")) + df.with_columns( + (pl.col(" DriveLength (meters)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" DriveLength (meters)")) + df.with_columns( + (pl.col(" HRCur (bpm)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" HRCur (bpm)")) + df.with_columns((pl.col("forceratio").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode()).alias("forceratio")) + + df = df.with_columns((pl.col(" DriveLength (meters)") / pl.col(" DriveTime (ms)") * 1.0e3).alias("drivespeed")) + + if 'driveenergy' not in df.columns: + if forceunit == 'lbs': + df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)") * lbstoN).alias("driveenergy")) + df = df.with_columns((pl.col(" AverageDriveForce (lbs)") * lbstoN).alias(" AverageDriveForce (lbs)")) + df = df.with_columns((pl.col(" PeakDriveForce (lbs)") * lbstoN).alias(" PeakDriveForce (lbs)")) + else: + df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)")).alias("driveenergy")) + + if df["driveenergy"].mean() == 0 and df["driveenergy"].std() == 0: + df = df.with_columns((0.0*pl.col("driveenergy")+100).alias("driveenergy")) + + df = df.with_columns((60. * pl.col(" AverageBoatSpeed (m/s)")/pl.col(" Cadence (stokes/min)")).alias("distanceperstroke")) + + t2 = df["TimeStamp (sec)"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) + p2 = df[" Stroke500mPace"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) + + data = pl.DataFrame( + dict( + time=df["TimeStamp (sec)"] * 1e3, + hr=df[" HRCur (bpm)"], + pace=df[" Stroke500mPace"] * 1e3, + spm=df[" Cadence (stokes/min)"], + velo=df[" AverageBoatSpeed (m/s)"], + cumdist=df["cum_dist"], + ftime=niceformat(t2), + fpace=nicepaceformat(p2), + driveenergy=df["driveenergy"], + power=df[' Power (watts)'], + workoutstate=df[" WorkoutState"], + averageforce=df[" AverageDriveForce (lbs)"], + drivelength=df[" DriveLength (meters)"], + peakforce=df[" PeakDriveForce (lbs)"], + forceratio=df["forceratio"], + distance=df["cum_dist"], + drivespeed=df["drivespeed"], + rhythm=df["rhythm"], + distanceperstroke=df["distanceperstroke"], + ) + ) + + data = data.with_columns( + hr_ut2 = df['hr_ut2'], + hr_ut1 = df['hr_ut1'], + hr_at = df['hr_at'], + hr_tr = df['hr_tr'], + hr_an = df['hr_an'], + hr_max = df['hr_max'], + hr_bottom = 0.0*df[' HRCur (bpm)'], + ) + + if 'wash' not in df.columns: + data = data.with_columns( + wash = pl.lit("0"), + catch = pl.lit("0"), + peakforceangle = pl.lit("0"), + finish = pl.lit("0"), + slip = pl.lit("0"), + totalangle = pl.lit("0"), + effectiveangle = pl.lit("0"), + efficiency = pl.lit("0"), + ) + else: + wash = df['wash'] + catch = df['catch'] + finish = df['finish'] + peakforceangle = df['peakforceangle'] + arclength = (inboard - 0.05) * (np.radians(finish) - np.radians(catch)) + if arclength.mean() > 0: + drivelength = arclength + + slip = df['slip'] + totalangle = finish - catch + effectiveangle = finish - wash - catch - slip + + if windowsize > 3 and windowsize < len(slip): + try: + wash = savgol_filter(wash, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + slip = savgol_filter(slip, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + catch = savgol_filter(catch, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + finish = savgol_filter(finish, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + peakforceangle = savgol_filter(peakforceangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + driveenergy = savgol_filter(driveenergy, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + drivelength = savgol_filter(drivelength, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + totalangle = savgol_filter(totalangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + effectiveangle = savgol_filter(effectiveangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + + data = data.with_columns( + wash = wash, + catch = catch, + slip = slip, + finish = finish, + peakforceangle = peakforceangle, + driveenergy = driveenergy, + drivelength = drivelength, + totalangle = totalangle, + effectiveangle = effectiveangle, + ) + + ergpw = 2.8*data['velo']**3 + efficiency = 100. * ergpw / data['power'] + + data = data.with_columns(efficiency=efficiency) + + if id != 0: + data = data.with_columns( + workoutid = pl.lit(id) + ) + filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) + try: + data.write_parquet(filename, compression='gzip') + except IsADirectoryError: + shutil.rmtree(filename) + data.write_parquet(filename, compression='gzip') + + + return data + +# pandas/a little polars def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, empower=True, inboard=0.88, forceunit='lbs', debug=False, polars=True): From 2a9beb7a25052520f67d2ce627d823fdd21183c3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Apr 2024 21:37:31 +0200 Subject: [PATCH 066/121] dataprep -> plep --- rowers/dataprep.py | 4 +-- rowers/dataroutines.py | 45 +++++++++++++------------- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4002 bytes rowers/views/apiviews.py | 4 +-- rowers/views/workoutviews.py | 4 +-- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index cca3b186..018dc8ae 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -392,7 +392,7 @@ def resample(id, r, parent, overwrite='copy'): row.write_csv(parent.csvfilename, gzip=True) - _ = dataprep(row.df, id=parent.id, bands=True, barchart=True, + _ = dataplep(row.df, id=parent.id, bands=True, barchart=True, otwpower=True, empower=True, inboard=parent.inboard) isbreakthrough, ishard = checkbreakthrough(parent, r) _ = check_marker(parent) @@ -1397,7 +1397,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', w.team.add(t) # put stroke data in database - _ = dataprep(row.df, id=w.id, bands=True, + _ = dataplep(row.df, id=w.id, bands=True, barchart=True, otwpower=True, empower=True, inboard=inboard) isbreakthrough, ishard = checkbreakthrough(w, r) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 085c869a..38f2ccce 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1367,7 +1367,7 @@ def delete_strokedata(id, debug=False): def update_strokedata(id, df, debug=False): delete_strokedata(id, debug=debug) - _ = dataprep(df, id=id, bands=True, barchart=True, otwpower=True) + _ = dataplep(df, id=id, bands=True, barchart=True, otwpower=True) # Test that all data are of a numerical time @@ -1395,7 +1395,7 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, if data.empty: rowdata, row = getrowdata(id=id) if not rowdata.empty: # pragma: no cover - data = dataprep(rowdata.df, id=id, bands=True, + data = dataplep(rowdata.df, id=id, bands=True, barchart=True, otwpower=True) else: data = pd.DataFrame() # returning empty dataframe @@ -1425,7 +1425,7 @@ def getrowdata_pl(id=0, doclean=False, convertnewtons=True, if data.is_empty(): rowdata, row = getrowdata(id=id) if not rowdata.empty: # pragma: no cover - data = dataprep(rowdata.df, id=id, bands=True, + data = dataplep(rowdata.df, id=id, bands=True, barchart=True, otwpower=True, polars=True) else: data = pl.DataFrame() # returning empty dataframe @@ -1471,7 +1471,7 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp except: pass if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True, polars=True) try: @@ -1491,7 +1491,7 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp except (OSError, IndexError, ArrowInvalid): rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pl.read_parquet(csvfilenames[0], columns=columns) @@ -1502,7 +1502,7 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp except: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pl.read_parquet(csvfilenames[0], columns=columns) @@ -1554,7 +1554,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except (OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(f, columns=columns) @@ -1573,7 +1573,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except (OSError, IndexError, ArrowInvalid): rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(csvfilenames[0], columns=columns) @@ -1584,7 +1584,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(csvfilenames[0], columns=columns) @@ -1651,7 +1651,7 @@ def prepmultipledata(ids, verbose=False): # pragma: no cover if verbose: print(id) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, bands=True, + _ = dataplep(rowdata.df, id=id, bands=True, barchart=True, otwpower=True) return ids @@ -1722,7 +1722,7 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): except OSError: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, + _ = dataplep(rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) pq_file = pq.ParquetDataset(filename) columns_in_file = [c for c in columns if c in pq_file.schema.names] @@ -1740,7 +1740,7 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): except (OSError, IndexError, ArrowInvalid): rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): # pragma: no cover - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) pq_file = pq.ParquetDataset(f) columns_in_file = [c for c in columns if c in pq_file.schema.names] @@ -1789,7 +1789,7 @@ def read_df_sql(id, polars=False): except: pass if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True, polars=True) try: @@ -1805,7 +1805,7 @@ def read_df_sql(id, polars=False): except (OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): - data = dataprep(rowdata.df, id=id, bands=True, + data = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(f) @@ -1973,7 +1973,9 @@ def add_efficiency(id=0): # pragma: no cover # Takes a rowingdata object's DataFrame as input # polars -def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs'): +def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchart=True, otwpower=True, + empower=True, debug=False, polars=True + ): # rowdatadf is pd.DataFrame if rowdatadf.empty: return 0 @@ -2082,6 +2084,8 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs'): arclength = (inboard - 0.05) * (np.radians(finish) - np.radians(catch)) if arclength.mean() > 0: drivelength = arclength + else: + drivelength = data['drivelength'] slip = df['slip'] totalangle = finish - catch @@ -2108,10 +2112,6 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs'): peakforceangle = savgol_filter(peakforceangle, windowsize, 3) except TypeError: # pragma: no cover pass - try: - driveenergy = savgol_filter(driveenergy, windowsize, 3) - except TypeError: # pragma: no cover - pass try: drivelength = savgol_filter(drivelength, windowsize, 3) except TypeError: # pragma: no cover @@ -2131,7 +2131,6 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs'): slip = slip, finish = finish, peakforceangle = peakforceangle, - driveenergy = driveenergy, drivelength = drivelength, totalangle = totalangle, effectiveangle = effectiveangle, @@ -2598,7 +2597,7 @@ def add_c2_stroke_data_db(strokedata, workoutid, starttimeunix, csvfilename, compression='gzip') - data = dataprep(df, id=workoutid, bands=False, debug=debug) + data = dataplep(df, id=workoutid, bands=False, debug=debug) return data @@ -2666,7 +2665,7 @@ def create_c2_stroke_data_db( _ = df.to_csv(csvfilename, index_label='index', compression='gzip') - data = dataprep(df, id=workoutid, bands=False, debug=debug) + data = dataplep(df, id=workoutid, bands=False, debug=debug) return data @@ -2705,7 +2704,7 @@ def update_empower(id, inboard, oarlength, boattype, df, f1, debug=False): # pr if debug: # pragma: no cover print("not updated ", id) - _ = dataprep(df, id=id, bands=True, barchart=True, otwpower=True, debug=debug) + _ = dataplep(df, id=id, bands=True, barchart=True, otwpower=True, debug=debug) row = rrdata(df=df) row.write_csv(f1, gzip=True) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index db6565d3c122e8c9a176a06b2f7a51a8007b87cb..62b2f5931143a4b2872cff35338cfaf73f70a23d 100644 GIT binary patch literal 4002 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 H#DD<+aDYFZ diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 43f8d257..1f2f87bb 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -743,7 +743,7 @@ def strokedatajson_v2(request, id): powerperc=powerperc, powerzones=r.powerzones) rowdata = rdata(csvfile=row.csvfilename, rower=rr).df - datadf = dataprep.dataprep( + datadf = dataprep.dataplep( rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True) _ = myqueue(queuehigh, handle_calctrimp, row.id, @@ -910,7 +910,7 @@ def strokedatajson(request, id=0): powerperc=powerperc, powerzones=r.powerzones) rowdata = rdata(csvfile=row.csvfilename, rower=rr).df - datadf = dataprep.dataprep( + datadf = dataprep.dataplep( rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True) # mangling diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 2d871868..e7a951ee 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1293,7 +1293,7 @@ def remove_power_view(request, id=0): row = rdata(csvfile=f, rower=rr) row.df[' Power (watts)'] = 0 row.write_csv(f) - _ = dataprep.dataprep(row.df, id=workout.id) + _ = dataprep.dataplep(row.df, id=workout.id) cpdf, delta, cpvalues = dataprep.setcp(workout) workout.normp = 0 @@ -3599,7 +3599,7 @@ def workout_erase_column_view(request, id=0, column=''): row.write_csv(w.csvfilename, gzip=True) row, workout = dataprep.getrowdata(id=w.id) - _ = dataprep.dataprep(row.df, id=w.id) + _ = dataprep.dataplep(row.df, id=w.id) if column == 'hr': w.hrtss = 0 From eccb20b82fb9d4415fd6507d077363c328c3a683 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Apr 2024 22:17:07 +0200 Subject: [PATCH 067/121] fixed dataplep --- rowers/dataroutines.py | 33 ++++++++++++++++---------- rowers/tests/testdata/testdata.tcx.gz | Bin 4002 -> 4002 bytes 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 38f2ccce..aa562ba2 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -569,6 +569,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, except KeyError: pass + if not ignoreadvanced: try: mask = datadf['rhythm'] < 0 @@ -1418,7 +1419,7 @@ def getrowdata_pl(id=0, doclean=False, convertnewtons=True, checkefficiency=True, for_chart=False): data = read_df_sql(id,polars=True) try: - data = data.with_columns(pl.col(data['time'].diff()).alias("deltat")) # data['time'].diff() + data = data.with_columns((pl.col('time').diff()).alias("deltat")) # data['time'].diff() except KeyError: # pragma: no cover data = pl.DataFrame() @@ -1781,7 +1782,7 @@ def read_df_sql(id, polars=False): if polars: try: f = 'media/strokedata_{id}.parquet.gz'.format(id=id) - df = pd.read_parquet(f) + df = pl.read_parquet(f) except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) try: @@ -2018,11 +2019,15 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar if 'driveenergy' not in df.columns: if forceunit == 'lbs': df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)") * lbstoN).alias("driveenergy")) - df = df.with_columns((pl.col(" AverageDriveForce (lbs)") * lbstoN).alias(" AverageDriveForce (lbs)")) - df = df.with_columns((pl.col(" PeakDriveForce (lbs)") * lbstoN).alias(" PeakDriveForce (lbs)")) else: df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)")).alias("driveenergy")) + + if forceunit == 'lbs': + df = df.with_columns((pl.col(" AverageDriveForce (lbs)") * lbstoN).alias(" AverageDriveForce (lbs)")) + df = df.with_columns((pl.col(" PeakDriveForce (lbs)") * lbstoN).alias(" PeakDriveForce (lbs)")) + + if df["driveenergy"].mean() == 0 and df["driveenergy"].std() == 0: df = df.with_columns((0.0*pl.col("driveenergy")+100).alias("driveenergy")) @@ -2067,14 +2072,14 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar if 'wash' not in df.columns: data = data.with_columns( - wash = pl.lit("0"), - catch = pl.lit("0"), - peakforceangle = pl.lit("0"), - finish = pl.lit("0"), - slip = pl.lit("0"), - totalangle = pl.lit("0"), - effectiveangle = pl.lit("0"), - efficiency = pl.lit("0"), + wash = pl.lit(0.0), + catch = pl.lit(0.0), + peakforceangle = pl.lit(0.0), + finish = pl.lit(0.0), + slip = pl.lit(0.0), + totalangle = pl.lit(0.0), + effectiveangle = pl.lit(0.0), + efficiency = pl.lit(0.0), ) else: wash = df['wash'] @@ -2145,6 +2150,10 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar data = data.with_columns( workoutid = pl.lit(id) ) + # cast data + for k, v in dtypes.items(): + if v == 'int': + data = data.cast({k: pl.Int64}) filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) try: data.write_parquet(filename, compression='gzip') diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 62b2f5931143a4b2872cff35338cfaf73f70a23d..4623d9967757276973f7be061272a75a8eb56885 100644 GIT binary patch delta 3803 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 delta 3803 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 RrAwbY`XAY=VbH{Y0RXz*z1{!- From ef8054b5a6b4f89f2feb280e9bf1c6afa4293af1 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Apr 2024 22:34:53 +0200 Subject: [PATCH 068/121] fix --- rowers/dataroutines.py | 15 ++++++++++++++- rowers/tests/testdata/testdata.tcx.gz | Bin 4002 -> 4001 bytes 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index aa562ba2..69658d1f 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1981,7 +1981,20 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar if rowdatadf.empty: return 0 - df = pl.from_pandas(rowdatadf) + try: + df = pl.from_pandas(rowdatadf) + except ArrowInvalid: + for k, v in dtypes.items(): + try: + rowdatadf[k] = rowdatadf[k].astype(v) + except KeyError: # pragma: no cover + pass + try: + df = pl.from_pandas(rowdatadf) + except ArrowInvalid: + return dataprep(rowdatadf, id=id, inboard=inboard, forceunit=forceunit, bands=bands, barchart=barchart, + otwpower=otwpower, debug=debug,polars=True) + df = df.with_columns((pl.col("TimeStamp (sec)")-df[0, "TimeStamp (sec)"]).alias("TimeStamp (sec)")) df = df.with_columns((pl.col(" Stroke500mPace (sec/500m)").clip(1,3000)).alias(" Stroke500mPace")) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 4623d9967757276973f7be061272a75a8eb56885..e9790191254e672f9f8b99df4a26d3f4a98735fc 100644 GIT binary patch literal 4001 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 H#DD<+uIcmVC4TW=dT7J%RLD-1uh4~tUg!o!Q} zx@eOwHbBxX+MwIF#ne_E?b@;-O)mZSOG>HZBp$TK-XS;x<{_{~bJKit$aClM*KaQ` z4qh)e+tt{c`i9>$_jO?eZY)yxx9w__FV>j~0tJ zZ{C>mZgaU>o71byMf!4k`f_>MZ5MYRzIaWC`%@fsZ~wwszB%nzuUGx=-@WaZ>wJT^ zPVjcS`rr(2*j#PS7X*NdU!FhvX?Kb*R_krwtxuQfsO{=#f8J+Tr(M7MwfDM#1N~92 z$-Ld3{rQ1^@A}8x#nooD+%8_@pYQ)$*Prh9U*22_|JtPn0p0h|{ck)xd311a{O$py zpOHVl-|+jvPuEwQ{;R`(_Ai&4!~K`(>!)WY1n9m1`GVjD@{z?Oawa|gF1vpF!*cU{wSLtv|J~pE5$QBPcNedgCuq|}_J6*=!s3og-1_=?_jYyp>hk|| z1LE$ufjcg7>+7fM-K*OFezJQjmWk}mmfH{VY?&VL31NPW`@e5d+^o*e)2%K0mse*e z&%5=j?jlkAVC(I@yVL)EaQC|{*H^1`e|vNOBE18D;a%MA_TiBqy%J*V@N2R=;DH2g zQy~8dg*}P85q$7~;g09K+u>&P+J17`7eD;+muG@I$MBEOn9ljHuJ4oB#N);OuaB>} zNEiM1=iPma`9-)3ua-K4NzyJ>K^@uPG_dCfT@iP36>(1x5nNDkr=&L(SISAon;M?#@=kJtld#;x*i3NqJ|O4R>D=cT2l*-YK}-V5lH>d;oW> zihF>HxJ%-mgi52$d*dd;y)JnVToHF~f(NJ3=bbBQ#xc!@yRV14fbjvXM&7*`nF#mF z#`%iuIAIgPMh*8MCGGb)$$O}XyE6_5GqWb)N*(XTU1S2bhe^*EY{r6s5Jn`1C5O?D+`BHw|y<^Sq!*BCoRN8K7vy zW=5lZO)@Xmq-P7HA&)p2-M&R>hsitR1%oC^%Y;Sk`8koV$ezy@8e7#o&x|Hcggmx+ z{%-P|b8i`tG@hLFqLj?Kwyyc@MqmXw7BZ^Aqqb=&aZ8AR&`CaaG#tO4)o_9rSX=g=V zDk2|D;;zcTv}G$Q1)9u{LEfTC8mA*w%hRI0Ze>PQ)M6_l&&E4ll-9;rGACzFg`w&8)0cU%CM4ZnCG)my z)^%*Pw4;#sMpzfI5h+W)`S3;{Zxd{T-m0>$#WnKxC2uPtPbPR>b(MIWOnkn{{4jYj zmQYn)C82CxUF(544*6gRQPnv?V%ZuS-*o#i$Rn7D#2W9d18ljs)0cTj{Ubc989`#F)XX|#_95Q8Q@@0f~KQTrC3AA@|(y$u*NeQ%wmqPe$o zB43d`Pu7q_R?YK5lgYj1RzE*T-W#+?s?pw2QG0$yy+8K}+10bp{=Pq)K(oTQ0XKzGwoe(`V^7kcQk#29nuosml$D=K& z;XNo1uW5M0LD+fYiU($T6ig1KR+j#uSvrbHeLudK0NPSNx%K9=J|^3`6Ry_NmXOx zy=xVf_at9YlvbhvCQyy`!A(ZDZ{FJh@^S=dywZo~W6@Q}XC?De5qWDYh)U)I6kUsY zRx7r4B=Xi!`lljKMc1*(^`|W}V+8U(=i#aP;YBXGj*ZwEbw90%d^EvfRLOkd zY%JUQ-i|@ul5wJ|u8M`TvF5ZcZADSqB)`H+-QKyPR^6P)SLDgD!B8a7`I}{LSK<9 zM`BL6hoC}F(9qwr|7kVjy~~|%UDS>D2#iHj?jM34Dnl<7p_kk+1#|=N4HW zRp^78jMP_#o?SiYlbvP*fmNZ8zNnfvrvV=;LeFWgCs57x2w^hlYa8%ul6q$tkW_O$ zV%c(=Iicr@(8sLdN1fD{UA3^G9|wKzgz8w8nT2V_Y8w6w&`TchRUunMp`y|HIjMOS z8Fw5Ec?>EIA6@h2-PgDa^`K89js z(ffp6*F+;{a}`C;4gDzSqls4O(jjm~jXP@IyfM%xWj&&<>;@@2{+rZK9=#anwdkpA ztF$>uJye7~7!gu6z(z_Ip>OD??b7kNGp2PSD&vllf*=!VpMz~#`a$&8u-CQE$qQpl zrgKuW>xa>MLqURW!1qwJupXP5HwJo40<0{!Lkc*Vj=0#;G6&JKL8t4Qg%pb}Tso@( zpDIG{3|P`-!HH)!dDNK4(}8>gwxR%C=LXrsj=;o{a!Toz%N9 znE@YLg4Q5{M z@U0jSW#N@Ym`uFBwq)#zg0?nkY)CPx&=a+}e){MIOjIshjFzcr z%ad8&tT}Rr(eGL&dX<`I#Wt7D^c}gomWk+fbG?(vBxtp?%mMUa*D?`wOWvUM(u}*Q z+w~#0Otd1Xa^%|3s&DR#K2(H0~Xc@UvFT4u5Wg@0+I`jc2(@>?Uc?0NUZkY&(QD@gjYH67>K+h(C(wpa@ z>~hA?dg+XU-evSrW!HP;qBVEWjJr|LN8?eMhwrIu2?n&F?HK5hazhoR)N|Pq3}`{y zQP4|ns6slaw^B562Tjcz1${6q7<5uEu4t-8Q}aeaPsz9=WorfRTyx}3-K7(A%S6zw zY3se8%v?_`Xgi2L7g;l?!Z$wnX4lUEeKeA+JDt==KN-88nwmF+p2>){y6{ba(Cqpd zpqE^D1v>Ot6n$PQs15idpbrKZb^Y)|PTrpgdTQRhA@q4AYrvpG&v7#7+xqpx=-C7z zUF0skC?z%g+=-&09|65Ho^4dQbb`Z@E}dDSmx|EGyrwPaq~2rE0pGV0j4{v)8D}x5 zvUEZyT50LPa$iY|6GD-FMvEFFQ} zO4fj0<;aaFlX2-l>zg--ez%gfZk@YGWrMcR=K4|4`&@maM9+RQt6R3Yeh__L$(oT= z-n?ji(drv&ee*^^&$~s;RmNSE*jBfkxHm7Oce(INiQdJcQr{Y-j)Okt!YiF!9|N@J zX%qM6CG@+Mtd)_w7_oK6o&kDm(oC;f0EGZrkKFsB_hX@dxAl(=$BSp(_05OxulnWo zJldb>kKdR--+Wr_ zzdwGwNgS^>@Amz^OK;ES>dm%WpLG`(yQh^tuGZ&gUEihicKf$0QF(vd@i(vfmsgux zugN#p*B7hPu1`Px=Eb-<_a`Sy-+lXPb#Zp{bAL|nV> zA-%=lzq Date: Sat, 13 Apr 2024 09:39:16 +0200 Subject: [PATCH 069/121] fix --- rowers/dataroutines.py | 4 ++++ rowers/interactiveplots.py | 2 ++ rowers/views/analysisviews.py | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 69658d1f..52a52cba 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -32,6 +32,7 @@ import zipfile import os from rowers.models import strokedatafields import polars as pl +from polars.exceptions import ColumnNotFoundError, SchemaError from rowingdata import ( KinoMapParser, @@ -1484,6 +1485,9 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp df = pl.concat(data, rechunk=True) except ValueError: # pragma: no cover return pl.DataFrame() + except SchemaError: + df = pl.concat(data, rechunk=True, how='vertical_relaxed') + else: try: diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 541e46d8..856154b3 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -199,6 +199,8 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): df = df.with_columns((pl.col("deltat")*pl.col("hr")).alias("deltat")) sumtimehr = df['deltat'].sum() + if sumtimehr == 0: + return "", "No HR data" if totalseconds == 0: totalseconds = sumtimehr diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 0dd4334d..7008586d 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -2386,7 +2386,7 @@ def history_view_data(request, userid=0): df = getsmallrowdata_pl(columns, ids=ids) try: - df = df.with_columns(pl.col('time').diff().clip(lower_bound=0).alias("deltat")) + df = df.with_columns(pl.col('time').diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover pass From cc611f049bb278eeecfa9bc8bb7be9f8d77737b3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 10:31:29 +0200 Subject: [PATCH 070/121] using rowingdata_pl, requires rowingdata > 3.6.6 --- rowers/dataprep.py | 8 +++++++ rowers/dataroutines.py | 33 ++++++++++++++++---------- rowers/templates/menu_workout.html | 25 ------------------- rowers/tests/test_unit_tests.py | 3 ++- rowers/tests/test_urls.py | 3 --- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4001 bytes 6 files changed, 30 insertions(+), 42 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 018dc8ae..e73bc863 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -73,6 +73,7 @@ import pytz import collections import pendulum from rowingdata import rowingdata as rrdata +from rowingdata import rowingdata_pl as rrdata_pl from rowingdata import rower as rrower @@ -618,6 +619,8 @@ def update_wps(r, types, mode='water', asynchron=True): r.save() except ValueError: # pragma: no cover pass + except OverflowError: + pass return True @@ -1397,6 +1400,11 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', w.team.add(t) # put stroke data in database + try: + row = rrdata_pl(df=pl.from_pandas(row.df)) + except: + pass + _ = dataplep(row.df, id=w.id, bands=True, barchart=True, otwpower=True, empower=True, inboard=inboard) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 52a52cba..f545f843 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1982,24 +1982,31 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar empower=True, debug=False, polars=True ): # rowdatadf is pd.DataFrame - if rowdatadf.empty: - return 0 + + + if isinstance(rowdatadf, pd.DataFrame): + if rowdatadf.empty: + return 0 - try: - df = pl.from_pandas(rowdatadf) - except ArrowInvalid: - for k, v in dtypes.items(): - try: - rowdatadf[k] = rowdatadf[k].astype(v) - except KeyError: # pragma: no cover - pass try: df = pl.from_pandas(rowdatadf) except ArrowInvalid: - return dataprep(rowdatadf, id=id, inboard=inboard, forceunit=forceunit, bands=bands, barchart=barchart, - otwpower=otwpower, debug=debug,polars=True) - + for k, v in dtypes.items(): + try: + rowdatadf[k] = rowdatadf[k].astype(v) + except KeyError: # pragma: no cover + pass + try: + df = pl.from_pandas(rowdatadf) + except ArrowInvalid: + return dataprep(rowdatadf, id=id, inboard=inboard, forceunit=forceunit, bands=bands, barchart=barchart, + otwpower=otwpower, debug=debug,polars=True) + else: + df = rowdatadf + if df.is_empty(): + return 0 + df = df.with_columns((pl.col("TimeStamp (sec)")-df[0, "TimeStamp (sec)"]).alias("TimeStamp (sec)")) df = df.with_columns((pl.col(" Stroke500mPace (sec/500m)").clip(1,3000)).alias(" Stroke500mPace")) if ' AverageBoatSpeed (m/s)' not in df.columns: diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index 49c9f361..f61797f6 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -289,31 +289,6 @@
      {% if workout|water %} -
    • - -  Wind - -
    • -
    • - -  Stream - -
    • -
    • - -  Corrected Pace Plot - -
    • -
    • - -  OTW Power - -
    • -
    • - -  Remove Power Data - -
    • {% if 'speedcoach2' in workout.workoutsource or 'nklinklogbook' in workout.workoutsource %}
    • diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 195b9ec1..467267e9 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -13,6 +13,7 @@ from django.db import transaction nu = datetime.datetime.now() import datetime import pytz +import polars as pl # interactive plots from rowers import interactiveplots @@ -659,7 +660,7 @@ class InteractivePlotTests(TestCase): pass def test_interactive_hr_piechart(self): - df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv') + df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv') script, div = interactiveplots.interactive_hr_piechart(df, self.r,'') self.assertFalse(len(script)==0) diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index b7d417b9..af6e07e1 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -157,17 +157,14 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/instroke/', '/rowers/workout/'+encoded1+'/interactiveotwplot/', '/rowers/workout/'+encoded1+'/map/', - '/rowers/workout/'+encoded1+'/otwsetpower/', '/rowers/workout/'+encoded1+'/recalcsummary/', '/rowers/workout/'+encoded1+'/restore/', '/rowers/workout/'+encoded1+'/smoothenpace/', '/rowers/workout/'+encoded1+'/split/', '/rowers/workout/'+encoded1+'/stats/', - '/rowers/workout/'+encoded1+'/stream/', '/rowers/workout/'+encoded1+'/undosmoothenpace/', '/rowers/workout/'+encoded1+'/unsubscribe/', '/rowers/workout/'+encoded1+'/view/', - '/rowers/workout/'+encoded1+'/wind/', '/rowers/workout/'+encoded1+'/workflow/', '/rowers/workout/fusion/'+encoded1+'/', '/rowers/workout/upload/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e9790191254e672f9f8b99df4a26d3f4a98735fc..2f3d726fb35072c64ab94b513e74853cf9d6ccfb 100644 GIT binary patch literal 4001 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 H#DD<+;JiP3 literal 4001 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 H#DD<+ Date: Sat, 13 Apr 2024 10:41:22 +0200 Subject: [PATCH 071/121] verbeteringen --- rowers/interactiveplots.py | 104 +------ rowers/tests/test_urls.py | 1 - rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes rowers/urls.py | 10 - rowers/views/workoutviews.py | 393 -------------------------- 5 files changed, 2 insertions(+), 506 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 856154b3..8777367b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -477,12 +477,11 @@ def interactive_forcecurve(theworkouts): rowdata = dataprep.getsmallrowdata_pl(columns, ids=ids, workstrokesonly=False) - rowdata = rowdata.fill_nan(None).drop_nulls() - - if rowdata.is_empty(): return "", "No Valid Data Available" + rowdata = rowdata.fill_nan(None).drop_nulls() + data_dict = rowdata.to_dicts() thresholdforce = 100. if 'x' in boattype else 200. @@ -3131,105 +3130,6 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', return [script, div, message, errormessage] -def interactive_otw_advanced_pace_chart(id=0, promember=0): - # check if valid ID exists (workout exists) - rowdata, row = dataprep.getrowdata_db(id=id) - rowdata.dropna(axis=1, how='all', inplace=True) - rowdata.dropna(axis=0, how='any', inplace=True) - - if rowdata.empty: - return "", "No Valid Data Available" - - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - source = ColumnDataSource( - rowdata - ) - - plot = figure(x_axis_type="datetime", y_axis_type="datetime", - tools=TOOLS, - width=920, - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'scale_both' - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - #plot.title.text_font_size = value("1.2em") - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - ymax = 1.0e3*90 - ymin = 1.0e3*210 - - plot.y_range = Range1d(ymin, ymax) - - hover = plot.select(dict(type=HoverTool)) - - plot.line('time', 'pace', source=source, - legend_label="Pace", color="black") - plot.line('time', 'nowindpace', source=source, - legend_label="Corrected Pace", color="red") - - hover.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Pace', '@fpace'), - ('Corrected Pace', '@fnowindpace'), - ('HR', '@hr{int}'), - ('SPM', '@spm{1.1}'), - ]) - - hover.mode = 'mouse' - - try: - script, div = components(plot) - except: # pragma: no cover - script = '' - div = '' - - return [script, div] - - def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index af6e07e1..4e1f0f4f 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -146,7 +146,6 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/addtimeplot/', '/rowers/workout/'+encoded1+'/addtimeplot2/', '/rowers/workout/'+encoded1+'/comment/', - '/rowers/workout/'+encoded1+'/darkskywind/', '/rowers/workout/'+encoded1+'/data/', '/rowers/workout/'+encoded1+'/edit/', '/rowers/workout/'+encoded1+'/editintervals/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 2f3d726fb35072c64ab94b513e74853cf9d6ccfb..e3b8adc00f8289642ff85c1a3d6305d33a81fa55 100644 GIT binary patch delta 3773 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 delta 3733 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 diff --git a/rowers/urls.py b/rowers/urls.py index ca762683..0135688a 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -510,14 +510,6 @@ urlpatterns = [ name='workout_erase_column_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower-confirm/$', views.remove_power_confirm_view, name='remove_power_confirm_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower/$', views.remove_power_view, - name='remove_power_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/otwsetpower/$', - views.workout_otwsetpower_view, name='workout_otwsetpower_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/interactiveotwplot/$', - views.workout_otwpowerplot_view, name='workout_otwpowerplot_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/wind/$', - views.workout_wind_view, name='workout_wind_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/image/$', views.workout_uploadimage_view, name='workout_uploadimage_view'), re_path(r'^virtualevent/(?P\d+)/compare/$', @@ -540,8 +532,6 @@ urlpatterns = [ views.workout_downloadwind_view, name='workout_downloadwind_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/metar/(?P\w+)/$', views.workout_downloadmetar_view, name='workout_downloadmetar_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/stream/$', - views.workout_stream_view, name='workout_stream_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/editintervals/$', views.workout_summary_edit_view, name='workout_summary_edit_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/restore/$', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index e7a951ee..987c9bdb 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2763,348 +2763,6 @@ def workout_downloadmetar_view(request, id=0, return response -# Show form to update wind data -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, - login_url="/rowers/paidplans", - message="This functionality requires a Pro plan or higher." - " If you are already a Pro user, please log in to access this functionality", - redirect_field_name=None) -def workout_wind_view(request, id=0, message="", successmessage=""): - row = get_workoutuser(id, request) - r = getrower(request.user) - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': row.name - }, - { - 'url': reverse('workout_wind_view', kwargs={'id': id}), - 'name': 'Wind' - } - - ] - - # get data - f1 = row.csvfilename - u = row.user.user - r = getrower(u) - - # create bearing - rowdata = rdata(csvfile=f1) - if row == 0: # pragma: no cover - return HttpResponse("Error: CSV Data File Not Found") - - hascoordinates = 1 - try: - latitude = rowdata.df.loc[:, ' latitude'] - except KeyError: - hascoordinates = 0 - - if hascoordinates and not latitude.std(): # pragma: no cover - hascoordinates = 0 - - try: - _ = rowdata.df.loc[:, 'bearing'].values - except KeyError: - rowdata.add_bearing() - rowdata.write_csv(f1, gzip=True) - - if hascoordinates: - avglat = rowdata.df[' latitude'].mean() - avglon = rowdata.df[' longitude'].mean() - airportcode, newlat, newlon, airportdistance = get_airport_code( - avglat, avglon) - airportcode = airportcode.upper() - airportdistance = airportdistance[0] - else: - airportcode = 'UNKNOWN' - airportdistance = 0 - - if request.method == 'POST': - # process form - form = UpdateWindForm(request.POST) - - if form.is_valid(): - - vwind1 = form.cleaned_data['vwind1'] - vwind2 = form.cleaned_data['vwind2'] - dist1 = form.cleaned_data['dist1'] - dist2 = form.cleaned_data['dist2'] - winddirection1 = form.cleaned_data['winddirection1'] - winddirection2 = form.cleaned_data['winddirection2'] - windunit = form.cleaned_data['windunit'] - - rowdata.update_wind(vwind1, vwind2, - winddirection1, - winddirection2, - dist1, dist2, - units=windunit) - - rowdata.write_csv(f1, gzip=True) - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id - } - url = reverse('workout_wind_view', kwargs=kwargs) - _ = HttpResponseRedirect(url) - - else: - form = UpdateWindForm() - - # create interactive plot - res = interactive_windchart(encoder.decode_hex(id), promember=1) - script = res[0] - div = res[1] - - if hascoordinates: - gmscript, gmdiv = leaflet_chart( - rowdata.df[' latitude'], - rowdata.df[' longitude'], - row.name) - else: - gmscript = "" - gmdiv = "No GPS data available" - - messages.info(request, successmessage) - messages.error(request, message) - - return render(request, - 'windedit.html', - {'workout': row, - 'rower': r, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'form': form, - 'airport': airportcode, - 'airportdistance': airportdistance, - 'the_div': div, - 'gmap': gmscript, - 'gmapdiv': gmdiv}) - - -# Show form to update River stream data (for river dwellers) -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, - login_url="/rowers/paidplans", - message="This functionality requires a Pro plan or higher." - " If you are already a Pro user, please log in to access this functionality", - redirect_field_name=None) -def workout_stream_view(request, id=0, message="", successmessage=""): - row = get_workoutuser(id, request) - r = getrower(request.user) - - # create interactive plot - f1 = row.csvfilename - u = row.user.user - r = getrower(u) - - rowdata = rdata(csvfile=f1) - if rowdata == 0: # pragma: no cover - messages.info(request, "Error: CSV data file not found") - url = reverse('workout_edit_view', kwargs={ - 'id': encoder.encode_hex(row.id)}) - return HttpResponseRedirect(url) - - if request.method == 'POST': - # process form - form = UpdateStreamForm(request.POST) - - if form.is_valid(): - - dist1 = form.cleaned_data['dist1'] - dist2 = form.cleaned_data['dist2'] - stream1 = form.cleaned_data['stream1'] - stream2 = form.cleaned_data['stream2'] - streamunit = form.cleaned_data['streamunit'] - - rowdata.update_stream(stream1, stream2, dist1, dist2, - units=streamunit) - - rowdata.write_csv(f1, gzip=True) - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id} - url = reverse('workout_wind_view', kwargs=kwargs) - _ = HttpResponseRedirect(url) - - else: - form = UpdateStreamForm() - - # create interactive plot - res = interactive_streamchart(encoder.decode_hex(id), promember=1) - script = res[0] - div = res[1] - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': row.name - }, - { - 'url': reverse('workout_stream_view', kwargs={'id': id}), - 'name': 'Stream' - } - - ] - - messages.info(request, successmessage) - messages.error(request, message) - return render(request, - 'streamedit.html', - {'workout': row, - 'rower': r, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'form': form, - 'the_div': div}) - -# Form to set average crew weight and boat type, then run power calcs - - -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, login_url="/rowers/paidplans", redirect_field_name=None) -def workout_otwsetpower_view(request, id=0, message="", successmessage=""): - w = get_workoutuser(id, request) - r = getrower(request.user) - - mayedit = 1 - - if request.method == 'POST': - # process form - form = AdvancedWorkoutForm(request.POST) - - if form.is_valid(): - boattype = form.cleaned_data['boattype'] - weightvalue = form.cleaned_data['weightvalue'] - coastalbrand = form.cleaned_data['boatbrand'] - boatclass = w.workouttype - w.boattype = boattype - w.weightvalue = weightvalue - w.boatbrand = coastalbrand - w.save() - - # load row data & create power/wind/bearing columns if not set - f1 = w.csvfilename - rowdata = rdata(csvfile=f1) - if rowdata == 0: # pragma: no cover - return HttpResponse("Error: CSV Data File Not Found") - try: - _ = rowdata.df['vstream'] - except KeyError: - rowdata.add_stream(0) - rowdata.write_csv(f1, gzip=True) - - try: - _ = rowdata.df['bearing'] - except KeyError: - rowdata.add_bearing() - rowdata.write_csv(f1, gzip=True) - - try: - _ = rowdata.df['vwind'] - except KeyError: - rowdata.add_wind(0, 0) - rowdata.write_csv(f1, gzip=True) - - # do power calculation (asynchronous) - r = w.user - u = r.user - - first_name = u.first_name - last_name = u.last_name - emailaddress = u.email - - job = myqueue(queue, - handle_otwsetpower, f1, boattype, boatclass, coastalbrand, - weightvalue, - first_name, last_name, emailaddress, encoder.decode_hex( - id), - ps=[r.p0, r.p1, r.p2, r.p3], - ratio=r.cpratio, - # quick_calc = quick_calc, - # go_service=go_service, - emailbounced=r.emailbounced - ) - - try: - request.session['async_tasks'] += [(job.id, 'otwsetpower')] - except KeyError: - request.session['async_tasks'] = [(job.id, 'otwsetpower')] - - successmessage = 'Your calculations have been submitted." \ - " You will receive an email when they are done." \ - " You can check the status of your calculations" \ - " here' - messages.info(request, successmessage) - kwargs = { - 'id': id} - - try: - url = request.session['referer'] - except KeyError: - url = reverse('workout_edit_view', kwargs=kwargs) - - response = HttpResponseRedirect(url) - return response - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id} - url = reverse('workout_otwsetpower_view', kwargs=kwargs) - response = HttpResponseRedirect(url) - - else: - form = AdvancedWorkoutForm(instance=w) - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_otwsetpower_view', kwargs={'id': id}), - 'name': 'OTW Power' - } - - ] - - messages.error(request, message) - messages.info(request, successmessage) - return render(request, - 'otwsetpower.html', - {'workout': w, - 'rower': w, - 'mayedit': mayedit, - 'active': 'nav-workouts', - 'breadcrumbs': breadcrumbs, - 'teams': get_my_teams(request.user), - 'form': form, - }) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @@ -4501,57 +4159,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): # The interactive plot with wind corrected pace for OTW outings -def workout_otwpowerplot_view(request, id=0, message="", successmessage=""): - w = get_workout(id) - r = getrower(request.user) - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_otwpowerplot_view', kwargs={'id': id}), - 'name': 'Interactive OTW Power Plot' - } - - ] - - # check if user is owner of this workout - - # create interactive plot - - promember = 0 - mayedit = 0 - result = request.user.is_authenticated and ispromember(request.user) - if result: - promember = 1 - if request.user == w.user.user: - mayedit = 1 - - # create interactive plot - res = interactive_otw_advanced_pace_chart( - encoder.decode_hex(id), promember=promember) - script = res[0] - div = res[1] - - messages.error(request, message) - messages.info(request, successmessage) - - return render(request, - 'otwinteractive.html', - {'workout': w, - 'rower': r, - 'active': 'nav-workouts', - 'breadcrumbs': breadcrumbs, - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'the_div': div, - 'mayedit': mayedit}) # From a0cecf23baf35bb5933e3da8be32bd42131791b0 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 10:47:07 +0200 Subject: [PATCH 072/121] removing wind, setpower, stream --- rowers/forms.py | 52 ------------ rowers/tests/test_aworkouts.py | 117 -------------------------- rowers/tests/test_urls.py | 1 - rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/views/statements.py | 4 +- 5 files changed, 2 insertions(+), 172 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index f28ed65d..27ecdd65 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -778,59 +778,7 @@ class PredictedPieceFormNoDistance(forms.Form): value = forms.FloatField(initial=10, label='Free ranking piece') -# On the Geeky side, to update stream information for river dwellers -class UpdateStreamForm(forms.Form): - unitchoices = ( - ('m', 'm/s'), - ('f', 'foot/s'), - ('k', 'knots'), - ('p', 'pace difference (sec/500m)'), - ) - dist1 = forms.FloatField(initial=0, label='Distance 1') - dist2 = forms.FloatField(initial=1000, label='Distance 2') - stream1 = forms.FloatField(initial=0, label='Stream velocity 1') - stream2 = forms.FloatField(initial=0, label='Stream velocity 2') - streamunit = forms.ChoiceField(required=True, - choices=unitchoices, - initial='m', - label='Unit') - - class Meta: - fields = ['dist1', 'dist2', 'stream1', 'stream2', 'streamunit'] - -# add wind information to your workout - - -class UpdateWindForm(forms.Form): - unitchoices = ( - ('m', 'm/s'), - ('k', 'knots'), - ('b', 'beaufort'), - ('kmh', 'km/h'), - ('mph', 'miles/hour'), - ) - dist1 = forms.FloatField(initial=0, label='Distance 1') - dist2 = forms.FloatField(initial=1000, label='Distance 2') - vwind1 = forms.FloatField(initial=0, required=False, label='Wind Speed 1') - vwind2 = forms.FloatField(initial=0, required=False, label='Wind Speed 2') - windunit = forms.ChoiceField(required=True, - choices=unitchoices, - initial='m', - label='Unit') - winddirection1 = forms.IntegerField(initial=0, required=False, - label='Wind Direction 1') - winddirection2 = forms.IntegerField(initial=0, required=False, - label='Wind Direction 2') - - class Meta: - fields = ['dist1', 'dist2', - 'vwind1', 'vwind2', - 'windunit', - 'winddirection1', 'winddirection2'] - # Form to select a data range to show workouts from a certain time period - - class DateRangeForm(forms.Form): startdate = forms.DateField( initial=timezone.now()-datetime.timedelta(days=365), diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index fdbb7e75..40d0c951 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -371,10 +371,6 @@ class WorkoutViewTest(TestCase): response = self.c.get(url,follow=True) self.assertEqual(response.status_code,200) - url = reverse('workout_downloadwind_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - } - ) response = self.c.get(url,follow=True) self.assertEqual(response.status_code,200) @@ -459,122 +455,9 @@ class WorkoutViewTest(TestCase): status_code=302,target_status_code=200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_windform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_wind_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'vwind1':2.0, - 'vwind2':1.4, - 'windunit':'m', - 'winddirection1': 0, - 'winddirection2': 90, - } - - form = UpdateWindForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_streamform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_stream_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'stream1':2.0, - 'stream2':1.4, - 'streamunit':'m', - } - - form = UpdateStreamForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.middleware.myqueue') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_myqueue): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('instroke_view',kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - rowdata = rowingdata.rowingdata(csvfile=self.winstroke.csvfilename) - instrokemetrics = rowdata.get_instroke_columns() - - self.assertTrue(len(instrokemetrics)>0) - - url = reverse('instroke_chart', - kwargs={ - 'id':encoder.encode_hex(self.winstroke.id), - 'metric':instrokemetrics[0], - }) - url2 = reverse(self.r.defaultlandingpage,kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_otwsetpower_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'quick_calc':True, - 'boattype': '1x', - 'weightvalue': 75.0, - 'boatbrand':'maas', - } - - form = AdvancedWorkoutForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getsmallrowdata_db') def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index 4e1f0f4f..b522fd2d 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -154,7 +154,6 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/get-thumbnails/', '/rowers/workout/'+encoded1+'/image/', '/rowers/workout/'+encoded1+'/instroke/', - '/rowers/workout/'+encoded1+'/interactiveotwplot/', '/rowers/workout/'+encoded1+'/map/', '/rowers/workout/'+encoded1+'/recalcsummary/', '/rowers/workout/'+encoded1+'/restore/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e3b8adc00f8289642ff85c1a3d6305d33a81fa55..a60cff577eadb010ef8ed003a4c8fd085f798f34 100644 GIT binary patch literal 4000 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 GfB^txFg+Ro literal 4000 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 GfB^u_CqOd* diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 7f30dff9..fc48d89a 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -136,9 +136,9 @@ from rowers.forms import ( FitnessMetricForm, PredictedPieceFormNoDistance, EmailForm, RegistrationForm, RegistrationFormTermsOfService, RegistrationFormUniqueEmail, RegistrationFormSex, - CNsummaryForm, UpdateWindForm, + CNsummaryForm, StandardsForm, - UpdateStreamForm, WorkoutMultipleCompareForm, ChartParamChoiceForm, + WorkoutMultipleCompareForm, ChartParamChoiceForm, FusionMetricChoiceForm, BoxPlotChoiceForm, MultiFlexChoiceForm, TrendFlexModalForm, WorkoutSplitForm, WorkoutJoinParamForm, AnalysisOptionsForm, AnalysisChoiceForm, From e1b28bd770d788c221827b1146580fd4ed35e295 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:05:23 +0200 Subject: [PATCH 073/121] fix flex --- rowers/interactiveplots.py | 6 ++++-- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8777367b..9505b4aa 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2691,6 +2691,8 @@ def interactive_flex_chart2(id, r, promember=0, except TypeError: # pragma: no cover rowdata['ytrend'] = y1 + rowdata.replace([np.inf, -np.inf], np.nan, inplace=True) + rowdata.dropna(axis=1, how='all', inplace=True) data_dict = rowdata.to_dict("records") metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] @@ -2705,8 +2707,8 @@ def interactive_flex_chart2(id, r, promember=0, 'trendline': trendline, 'plottype': plottype, } - - script, div = get_chart("/flex", chart_data) + + script, div = get_chart("/flex", chart_data, debug=False) return script, div, workstrokesonly diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index a60cff577eadb010ef8ed003a4c8fd085f798f34..004c7b8712ef197501249f326e154723fe9b64f2 100644 GIT binary patch literal 3999 zcmV;Q4`A>giwFopM;c}V|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!zwlBQl~DC zaYP`iMFKVoD4VxcV&?BpuloLcf3bM` z_N_VYE>2f#b98pPNMCM_UM)|%&EopQ7jNk9?iBmoyKgwl{iA;MX4Sv{fJ=Ir8lK>)b;iX@kz0nQq>5qCt z=H2GyUmy7Qu7A*-oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dJ3qVV@7?`p|7v-0clTxb`q9fn0(3us{6O#=dEeqbITIamguemo0~{>U zP9OJw+@0?|>${8o`Ra6e@9tk6dwHRB=<~C_JIRMVTOOUQUv3VC{lVg^t)C8fxZ1rz zPnPM&Zqj|`d--DHr@cSoJ46fzi_1@+cDSEJtvg=+W0~&$x2|9Qe15upHT(DH>6ct? zezg91+4Y-Wmlsb~>(~AA-~E*zkxuh_ck+69h&Ekh_vfc8EUvl4m9L+4?^dU;PybIh zAnuwQxaJa9zJ9dczN-EE!|hwKOk^*&Tz!x)m+1i?66VLa`}+#Ti`DUQy0vBh>g?s= zlWzUGJ4qBj+j@2H?&!at-TijU`PpjSU)`L)NbkTmyo=l2K0oq{S3-;(eoeLq+>*dm z3go|{up@Cjg3lf>T=RUlJ6w)lo3AeW{MSFec_z4a4FB?s>74)S`aX$GJXq}h`tq8K zbkQ$=-u1VbUxe%MYN<1rB<*4q)R7HN1ABhZ6>%q55%&ZU!370(N_ta4?&0R#sV;f9 zQW1A&h*34lry+t1a*wm&?rcTeW0H3(Uc)_>ly`>NaQ78)x3nGSor1d!h6-}WTX4s! zxCf|+yCm*Os5IKVH*O-_>yr1t6>;|_cyJnh-no)y9MgQb`+B$w7$4AT?9gyotqY+EP1{Kd8&&% zRWrrYJ|t64bfz4ll=R)OosZ13R7BpIn3Q^z%#)ZBb!3eIqDsSyv7|jeC-OE_ zL_X>1OsGn}f><(2XK6;GJyt|MntwZS#CZQCb^}Pp_iNo{wOB)9|J~&kLF)@+y0t0g6U! zW;EK@B=cfTdbU6s@`#hs?OT*~n7lJyFleH*Ojy*OpA-3t?D=e=u~p6U%xL07$YY!5 zuP4tr_m%-kzdzw6!Kz#A*f_tsA$BdkspV=Hz7$pYREfoGJiwzTn~8! zBLRY{Nrva5GVhyiKMHv^Y*AG6eBh!Qo^LWg3VCNRqNrp(+M?duCiCNv-{#(`TAZ3-tx8#b-d}vnPDCE7Nh)(0Zwd{)K z+lMyVk3pVc+Y(Eo+gt03>h__<=SLw=#xX=y6FezPeohsYt|IbctT3zQc~`WSc2?x2 zBJ#l`?y3w-TehN7pvn9gE)pb8_ZHzMdKz z&bhbFBWgx_L70rpH+z1Vd^8jw1{L|(bo=Q?Y=m;R6jhW~7@BTBeVG?)LelM3GH=Uf zUB^~SI|_Mkgmn=ck+S5Q4{rqWHo-RNtt#tUTqA!|^0p%KWP;aKSBb~T#OIsL50e*T z302iq63W)qwH}z`kPn6sRh<(gmaVbzO}8I|Jc5ZxtnuDDz?OSEeVIo#5{NWWT8Gmi z&&`uFNZ#eFYgH#iM_5$1pA-3-M*C<0F=*oRj;UxBwQuqHG05lK+kipS_tse|ntMAZ z@)gGl>3dr^6EJlc{P z-mUWRnua$_o^$t8mG{;sOE9(!%*m4%&^;Z!Fe$azfcPDuo*PQOL7F z()GX$q3Eh*b0S~UJWpbRVANz_hKNNoGiXNhyswCSFp^|mQ)v-_n#@lzJ2t*=xFcS zTGV5Z-{s!w=J^ncT6J?GUy&zAl7+?uD)KQD&B<|Xo*#w0Gri5Z8t}0q^ql5;0@YlP5GI4ZwgJB;sdt6}Nj29a zmMyoL6MC))easqu)Jc8WRSO&XanR>ZsE$>cS(s+5rs2;3z2pI36|zMXDjKbylbTnN zamT@s$Dq>i(KT=0O^v%y5BfBAgUkK#H0XWNKDf8a$b@Fx4WhROfLJH>!A=G}Hsfv( zy-(gS5frb(2s&XnrM|S9RgR>xTEIH8v}h()+6f5ZjiF$ze)Y%(Tj0ji=N81 zN}H3^Lq+I=5g}CrY@}on`i6emE*+mcV_GMoGVUlT2r`lOIoOt^A4G2rdtLjSyfDUO zIwv)|ei*$s6eQ>dd=Etn>#?bMW1z<*z{-LI;Pa}Yfnbh@rtNU`X`rL!9F zsUq~wfF)f-w7Gs1^n@l7fhs|ZX*z3~H6Q*kdTRnJ zt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7Y&#WdYThX5*$80NNxch` z8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_&EpuGnm7Fc z---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO9Q16WL(-wQ zJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbvIKR!SI;w2^PnR1 zcH3G(rRGWDHcE4+oZMUA`GRbw3VAxpj24fviXBlT^-A4Jb+g0L=LFTobo@LSM! z9Q4*WL{xRvkZ7AXZ~Bg0_Qo?=RnXRfn@p-kQ}YJVCp*nL)Me>7bZx+&dTw4q&qgRa zX*B3*IwcsbjnXiBXDsAIOAUGsZNQ(tBiF`+o=8=AKYLZ4T%1`Im%94CXmtzSQko=p(a zMefpzQc}auohTam5zss1*+!L1CpawW(wP-{sR(_{YubWN>OB@6@O>-67z4eKaTbFr zODBY)mA1D+-)ioL(Mztr395|T5Q|pY-fF;ay>y1rhrFh(E^-&4=wfHH((r4^(h=CM zWDV$5j@*bc8J7;UzIlV_w<}re*13yRHfRfNt{(-x&($|d^z0|Ix@DW|2hr!1tQkq= z&5PC-t-hhwH*Xa5yj{dxW!y!HZFS3ud-F1SmkY0y=v^!-^{r9rIOt<8ywchAF+gjc zHgRuWLcd+fS{b>E5nE^M8KAc&&Gf1TPza#)$h|3gKNk8ATR(0%SUm2|FF!mz>zA9u z?@sz3e>qv6F4z6u_uEb1?{)niyz_q|Q+UDt1;cm8{f`HW-2r!YXZrXX^XJP?tKIhp z4=xhNvx^V=KJC)mb8+@|)2&~2Cnwvdl|HW4$1l6SOXux&Z>9~XYulrYL7gt`B z`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yp5DiF=#_`_Vat>5Kk1U$S#b69*SoFO zX|tD4`)xN|KH4wNfABor^x<#IlV!JACJy=2hevniz78ID>*Gfc4}W=@*=+yZZT~gB z#ZO+~ERB$UgGhv{j&NS8jm^FO3$u)@TE F0RU$&7)byC literal 4000 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 GfB^txFg+Ro From fffa7d5c7bb8049d3f3d6cce9cdcfb7919fa6e1b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:15:29 +0200 Subject: [PATCH 074/121] fixing plot data --- rowers/interactiveplots.py | 2 ++ rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/tests/viewnames.csv | 6 ------ 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9505b4aa..1bd5c621 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2907,6 +2907,8 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', datadf[xparam] = datadf[xparam] - datadf[xparam].iloc[0] datadf = datadf.fillna(0) + datadf.replace([np.inf, -np.inf], np.nan, inplace=True) + datadf = datadf.fillna(0) data_dict = datadf.to_dict("records") diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 004c7b8712ef197501249f326e154723fe9b64f2..e6e4549969a44723dee8b2ce6b738d55121a3d89 100644 GIT binary patch delta 3913 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 X#iR5z{z#WTyz@WGY*Ru6#DD<+cjo^Z delta 3912 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 Whv{j&NS8jm^FO3$u)@RxfB^t}!{18) diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index d6d99cfb..2f341b8d 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -55,17 +55,11 @@ 67,73,instroke_view,Create in stroke chart,TRUE,403,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 68,74,workout_stats_view,View Workout Stats,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 69,75,workout_data_view,shows the detailed data for a workout,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, -70,76,workout_otwsetpower_view,set weight and start offline OTW power calculations,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, -71,77,workout_otwpowerplot_view,generates OTW power chart,TRUE,200,basic,200,200,basic,200,200,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, -72,78,workout_wind_view,set wind,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 73,79,workout_uploadimage_view,upload image,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 74,80,virtualevent_compare_view,compare workouts from a virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 75,81,virtualevent_uploadimage_view,upload image to virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 76,82,virtualevent_setlogo_view,set logo of virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 77,83,logo_delete_view,delete logo,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, -78,84,workout_downloadwind_view,download Wind,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,FALSE,FALSE, -79,85,workout_downloadmetar_view,download METAR,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,FALSE,FALSE, -80,86,workout_stream_view,edit stream (redirects as no data in test suite),TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 81,87,workout_summary_edit_view,edit workout summary,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 82,88,workout_summary_restore_view,restore workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE, 83,89,workout_split_view,split workout,TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, From d6ee7ca2e5821de2f4661de71814f4d8d543bb6b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:28:00 +0200 Subject: [PATCH 075/121] c --- rowers/dataroutines.py | 14 +++++++++----- rowers/tests/test_aworkouts.py | 28 ---------------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index f545f843..40ea6451 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -73,6 +73,7 @@ from pytz.exceptions import UnknownTimeZoneError import collections import pendulum from rowingdata import rowingdata as rrdata +from rowingdata import rowingdata_pl as rrdata_pl from rowingdata import rower as rrower @@ -2619,18 +2620,21 @@ def add_c2_stroke_data_db(strokedata, workoutid, starttimeunix, csvfilename, ' lapIdx': lapidx, ' WorkoutState': 4, ' ElapsedTime (sec)': seconds, - 'cum_dist': dist2 + 'cum_dist': dist2, + ' WorkoutState': 0, }) df.sort_values(by='TimeStamp (sec)', ascending=True) # Create CSV file name and save data to CSV file - - res = df.to_csv(csvfilename, index_label='index', - compression='gzip') + row = rrdata(df=df) + row.write_csv(csvfilename) + row = rrdata_pl(df=pl.from_pandas(row.df)) + #res = df.to_csv(csvfilename, index_label='index', + # compression='gzip') - data = dataplep(df, id=workoutid, bands=False, debug=debug) + data = dataplep(row.df, id=workoutid, bands=False, debug=debug) return data diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 40d0c951..5bb15c68 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -362,18 +362,6 @@ class WorkoutViewTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - url = reverse('workout_downloadmetar_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - 'airportcode': 'LKHO' - } - ) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) # Stacked Flex Chart url = reverse('workout_flexchart_stacked_view',kwargs={ @@ -582,22 +570,6 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) - def test_remove_power_view(self): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('remove_power_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - url2 = reverse(self.r.defaultlandingpage, - kwargs={ - 'id':encoder.encode_hex(self.wwater.id) - } - ) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200 - ) @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getsmallrowdata_db') From 4b7ab5f9238b85c7c7e32ae0870fb9f755acc2cc Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:33:24 +0200 Subject: [PATCH 076/121] adding invalidoperationerror --- rowers/dataroutines.py | 48 +++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 40ea6451..3e782ffc 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -32,7 +32,10 @@ import zipfile import os from rowers.models import strokedatafields import polars as pl -from polars.exceptions import ColumnNotFoundError, SchemaError +from polars.exceptions import ( + ColumnNotFoundError, SchemaError, ComputeError, + InvalidOperationError +) from rowingdata import ( KinoMapParser, @@ -689,29 +692,29 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, return datadf try: datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) - except (KeyError, TypeError): + except (KeyError, TypeError, InvalidOperationError): pass try: datadf = datadf.with_columns((pl.col('peakforceangle')+1000).alias('peakforceangle')) - except (KeyError, TypeError): + except (KeyError, TypeError, InvalidOperationError): pass try: datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) - except (KeyError, TypeError): + except (KeyError, TypeError, InvalidOperationError): pass # protect 0 spm values from being nulled try: datadf = datadf.with_columns((pl.col('spm')+1.0).alias('spm')) - except (KeyError, TypeError): + except (KeyError, TypeError, InvalidOperationError): pass # protect 0 workoutstate values from being nulled try: datadf = datadf.with_columns((pl.col('workoutstate')+1).alias('workoutstate')) - except (KeyError, TypeError): + except (KeyError, TypeError, InvalidOperationError): pass try: @@ -2028,16 +2031,28 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar if windowsize <= 3: windowsize = 5 - df.with_columns( - (pl.col(" Cadence (stokes/min)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() - ).alias(" Cadence (stokes/min)")) - df.with_columns( - (pl.col(" DriveLength (meters)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() - ).alias(" DriveLength (meters)")) - df.with_columns( - (pl.col(" HRCur (bpm)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() - ).alias(" HRCur (bpm)")) - df.with_columns((pl.col("forceratio").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode()).alias("forceratio")) + try: + df.with_columns( + (pl.col(" Cadence (stokes/min)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" Cadence (stokes/min)")) + except ComputeError: + pass + try: + df.with_columns( + (pl.col(" DriveLength (meters)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" DriveLength (meters)")) + except ComputeError: + pass + try: + df.with_columns( + (pl.col(" HRCur (bpm)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" HRCur (bpm)")) + except ComputeError: + pass + try: + df.with_columns((pl.col("forceratio").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode()).alias("forceratio")) + except ComputeError: + pass df = df.with_columns((pl.col(" DriveLength (meters)") / pl.col(" DriveTime (ms)") * 1.0e3).alias("drivespeed")) @@ -2621,7 +2636,6 @@ def add_c2_stroke_data_db(strokedata, workoutid, starttimeunix, csvfilename, ' WorkoutState': 4, ' ElapsedTime (sec)': seconds, 'cum_dist': dist2, - ' WorkoutState': 0, }) df.sort_values(by='TimeStamp (sec)', ascending=True) From ee0229a43df400e70567a707e04623977dd8e5ec Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:46:08 +0200 Subject: [PATCH 077/121] adding more data filtering exception catching --- rowers/dataroutines.py | 28 +++++++++++++++++++++------- rowers/interactiveplots.py | 6 +++++- rowers/views/analysisviews.py | 7 +++++++ rowers/views/statements.py | 1 + 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 3e782ffc..09e70f02 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -679,6 +679,8 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, datadf['workoutid'] = 0 except TypeError: datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) + except ColumnNotFoundError: + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) before = {} ids = list(datadf['workoutid'].unique()) @@ -689,39 +691,51 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, # bring metrics which have negative values to positive domain if len(datadf) == 0: - return datadf + return data_orig try: datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) - except (KeyError, TypeError, InvalidOperationError): + except (KeyError, TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig try: datadf = datadf.with_columns((pl.col('peakforceangle')+1000).alias('peakforceangle')) - except (KeyError, TypeError, InvalidOperationError): + except (KeyError, TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig try: datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) - except (KeyError, TypeError, InvalidOperationError): + except (KeyError, TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig # protect 0 spm values from being nulled try: datadf = datadf.with_columns((pl.col('spm')+1.0).alias('spm')) - except (KeyError, TypeError, InvalidOperationError): + except (KeyError, TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig # protect 0 workoutstate values from being nulled try: datadf = datadf.with_columns((pl.col('workoutstate')+1).alias('workoutstate')) - except (KeyError, TypeError, InvalidOperationError): + except (KeyError, TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig try: datadf = datadf.select(pl.all().clip(lower_bound=0)) # datadf = datadf.clip(lower=0) - except TypeError: + except (TypeError): pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig # protect advanced metrics columns advancedcols = [ diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1bd5c621..1f8a073f 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -195,7 +195,11 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): if df.is_empty(): return "", "Not enough data to make a chart" - df = df.sort("hr") + try: + df = df.sort("hr") + except ColumnNotFoundError: + return "", "Not enough data to make a chart" + df = df.with_columns((pl.col("deltat")*pl.col("hr")).alias("deltat")) sumtimehr = df['deltat'].sum() diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 7008586d..391d92ad 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -2482,11 +2482,18 @@ def history_view_data(request, userid=0): meters, hours, minutes, seconds = get_totals(a_workouts) totalseconds = 3600 * hours + 60 * minutes + seconds ddf = getsmallrowdata_pl(columns, ids=[w.id for w in a_workouts]) + if ddf.is_empty(): + totalscript = "" + totaldiv = "No data" try: ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat")) except KeyError: pass + except ColumnNotFoundError: + totalscript = "" + totaldiv = "No data" + ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=True, ignoreadvanced=True) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index fc48d89a..4eb83f0a 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -205,6 +205,7 @@ from rowers.rojabo_stuff import rojabo_open from rowers.integrations import * +from polars.exceptions import ColumnNotFoundError import rowers.ownapistuff as ownapistuff from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI From acf5e401df6d240456353a61a49e5648478c7968 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 11:56:23 +0200 Subject: [PATCH 078/121] last failing tests --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/views/workoutviews.py | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e6e4549969a44723dee8b2ce6b738d55121a3d89..96809caca2971e112783a06ee9e4fc7fc5cc2180 100644 GIT binary patch literal 4000 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 literal 4000 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 GfB^uG7C09G diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 987c9bdb..c64c1bac 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3252,7 +3252,13 @@ def workout_erase_column_view(request, id=0, column=''): row, workout = dataprep.getrowdata(id=w.id) row.df[columnl] = defaultvalue - os.remove(w.csvfilename+'.gz') + try: + os.remove(w.csvfilename+'.gz') + except FileNotFoundError: + try: + os.remove(w.csvfilename) + except FileNotFoundError: + pass row.write_csv(w.csvfilename, gzip=True) From 5d8968fc5fa33ec62712d0df2f92891b47ce24c7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 13 Apr 2024 17:19:47 +0200 Subject: [PATCH 079/121] boxchart pandas to polars --- rowers/interactiveplots.py | 13 +++++------ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes rowers/views/analysisviews.py | 30 +++++++++----------------- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1f8a073f..6f2b42e0 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -328,7 +328,7 @@ def interactive_workouttype_piechart(workouts): def interactive_boxchart(datadf, fieldname, extratitle='', spmmin=0, spmmax=0, workmin=0, workmax=0): - if datadf.empty: # pragma: no cover + if datadf.is_empty(): # pragma: no cover return '', 'It looks like there are no data matching your filter' columns = datadf.columns @@ -339,20 +339,17 @@ def interactive_boxchart(datadf, fieldname, extratitle='', if 'date' not in columns: # pragma: no cover return '', 'Not enough data' - try: - datadf.date = datadf.date.apply(lambda x:x.strftime("%Y-%m-%d")) - except AttributeError: - datadf.date = "2000-01-01" + datadf = datadf.with_columns((pl.col("date").apply(lambda x:x.strftime("%Y-%m-%d"))).alias("date")) + datadf = datadf.with_columns((pl.col(fieldname)).alias("value")) - datadf['value'] = datadf[fieldname] - data_dict = datadf.to_dict("records") + data_dict = datadf.to_dicts() boxplot_data = { "metric": metricsdicts[fieldname]["verbose_name"], "data": data_dict } - script, div = get_chart("/boxplot", boxplot_data) + script, div = get_chart("/boxplot", boxplot_data, debug=False) return script, div diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 96809caca2971e112783a06ee9e4fc7fc5cc2180..0873ae7aafdc85f8930c098d869ebd25fb92fe0e 100644 GIT binary patch delta 257 zcmV+c0sj7=AE6%xABzYGMpv;0s}6q`?D6m5@C3d(?Y}u(><)OaJJTP(F@L`PwAy`t z`1mSuytw+X?~5+IJy#cRH{JTAJ3HGxt@LrVK0WFBE}ggAyIiu!NANSjCxPG*so&WG(-}FpB(-0BD2~4x!e9@ zdW&DYKL2f*?hN1|!0!LU#f>j^Pk#HDjvj1Luh!`~JWeQ=S5MN@_$^)f=)wO0Dd@J= H#DD<+oo177 delta 256 zcmV+b0ssD?AD|xwABzYGuvD=Is}6rS;SintZsDub{+q+a?tlloGyU-!^XKbNtKIj9 zkFOHPi>nX&zUb21b9M1{)2&asv$O5fN*`D2)03|6(s{ex+m)z%Jnrz(>;Bcn)s5HW z(dFgY>bUFEPrrUKuFw6&$so(()*YWz4359Yqq<9`43;Fn?Cw!dA95}%fumn`tay(+}Gi=ZhiXn$spmmin, + pl.col("spm")workmin, + pl.col("driveenergy") Date: Sat, 13 Apr 2024 20:09:37 +0200 Subject: [PATCH 080/121] plan chart --- rowers/interactiveplots.py | 11 +++++++++++ rowers/plannedsessions.py | 11 ++--------- rowers/templates/trainingplan_chart.html | 15 ++++++--------- rowers/views/planviews.py | 2 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 6f2b42e0..57ed3723 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -354,6 +354,17 @@ def interactive_boxchart(datadf, fieldname, extratitle='', def interactive_planchart(data, startdate, enddate): + # data = data.melt(id_vars=['startdate'], value_vars=['executed', 'planned']) + + data = data.with_columns((pl.col("startdate").apply(lambda x: x.strftime("%Y-%m-%d"))).alias("startdate")) + data_dict = data.to_dicts() + chart_data = { + 'data': data_dict, + } + + script, div = get_chart("/plan", chart_data, debug=False) + return script, div + hv.extension('bokeh') yaxmaximum = data['executed'].max() diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 3ecda13e..dc2c4a08 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -25,6 +25,7 @@ from rowingdata import rower as rrower from rowingdata import rowingdata as rrdata import arrow import pandas as pd +import polars as pl import json # Python @@ -203,16 +204,8 @@ def get_execution_report(rower, startdate, enddate, plan=None): startdate__gte=enddate) else: # pragma: no cover plans = TrainingPlan.objects.filter(rowers__in=[rower]) - #plans2 = TrainingPlan.objects.filter( - # enddate__lte=enddate, startdate__lte=enddate, rowers__in=[rower]) - #plans = plans | plans2 - - #plans = plans.exclude(status=False).order_by("-enddate") if not plans: - # make week cycles here - # get monday before startdate - micros = [] else: sorted_plans = sorted(plans, key= lambda t: t.overlap(startdate,enddate)) @@ -313,7 +306,7 @@ def get_execution_report(rower, startdate, enddate, plan=None): planned += [plannedscore] executed += [actualscore] - data = pd.DataFrame({ + data = pl.DataFrame({ 'startdate': startdates, 'planned': planned, 'executed': executed, diff --git a/rowers/templates/trainingplan_chart.html b/rowers/templates/trainingplan_chart.html index 3b1c5c11..bd8b523e 100644 --- a/rowers/templates/trainingplan_chart.html +++ b/rowers/templates/trainingplan_chart.html @@ -6,25 +6,22 @@ {% block main %} - - - +

      Training Plan - {{ plan.name }}

      -{{ the_script | safe }}
      • -
        - {{ the_div|safe }} -
        + {{ the_div|safe }}
      + +{{ the_script | safe }} + + {% endblock %} {% block scripts %} diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index e44609ac..eb0db639 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -3569,7 +3569,7 @@ def rower_trainingplan_execution_view(request, else: # pragma: no cover data, message = get_execution_report(r, startdate, enddate) - if not data.empty: + if not data.is_empty(): script, div = interactive_planchart(data, startdate, enddate) else: # pragma: no cover script = '' From 2c54524d638c919326bd0a0e2022b6d55d2b3857 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 16:01:59 +0200 Subject: [PATCH 081/121] plan chart v1 --- rowers/interactiveplots.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 57ed3723..356f4f7e 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -365,36 +365,6 @@ def interactive_planchart(data, startdate, enddate): script, div = get_chart("/plan", chart_data, debug=False) return script, div - hv.extension('bokeh') - - yaxmaximum = data['executed'].max() - if data['planned'].max() > yaxmaximum: # pragma: no cover - yaxmaximum = data['planned'].max() - - if yaxmaximum == 0: # pragma: no cover - yaxmaximum = 250 - - yrange1 = Range1d(start=0, end=1.1*yaxmaximum) - - tidy_df = data.melt(id_vars=['startdate'], value_vars=[ - 'executed', 'planned']) - bars = hv.Bars(tidy_df, ['startdate', 'variable'], ['value']) - bars.opts( - opts.Bars(show_legend=True, tools=['tap', 'hover'], legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.width = 550 - p.height = 350 - p.y_range = yrange1 - p.toolbar_location = 'above' - #p.sizing_mode = 'stretch_both' - - script, div = components(p) - - return script, div - - def interactive_activitychart2(workouts, startdate, enddate, stack='type', yaxis='duration'): From a4d964c4f017fbb4050eac0a3405d392c57717f9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 16:49:38 +0200 Subject: [PATCH 082/121] polarized marker workouts chart --- rowers/interactiveplots.py | 51 ++++++++++++++------------------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 356f4f7e..38782b76 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -559,20 +559,20 @@ def goldmedalscorechart(user, startdate=None, enddate=None): testduration = [ w.goldmedalseconds if w.rankingpiece else 0 for w in markerworkouts] - df = pd.DataFrame({ + df = pl.DataFrame({ 'id': outids, 'date': dates, 'testpower': testpower, 'testduration': testduration, }) - df.sort_values(['date'], inplace=True) + df = df.sort('date') + df = df.drop_nulls() - mask = df['testpower'].isnull() - dates = df.mask(mask)['date'].dropna().values - testpower = df.mask(mask)['testpower'].dropna().values - ids = df.mask(mask)['id'].dropna().values - - outids = df.mask(mask)['id'].dropna().unique() + dates = df['date'] + testpower = df['testpower'] + ids = df['id'] + + outids = ids.unique() # all workouts alldates, alltestpower, allduration, allids = all_goldmedalstandards( @@ -614,7 +614,7 @@ def goldmedalscorechart(user, startdate=None, enddate=None): duration.append(np.nan) workoutid.append(0) - df = pd.DataFrame({ + df = pl.DataFrame({ 'markerscore': markerscore, 'markerduration': markerduration, 'score': score, @@ -623,32 +623,20 @@ def goldmedalscorechart(user, startdate=None, enddate=None): 'id': workoutid, }) - df['url'] = df['id'].apply(lambda x: settings.SITE_URL + - '/rowers/workout/{id}/'.format(id=encoder.encode_hex(x))) - df['workout'] = df['id'].apply(lambda x: workoutname(x)) + df = df.with_columns((pl.col("id").apply(lambda x: settings.SITE_URL + + '/rowers/workout/{id}/'.format(id=encoder.encode_hex(x)))).alias("url")) + df = df.with_columns((pl.col("id").apply(lambda x: workoutname(x))).alias("workout")) - df.sort_values(['date'], inplace=True) + df = df.sort('date') # find index values where score is max - idx = df.groupby(['date'])['score'].transform(max) == df['score'] - df = df[idx] + dfmax = df.group_by("date", maintain_order=True).max() + dfmax = dfmax.fill_nan(0) + dfmax = dfmax.with_columns((pl.col("date").apply(lambda x: x.strftime("%Y-%m-%d"))).alias("date")) + dfmax = dfmax.with_columns((pl.col("duration").apply(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("duration")) + dfmax = dfmax.with_columns((pl.col("markerduration").apply(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("markerduration")) - df.fillna(value=0, inplace=True) - df['dat1'] = df['date'].map(lambda x: x.to_pydatetime(x).strftime("%Y-%m-%d")) - - df2 = pd.DataFrame({ - 'markerscore': df['markerscore'], - 'score': df['score'], - 'markerduration':df['markerduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - 'duration': df['duration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - 'date': df['dat1'], - 'url':df['url'], - 'workout':df['workout'] - }) - - data_dicts = df2.to_dict("records") + data_dicts = dfmax.to_dicts() chart_data = { 'data': data_dicts } @@ -708,7 +696,6 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= df = pd.DataFrame.from_records(records) if df.empty: # pragma: no cover return ['', 'No Data', 0, 0, 0, outids] - df.set_index('date', inplace=True) markerworkouts = Workout.objects.filter( user=user.rower, date__gte=startdate-datetime.timedelta(days=90), From c3d7afd7af8bacb24401279b657b5a5d08e1e5ec Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 16:50:00 +0200 Subject: [PATCH 083/121] polarized marker workouts --- rowers/dataprep.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index e73bc863..42efff8f 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -415,7 +415,7 @@ def resample(id, r, parent, overwrite='copy'): def calculate_goldmedalstandard(rower, workout, recurrance=True): cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: - df = pd.read_parquet(cpfile) + df = pl.read_parquet(cpfile) except: background = True if settings.TESTING: @@ -423,11 +423,13 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): df, delta, cpvalues = setcp(workout, background=background) if df.empty: return 0, 0 + df = pl.from_pandas(df) - if df.empty and recurrance: # pragma: no cover + if df.is_empty() and recurrance: # pragma: no cover df, delta, cpvalues = setcp(workout, recurrance=False, background=True) if df.empty: return 0, 0 + df = pl.from_pandas(df) age = calculate_age(rower.birthdate, today=workout.date) @@ -490,9 +492,10 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): scores = 100.*powers/wcpowers try: - indexmax = scores.idxmax() - delta = int(df.loc[indexmax, 'delta']) - maxvalue = scores.max() + df = pl.DataFrame({'times': times, 'scores': scores}) + df = df.filter(pl.col("scores") == pl.col("scores").max()) + delta = df[0, "times"] + maxvalue = df[0, "scores"] except (ValueError, TypeError, KeyError): # pragma: no cover indexmax = 0 delta = 0 From 30fdabd2cda8826f3e85ac0586994a7c87f1f67d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 19:32:58 +0200 Subject: [PATCH 084/121] fixing test boxplot chart --- rowers/tests/test_unit_tests.py | 3 ++- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 3998 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 467267e9..6b20a563 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -673,7 +673,8 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) def test_interactive_boxchart(self): - df = pd.read_csv('rowers/tests/testdata/boxplotdata.csv') + df = pl.read_csv('rowers/tests/testdata/boxplotdata.csv') + df = df.with_columns(pl.col("date").apply(lambda x:datetime.datetime.strptime(x, "%Y-%m-%d"))) script, div = interactiveplots.interactive_boxchart(df, 'spm') diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 0873ae7aafdc85f8930c098d869ebd25fb92fe0e..ac4ffb0c87e4512084761e3ace406f9fea46db4f 100644 GIT binary patch literal 3998 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 H#DD<+u}?i2 From 2d2923362f28c39caa9675800aef7ee4f38ca38c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 20:44:24 +0200 Subject: [PATCH 085/121] fix performance chart to polars --- rowers/interactiveplots.py | 43 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 38782b76..fbbd85fa 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -19,6 +19,7 @@ import rowers.dataprep as dataprep from rowers.dataprep import rdata import rowers.utils as utils import polars as pl +import pytz from rowers.rower_rules import ispromember from polars.exceptions import ColumnNotFoundError @@ -479,7 +480,7 @@ def interactive_forcecurve(theworkouts): def weightfromrecord(row,metricchoice): - vv = row[metricchoice] + vv = row[metricchoice][0] if vv > 0: return vv if metricchoice == 'rscore': # pragma: no cover @@ -505,18 +506,19 @@ def getfatigues( lambda_c = 2/(kfitness+1) nrdays = (enddate-startdate).days + for i in range(nrdays+1): date = startdate+datetime.timedelta(days=i) datekey = date.strftime('%Y-%m-%d') weight = 0 try: - df2 = df.loc[date.date()] + df2 = df.filter(pl.col("date") == date.date()) - if type(df2) == pd.Series: # pragma: no cover + if type(df2) == pl.Series: # pragma: no cover weight += weightfromrecord(df2,metricchoice) else: - for index, row in df2.iterrows(): + for row in df2.iter_slices(n_rows=1): weight += weightfromrecord(row,metricchoice) except KeyError: pass @@ -693,8 +695,8 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= } records.append(dd) - df = pd.DataFrame.from_records(records) - if df.empty: # pragma: no cover + df = pl.from_records(records) + if df.is_empty(): # pragma: no cover return ['', 'No Data', 0, 0, 0, outids] markerworkouts = Workout.objects.filter( @@ -727,7 +729,7 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= kfatigue, kfitness ) - df = pd.DataFrame({ + df = pl.DataFrame({ 'date': dates, 'testpower': testpower, 'testduration': testduration, @@ -742,19 +744,19 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= endform = endfitness-endfatigue if modelchoice == 'banister': # pragma: no cover - df['fatigue'] = k2*df['fatigue'] - df['fitness'] = p0+k1*df['fitness'] + df = df.with_columns((pl.col("fatigue")*k2)) + df = df.with_columns((p0+pl.col("fitness")*k1)) - df['form'] = df['fitness']-df['fatigue'] + df = df.with_columns((pl.col("fitness")-pl.col("fatigue")).alias("form")) + df = df.sort("date") + + df = df.group_by('date').max() + startdate = startdate.replace(tzinfo=None) + startdate = pytz.utc.localize(startdate) - df.sort_values(['date'], inplace=True) - df = df.groupby(['date']).max() - df['date'] = df.index.values - mask = df['date'] > np.datetime64(startdate.astimezone( - tz=datetime.timezone.utc).replace(tzinfo=None)) - df = df.loc[mask] + df = df.filter(pl.col("date") > startdate) - df2 = pd.DataFrame({ + df2 = pl.DataFrame({ "testpower" :df['testpower'], "testduration":df['testduration'].apply( lambda x: totaltime_sec_to_string(x, shorten=True)), @@ -762,14 +764,13 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= "fatigue":df['fatigue'], "form":df['form'], "impulse":df['impulse'], - "date": df['date'].map(lambda x: x.strftime('%Y-%m-%d')), + "date": df['date'].apply(lambda x: x.strftime('%Y-%m-%d')), }) + df2 = df2.fill_nan(0) - df2.fillna(value=0, inplace=True) - - data_dict = df2.to_dict("records") + data_dict = df2.to_dicts() chart_data = { 'data': data_dict, From 8d98eba403109cbf9341439b5a8941f776a5f007 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 20:52:40 +0200 Subject: [PATCH 086/121] fixing performance chart (polars) --- rowers/dataprep.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 42efff8f..c6f8049d 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -124,6 +124,10 @@ from rq import Queue from rowers.datautils import rpetotss def rscore_approx(row): + if isinstance(row, pl.DataFrame): + row = {'rscore': row['rscore'][0]} + if isinstance(row, pl.Series): + row = {'rscore': row['rscore'][0]} if row['rscore'] > 0: return row['rscore'] if row['rscore'] == 0: # pragma: no cover From 0d09ebcbc2863e1f8058326b30c8d40c92b4a2a4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 14 Apr 2024 21:26:28 +0200 Subject: [PATCH 087/121] leaflet_compare -> polars --- rowers/interactiveplots.py | 45 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index fbbd85fa..6ee68e4b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -802,14 +802,6 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, rowdata = rowdata.fill_nan(None).drop_nulls() - #rowdata = dataprep.filter_df(rowdata, 'spm', spmmin, largerthan=True) - #rowdata = dataprep.filter_df(rowdata, 'spm', spmmax, largerthan=False) - - #rowdata = dataprep.filter_df( - # rowdata, 'driveenergy', workmin, largerthan=True) - #rowdata = dataprep.filter_df( - # rowdata, 'driveenergy', workmax, largerthan=False) - if rowdata.is_empty(): return "", "No Valid Data Available" @@ -914,7 +906,7 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): w = Workout.objects.get(id=id) rowdata = rdata(w.csvfilename) time = rowdata.df['TimeStamp (sec)'] - df = pd.DataFrame({ + df = pl.DataFrame({ 'workoutid': id, 'lat': rowdata.df[' latitude'], 'lon': rowdata.df[' longitude'], @@ -924,26 +916,28 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except (Workout.DoesNotExist, KeyError): # pragma: no cover pass try: - df = pd.concat(data, axis=0) + df = pl.concat(data, rechunk=True) except ValueError: # pragma: no cover - df = pd.DataFrame() + df = pl.DataFrame() latmean, lonmean, coordinates = course_coord_center(course) course_dict = GeoCourseSerializer(course).data # Throw out 0,0 - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) + df = df.with_columns( + (pl.col("lat")+pl.col("lon")).alias("latlon") + ) + df =df.filter(pl.col("latlon")!=0,) + df = df.fill_nan(None) + df = df.select(pl.all()).interpolate() try: lat = df['lat'] lon = df['lon'] except KeyError: # pragma: no cover return [0, "invalid coordinate data"] - if lat.empty or lon.empty: # pragma: no cover + if lat.is_empty() or lon.is_empty(): # pragma: no cover return [0, "invalid coordinate data"] colors = itertools.cycle(palette) @@ -955,7 +949,7 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): trajectories = [] for id, color in items: - group = df[df['workoutid'] == int(id)].copy() + group = df.filter(pl.col("workoutid") == int(id)) try: startsecond, endsecond = startenddict[id] except KeyError: # pragma: no cover @@ -967,17 +961,14 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except KeyError: # pragma: no cover label = str(id) - group.sort_values(by='time', ascending=True, inplace=True) - group.dropna(axis=0, how='any', inplace=True) + group = group.sort("time") if endsecond > 0: - group['time'] = group['time'] - startsecond - mask = group['time'] < 0 - group.mask(mask, inplace=True) - mask = group['time'] > (endsecond-startsecond) - group.mask(mask, inplace=True) + group = group.with_columns((pl.col("time")-startsecond)) + group = group.filter(pl.col("time")>0) + group = group.filter(pl.col("time") Date: Mon, 15 Apr 2024 17:48:59 +0200 Subject: [PATCH 088/121] retiring agegroupcpchart --- rowers/interactiveplots.py | 278 ------------------------------------- rowers/urls.py | 8 -- 2 files changed, 286 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 6ee68e4b..df373c11 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -991,284 +991,6 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): return script, div - - - - -def interactive_agegroupcpchart(age, normalized=False): - durations = [1, 4, 30, 60] - distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - - fhduration = [] - fhpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - fhduration.append(duration) - fhpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - fhduration.append(60.*duration) - fhpower.append(worldclasspower) - except ValueError: # pragma: no cover - pass - - flduration = [] - flpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - flduration.append(duration) - flpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - flduration.append(60.*duration) - flpower.append(worldclasspower) - except ValueError: # pragma: no cover - pass - - mlduration = [] - mlpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - mlduration.append(duration) - mlpower.append(worldclasspower) - except ZeroDivisionError: - mlduration.append(duration) - mlpower.append(np.nan) - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mlduration.append(60.*duration) - mlpower.append(worldclasspower) - except ValueError: # pragma: no cover - mlduration.append(60.*duration) - mlpower.append(np.nan) - - mhduration = [] - mhpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - mhduration.append(duration) - mhpower.append(worldclasspower) - except ZeroDivisionError: - mhduration.append(duration) - mhpower.append(np.nan) - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mhduration.append(60.*duration) - mhpower.append(worldclasspower) - except ValueError: # pragma: no cover - mhduration.append(60.*duration) - mhpower.append(np.nan) - - def fitfunc(pars, x): - return pars[0] / (1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - - def errfunc(pars, x, y): - return fitfunc(pars, x)-y - - # p0 = [500,350,10,8000] - - # fitting WC data to three parameter CP model - if len(fhduration) >= 4: - p1fh, success = optimize.leastsq(errfunc, p0[:], - args=(fhduration, fhpower)) - else: # pragma: no cover - p1fh = None - - # fitting WC data to three parameter CP model - if len(flduration) >= 4: - p1fl, success = optimize.leastsq(errfunc, p0[:], - args=(flduration, flpower)) - else: # pragma: no cover - p1fl = None - - # fitting WC data to three parameter CP model - if len(mlduration) >= 4: - p1ml, success = optimize.leastsq(errfunc, p0[:], - args=(mlduration, mlpower)) - else: # pragma: no cover - p1ml = None - - if len(mhduration) >= 4: - p1mh, success = optimize.leastsq(errfunc, p0[:], - args=(mhduration, mhpower)) - else: # pragma: no cover - p1mh = None - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpowerfh = fitfunc(p1fh, fitt) - fitpowerfl = fitfunc(p1fl, fitt) - fitpowerml = fitfunc(p1ml, fitt) - fitpowermh = fitfunc(p1mh, fitt) - - if normalized: - facfh = fitfunc(p1fh, 60) - facfl = fitfunc(p1fl, 60) - facml = fitfunc(p1ml, 60) - facmh = fitfunc(p1mh, 60) - fitpowerfh /= facfh - fitpowerfl /= facfl - fitpowermh /= facmh - fitpowerml /= facml - fhpower /= facfh - flpower /= facfl - mlpower /= facml - mhpower /= facmh - - sourcemh = ColumnDataSource( - data=dict( - mhduration=mhduration, - mhpower=mhpower, - ) - ) - - sourcefl = ColumnDataSource( - data=dict( - flduration=flduration, - flpower=flpower, - ) - ) - - sourcefh = ColumnDataSource( - data=dict( - fhduration=fhduration, - fhpower=fhpower, - ) - ) - - sourceml = ColumnDataSource( - data=dict( - mlduration=mlduration, - mlpower=mlpower, - ) - ) - - sourcefit = ColumnDataSource( - data=dict( - duration=fitt, - fitpowerfh=fitpowerfh, - fitpowerfl=fitpowerfl, - fitpowerml=fitpowerml, - fitpowermh=fitpowermh, - ) - ) - - x_axis_type = 'log' - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - plot = figure(width=900, x_axis_type=x_axis_type, - tools=TOOLS) - #plot.sizing_mode = 'stretch_both' - - plot.line('duration', 'fitpowerfh', source=sourcefit, - legend_label='Female HW', color='blue') - plot.line('duration', 'fitpowerfl', source=sourcefit, - legend_label='Female LW', color='red') - - plot.line('duration', 'fitpowerml', source=sourcefit, - legend_label='Male LW', color='green') - - plot.line('duration', 'fitpowermh', source=sourcefit, - legend_label='Male HW', color='orange') - - plot.circle('flduration', 'flpower', source=sourcefl, - fill_color='red', size=15) - - plot.circle('fhduration', 'fhpower', source=sourcefh, - fill_color='blue', size=15) - - plot.circle('mlduration', 'mlpower', source=sourceml, - fill_color='green', size=15) - - plot.circle('mhduration', 'mhpower', source=sourcemh, - fill_color='orange', size=15) - - plot.title.text = 'age '+str(age) - - plot.xaxis.axis_label = "Duration (seconds)" - if normalized: - plot.yaxis.axis_label = "Power (normalized)" - else: - plot.yaxis.axis_label = "Power (W)" - - script, div = components(plot) - - return script, div - - def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, cpfit='data', title='', type='water', diff --git a/rowers/urls.py b/rowers/urls.py index 0135688a..0b0f761e 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -280,14 +280,6 @@ urlpatterns = [ views.failed_job_view, name='failed_job_view'), re_path(r'^update_empower/$', views.rower_update_empower_view, name='rower_update_empower_view'), - re_path(r'^agegroupcp/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/user/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/(?P\d+)/user/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), re_path(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)/$', views.ajax_agegrouprecords, name='ajax_agegrouprecords'), re_path(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)m/$', From 4eb07335f86c00a2452eed72ded6614dbc420cca Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 15 Apr 2024 18:53:12 +0200 Subject: [PATCH 089/121] otw chart pandas to polars --- rowers/datautils.py | 6 +++--- rowers/interactiveplots.py | 18 ++++++++---------- rowers/views/analysisviews.py | 15 ++++++++------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/rowers/datautils.py b/rowers/datautils.py index cb17a357..c53d20cf 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -81,8 +81,8 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): p1 = p0 - thesecs = powerdf['Delta'] - theavpower = powerdf['CP'] + thesecs = powerdf['Delta'].to_numpy() + theavpower = powerdf['CP'].to_numpy() if len(thesecs) >= 4: try: @@ -102,7 +102,7 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): fitpower = fitfunc(p1, fitt) fitpoints = fitfunc(p1, thesecs) - fitpoints0 = fitpoints.copy() + fitpoints0 = fitpoints dd = fitpoints-theavpower ddmin = dd.min() diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index df373c11..e2c01858 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -996,7 +996,8 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, title='', type='water', wcpower=[], wcdurations=[], cpoverlay=False): - powerdf2 = powerdf[~(powerdf == 0).any(axis=1)].copy() + powerdf2 = powerdf.filter(pl.col("Delta") > 0, pl.col("CP") > 0) + # plot tools if (promember == 1): # pragma: no cover TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' @@ -1006,11 +1007,9 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, x_axis_type = 'log' deltas = powerdf2['Delta'].apply(lambda x: timedeltaconv(x)) - powerdf2['ftime'] = deltas.apply(lambda x: strfdelta(x)) - powerdf2['Deltaminutes'] = powerdf2['Delta']/60. - - source = ColumnDataSource( - data=powerdf2 + powerdf2 = powerdf2.with_columns( + ftime = deltas.apply(lambda x: strfdelta(x)), + Deltaminutes = pl.col("Delta")/60. ) # there is no Paul's law for OTW @@ -1071,7 +1070,7 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, fitpoweraverage = 0*fitpower - fit_data = pd.DataFrame(dict( + fit_data = pl.DataFrame(dict( CP=fitpower, CPmax=ratio*fitpower, duration=fitt/60., @@ -1085,13 +1084,12 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, # url = urls, )) - if not title: title = "Critical Power for "+rowername chart_dict = { - 'data': powerdf2.to_dict("records"), - 'fitdata': fit_data.to_dict("records"), + 'data': powerdf2.to_dicts(), + 'fitdata': fit_data.to_dicts(), 'title': title, } diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 414430c0..5c88125c 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -639,7 +639,7 @@ def cpdata(workouts, options): delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new( r, workouts) - powerdf = pd.DataFrame({ + powerdf = pl.DataFrame({ 'Delta': delta, 'CP': cpvalue, 'workout': workoutnames, @@ -648,16 +648,17 @@ def cpdata(workouts, options): savedata = options.get('savedata',False) if savedata: # pragma: no cover - return powerdf + return powerdf.to_pandas() - if powerdf.empty: # pragma: no cover + if powerdf.is_empty(): # pragma: no cover return('', '

      No valid data found

      ') - powerdf = powerdf[powerdf['CP'] > 0] - powerdf.dropna(axis=0, inplace=True) - powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True) - powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True) + powerdf = powerdf.lazy().filter(pl.col("CP")>0) + powerdf = powerdf.sort(["Delta", "CP"], descending=[False, True]) + powerdf = powerdf.unique(subset="Delta", keep="first") + powerdf = powerdf.fill_nan(None).drop_nulls() + powerdf = powerdf.collect() rowername = r.user.first_name+" "+r.user.last_name From 0c306c7914827b832df3bf369c00a49c13a0316b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 15 Apr 2024 19:55:04 +0200 Subject: [PATCH 090/121] instroke interactive as much polars as works --- rowers/interactiveplots.py | 486 ++-------------------------------- rowers/views/analysisviews.py | 13 - 2 files changed, 28 insertions(+), 471 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e2c01858..c04dd167 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1177,440 +1177,6 @@ def interactive_agegroup_plot(df, distance=2000, duration=None, return script, div - -def interactive_cpchart(rower, thedistances, thesecs, theavpower, - theworkouts, promember=0, - wcpower=[], wcdurations=[]): - - message = 0 - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - x_axis_type = 'log' - - thesecs = pd.Series(thesecs) - - velo = thedistances/thesecs - p = pd.Series(500./velo) - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source = ColumnDataSource( - data=dict( - dist=thedistances, - duration=thesecs, - spm=0*theavpower, - tim=niceformat( - thesecs.fillna(method='ffill').apply( - lambda x: timedeltaconv(x)) - ), - - power=theavpower, - fpace=nicepaceformat(p2), - ) - ) - - # fitting the data to Paul - if len(thedistances) >= 2: - paulslope, paulintercept, r, p, stderr = linregress( - np.log10(thedistances), p) - else: # pragma: no cover - paulslope = 5.0/np.log10(2.0) - paulintercept = p[0]-paulslope*np.log10(thedistances[0]) - - fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.) - - fitp = paulslope*fitx+paulintercept - - fitvelo = 500./fitp - fitpower = 2.8*(fitvelo**3) - fitt = 10**fitx/fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - sourcepaul = ColumnDataSource( - data=dict( - dist=10**fitx, - duration=fitt, - power=fitpower, - spm=0*fitpower, - tim=niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - fpace=nicepaceformat(fitp2), - ) - ) - - def fitfunc(pars, x): - return pars[0] / (1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - - def errfunc(pars, x, y): - return fitfunc(pars, x)-y - - # p0 = [500,350,10,8000] - wcpower = pd.Series(wcpower, dtype='float') - wcdurations = pd.Series(wcdurations, dtype='float') - - # fitting WC data to three parameter CP model - if len(wcdurations) >= 4: - p1wc, success = optimize.leastsq(errfunc, p0[:], - args=(wcdurations, wcpower)) - else: # pragma: no cover - p1wc = None - - # fitting the data to three parameter CP model - - success = 0 - p1 = p0 - if len(thesecs) >= 4: - try: - p1, success = optimize.leastsq( - errfunc, p0[:], args=(thesecs, theavpower)) - except (RuntimeError, RuntimeWarning): # pragma: no cover - factor = fitfunc(p0, thesecs.mean())/theavpower.mean() - p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] - success = 0 - else: # pragma: no cover - factor = fitfunc(p0, thesecs.mean())/theavpower.mean() - p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] - success = 0 - - # Get stayer score - if success == 1: # pragma: no cover - power4min = fitfunc(p1, 240.) - power1h = fitfunc(p1, 3600.) - power10sec = fitfunc(p1, 10.) - r10sec4min = 100.*power10sec/power4min - r1h4min = 100.*power1h/power4min - - combined = r1h4min-0.2*(r10sec4min-100) - - dataset = pd.read_csv('static/stats/combined_set.csv') - - stayerscore = int(percentileofscore(dataset['combined'], combined)) - else: - stayerscore = None - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpower = fitfunc(p1, fitt) - if p1wc is not None: - fitpowerwc = 0.95*fitfunc(p1wc, fitt) - fitpowerexcellent = 0.7*fitfunc(p1wc, fitt) - fitpowergood = 0.6*fitfunc(p1wc, fitt) - fitpowerfair = 0.5*fitfunc(p1wc, fitt) - fitpoweraverage = 0.4*fitfunc(p1wc, fitt) - - else: # pragma: no cover - fitpowerwc = 0*fitpower - fitpowerexcellent = 0*fitpower - fitpowergood = 0*fitpower - fitpowerfair = 0*fitpower - fitpoweraverage = 0*fitpower - - message = "" - if len(fitpower[fitpower < 0]) > 0: # pragma: no cover - message = "CP model fit didn't give correct results" - - fitvelo = (fitpower/2.8)**(1./3.) - fitdist = fitt*fitvelo - fitp = 500./fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - sourcecomplex = ColumnDataSource( - data=dict( - dist=fitdist, - duration=fitt, - tim=niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - spm=0*fitpower, - power=fitpower, - fitpowerwc=fitpowerwc, - fitpowerexcellent=fitpowerexcellent, - fitpowergood=fitpowergood, - fitpowerfair=fitpowerfair, - fitpoweraverage=fitpoweraverage, - fpace=nicepaceformat(fitp2), - ) - ) - - # making the plot - plot = figure(tools=TOOLS, x_axis_type=x_axis_type, - width=900, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'scale_both' - - plot.image_url([watermarkurl], 1.8*max(thesecs), watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - y_range_name="watermark", - ) - - plot.circle('duration', 'power', source=source, fill_color='red', size=15, - legend_label='Power') - plot.xaxis.axis_label = "Duration (seconds)" - plot.yaxis.axis_label = "Power (W)" - - if stayerscore is not None: # pragma: no cover - plot.add_layout( - Label(x=100, y=100, x_units='screen', y_units='screen', - text='Stayer Score '+str(stayerscore)+'%', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black') - ) -# plot.add_layout( -# Label(x=100,y=120,x_units='screen',y_units='screen', -# text='Stayer Score (6min) '+str(stayerscore2)+'%', -# background_fill_alpha=0.7, -# background_fill_color='white', -# text_color='black') -# ) - - cpdata = dataprep.fetchcperg(rower, theworkouts) - - if cpdata.empty: # pragma: no cover - message = 'Calculations are running in the background. Please refresh this page to see updated results' - return ['', '', paulslope, paulintercept, p1, message, p1wc] - - velo = cpdata['distance']/cpdata['delta'] - - p = 500./velo - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source2 = ColumnDataSource( - data=dict( - duration=cpdata['delta'], - power=cpdata['cp'], - tim=niceformat( - cpdata['delta'].fillna(method='ffill').apply( - lambda x: timedeltaconv(x)) - ), - dist=cpdata['distance'], - pace=nicepaceformat(p2), - ) - ) - - plot.circle('duration', 'power', source=source2, - fill_color='blue', size=3, - legend_label='Power from segments') - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Duration ', '@tim'), - ('Power (W)', '@power{int}'), - ('Distance (m)', '@dist{int}'), - ('Pace (/500m)', '@fpace'), - ]) - - hover.mode = 'mouse' - - plot.y_range = Range1d(0, 1.5*max(theavpower)) - plot.x_range = Range1d(1, 2*max(thesecs)) - plot.legend.orientation = "vertical" - - plot.line('duration', 'power', source=sourcepaul, - legend_label="Paul's Law") - plot.line('duration', 'power', source=sourcecomplex, legend_label="CP Model", - color='green') - if p1wc is not None: - plot.line('duration', 'fitpowerwc', source=sourcecomplex, - legend_label="World Class", - color='Maroon', line_dash='dotted') - - plot.line('duration', 'fitpowerexcellent', source=sourcecomplex, - legend_label="90% percentile", - color='Purple', line_dash='dotted') - - plot.line('duration', 'fitpowergood', source=sourcecomplex, - legend_label="75% percentile", - color='Olive', line_dash='dotted') - - plot.line('duration', 'fitpowerfair', source=sourcecomplex, - legend_label="50% percentile", - color='Gray', line_dash='dotted') - - plot.line('duration', 'fitpoweraverage', source=sourcecomplex, - legend_label="25% percentile", - color='SkyBlue', line_dash='dotted') - - script, div = components(plot) - - return [script, div, paulslope, paulintercept, p1, message, p1wc] - - -def interactive_windchart(id=0, promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = figure(width=400, height=300) - - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - - rr = rrower(hrmax=r.max, hrut2=r.ut2, - hrut1=r.ut1, hrat=r.at, - hrtr=r.tr, hran=r.an, ftp=r.ftp) - - rowdata = rdata(f1, rower=rr) - if rowdata == 0: # pragma: no cover - return 0 - - try: - dist = rowdata.df.loc[:, 'cum_dist'] - except KeyError: - return ['', 'No Data Found'] - - try: # pragma: no cover - vwind = rowdata.df.loc[:, 'vwind'] - winddirection = rowdata.df.loc[:, 'winddirection'] - bearing = rowdata.df.loc[:, 'bearing'] - except KeyError: - rowdata.add_wind(0, 0) - rowdata.add_bearing() - vwind = rowdata.df.loc[:, 'vwind'] - winddirection = rowdata.df.loc[:, 'winddirection'] - bearing = rowdata.df.loc[:, 'winddirection'] - rowdata.write_csv(f1, gzip=True) - dataprep.update_strokedata(id, rowdata.df) - - winddirection = winddirection % 360 - winddirection = (winddirection + 360) % 360 - - tw = tailwind(bearing, vwind, 1.0*winddirection) - - source = ColumnDataSource( - data=dict( - dist=dist, - vwind=vwind, - tw=tw, - winddirection=winddirection, - ) - ) - - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - # making the plot - plot = figure(tools=TOOLS, width=400, height=500, - # toolbar_location="below", - toolbar_sticky=False, - ) - plot.line('dist', 'vwind', source=source, legend_label="Wind Speed (m/s)") - plot.line('dist', 'tw', source=source, - legend_label="Tail (+)/Head (-) Wind (m/s)", color='black') - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - # plot.title.text_font_size="1.0em" - plot.title.text_font = "1.0em" - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "Wind Speed (m/s)" - plot.y_range = Range1d(-7, 7) - #plot.sizing_mode = 'stretch_both' - - plot.extra_y_ranges = {"winddirection": Range1d(start=0, end=360)} - plot.line('dist', 'winddirection', source=source, - legend_label='Wind Direction', color="red", - y_range_name="winddirection") - plot.add_layout(LinearAxis(y_range_name="winddirection", - axis_label="Wind Direction (degree)"), 'right') - - script, div = components(plot) - - return [script, div] - - -def interactive_streamchart(id=0, promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = figure(width=400, - ) - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - - rr = rrower(hrmax=r.max, hrut2=r.ut2, - hrut1=r.ut1, hrat=r.at, - hrtr=r.tr, hran=r.an, ftp=r.ftp) - - rowdata = rdata(f1, rower=rr) - if rowdata == 0: # pragma: no cover - return "", "No Valid Data Available" - - try: - dist = rowdata.df.loc[:, 'cum_dist'] - except KeyError: - return ['', 'No Data found'] - - try: - vstream = rowdata.df.loc[:, 'vstream'] - except KeyError: - rowdata.add_stream(0) - vstream = rowdata.df.loc[:, 'vstream'] - rowdata.write_csv(f1, gzip=True) - dataprep.update_strokedata(id, rowdata.df) - - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - # making the plot - plot = figure(tools=TOOLS, width=400, height=500, - # toolbar_location="below", - toolbar_sticky=False, - ) - plot.line(dist, vstream, legend_label="River Stream Velocity (m/s)") - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "River Current (m/s)" - plot.y_range = Range1d(-2, 2) - #plot.sizing_mode = 'stretch_both' - - script, div = components(plot) - - return [script, div] - def forcecurve_multi_interactive_chart(selected): # pragma: no cover ids = [analysis.id for analysis in selected] workoutids = [analysis.workout.id for analysis in selected] @@ -1678,12 +1244,13 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co maximum_values[analysis.metric] = mean_vals.max() xvals = np.arange(len(mean_vals)) - data2 = pd.DataFrame({ - 'x': pd.Series(xvals), - 'y': pd.Series(mean_vals), + data2 = pl.DataFrame({ + 'x': pl.Series(xvals), + 'y': pl.Series(mean_vals), }) - data2['id'] = cntr + data2 = data2.with_columns((pl.lit(cntr)).alias("id")) + df2.append(data2) legendlabel = '{name} - {metric} - {workout}'.format( @@ -1704,9 +1271,9 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co ytitle = 'Scaled' cntr = cntr+1 - df2 = pd.concat(df2, axis=0) + df2 = pl.concat(df2) - data_dict = df2.to_dict("records") + data_dict = df2.to_dicts() chart_data = { 'title': '', @@ -1725,24 +1292,25 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, individual_curves, name='',notes=''): # pragma: no cover - df_pos = (df+abs(df))/2. - df_min = -(-df+abs(-df))/2. - if df.empty: return "", "No data in selection" - mean_vals = df.median().replace(0, np.nan) - q75 = df_pos.quantile(q=0.75).replace(0,np.nan) - q25 = df_pos.quantile(q=0.25).replace(0,np.nan) - q75min = df_min.quantile(q=0.75).replace(0,np.nan) - q25min = df_min.quantile(q=0.25).replace(0,np.nan) + df_pos = (df+abs(df))/2. + df_min = -(-df+abs(-df))/2. + + + mean_vals = df.median().replace(0, np.nan) + q75 = df_pos.quantile(q = 0.75).replace(0, np.nan) + q25 = df_pos.quantile(q=0.25).replace(0, np.nan) + q75min = df_min.quantile(q=0.75).replace(0, np.nan) + q25min = df_min.quantile(q=0.25).replace(0, np.nan) mean_vals = mean_vals.interpolate() xvals = np.arange(len(mean_vals)) - df_plot = pd.DataFrame({ + df_plot = pl.DataFrame({ 'x':xvals, 'median':mean_vals, 'high':q75, @@ -1751,14 +1319,17 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, 'low 2': q25, }) - df_plot['high'].update(df_plot.pop('high 2')) - df_plot['low'].update(df_plot.pop('low 2')) - try: - df_plot.interpolate(axis=1,inplace=True) - except TypeError: - pass + df_plot = df_plot.with_columns( + pl.coalesce(["high", "high 2"]).alias("high") + ) + + df_plot = df_plot.with_columns( + pl.coalesce("low", "low 2").alias("low") + ) + + df_plot = df_plot.drop(["high 2", "low 2"]) + df_plot = df_plot.drop_nulls() - if metric == 'boat accelerator curve': ytitle = "Boat acceleration (m/s^2)" elif metric == 'instroke boat speed': @@ -1771,7 +1342,7 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, lines_dict = df.to_dict("records") - data_dict = df_plot.to_dict("records") + data_dict = df_plot.to_dicts() chart_data = { 'lines': lines_dict, @@ -1790,7 +1361,6 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, 'analysis_name': name, } - script, div = get_chart("/instroke", chart_data, debug=False) return script, div diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 5c88125c..58566c67 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1523,19 +1523,6 @@ def otwcp_toadmin_view(request, theuser=0, return response -def agegroupcpview(request, age, normalize=0, userid=0): - script, div = interactive_agegroupcpchart(age, normalized=normalize) - - response = render(request, 'agegroupcp.html', - { - 'active': 'nav-analysis', - 'interactiveplot': script, - 'the_div': div, - } - ) - - return response - def agegrouprecordview(request, sex='male', weightcategory='hwt', distance=2000, duration=None): From 0d819a5316a354df6c024425a85e21c64ffe36dd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 15 Apr 2024 21:33:37 +0200 Subject: [PATCH 091/121] interactive_plot pandas to polars --- rowers/interactiveplots.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c04dd167..a0a50b36 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1375,26 +1375,24 @@ def interactive_chart(id=0, promember=0, intervaldata={}): TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) - - - datadf.dropna(axis=0, how='any', inplace=True) + datadf = dataprep.getsmallrowdata_pl(columns, ids=[id]) + datadf = datadf.fill_nan(None).drop_nulls() row = Workout.objects.get(id=id) - if datadf.empty: + if datadf.is_empty(): return "", "No Valid Data Available" try: _ = datadf['spm'] except KeyError: # pragma: no cover - datadf['spm'] = 0 + datadf = datadf.with_columns((pl.lit(0)).alias("spm")) try: _ = datadf['pace'] except KeyError: # pragma: no cover - datadf['pace'] = 0 + datadf = datadf.with_columns((pl.lit(0)).alias("pace")) - data_dict = datadf.to_dict("records") + data_dict = datadf.to_dicts() metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] From e6826d75c59e2630f934b47c16bcee91e20f62ea Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Apr 2024 13:17:36 +0200 Subject: [PATCH 092/121] fixing some tests, fixes for failing tests --- rowers/dataprep.py | 4 +++- rowers/datautils.py | 2 ++ rowers/interactiveplots.py | 28 +++++++++++++++++--------- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/test_urls.py | 5 ----- rowers/tests/testdata/testdata.tcx.gz | Bin 3998 -> 3999 bytes rowers/tests/viewnames.csv | 2 -- 7 files changed, 24 insertions(+), 19 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index c6f8049d..c69aeffa 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -820,7 +820,9 @@ def update_rolling_cp(r, types, mode='water', dosend=False): powerdf.dropna(axis=0, inplace=True) powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True) powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True) - + if powerdf.empty: + return False + res2 = datautils.cpfit(powerdf) p1 = res2[0] # calculate FTP diff --git a/rowers/datautils.py b/rowers/datautils.py index c53d20cf..c156f307 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -83,6 +83,7 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): thesecs = powerdf['Delta'].to_numpy() theavpower = powerdf['CP'].to_numpy() + if len(thesecs) >= 4: try: @@ -95,6 +96,7 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): else: factor = fitfunc(p0, thesecs.mean())/theavpower.mean() p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] + p1 = [abs(p) for p in p1] fitt = pd.Series(10**(4*np.arange(100)/100.)) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index a0a50b36..1fba7f43 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -340,7 +340,9 @@ def interactive_boxchart(datadf, fieldname, extratitle='', if 'date' not in columns: # pragma: no cover return '', 'Not enough data' - datadf = datadf.with_columns((pl.col("date").apply(lambda x:x.strftime("%Y-%m-%d"))).alias("date")) + + + datadf = datadf.with_columns((pl.col("date").dt.strftime("%Y-%m-%d")).alias("date")) datadf = datadf.with_columns((pl.col(fieldname)).alias("value")) data_dict = datadf.to_dicts() @@ -357,7 +359,7 @@ def interactive_boxchart(datadf, fieldname, extratitle='', def interactive_planchart(data, startdate, enddate): # data = data.melt(id_vars=['startdate'], value_vars=['executed', 'planned']) - data = data.with_columns((pl.col("startdate").apply(lambda x: x.strftime("%Y-%m-%d"))).alias("startdate")) + data = data.with_columns((pl.col("startdate").dt.strftime("%Y-%m-%d")).alias("startdate")) data_dict = data.to_dicts() chart_data = { 'data': data_dict, @@ -625,18 +627,18 @@ def goldmedalscorechart(user, startdate=None, enddate=None): 'id': workoutid, }) - df = df.with_columns((pl.col("id").apply(lambda x: settings.SITE_URL + + df = df.with_columns((pl.col("id").map_elements(lambda x: settings.SITE_URL + '/rowers/workout/{id}/'.format(id=encoder.encode_hex(x)))).alias("url")) - df = df.with_columns((pl.col("id").apply(lambda x: workoutname(x))).alias("workout")) + df = df.with_columns((pl.col("id").map_elements(lambda x: workoutname(x))).alias("workout")) df = df.sort('date') # find index values where score is max dfmax = df.group_by("date", maintain_order=True).max() dfmax = dfmax.fill_nan(0) - dfmax = dfmax.with_columns((pl.col("date").apply(lambda x: x.strftime("%Y-%m-%d"))).alias("date")) - dfmax = dfmax.with_columns((pl.col("duration").apply(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("duration")) - dfmax = dfmax.with_columns((pl.col("markerduration").apply(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("markerduration")) + dfmax = dfmax.with_columns((pl.col("date").dt.strftime("%Y-%m-%d")).alias("date")) + dfmax = dfmax.with_columns((pl.col("duration").map_elements(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("duration")) + dfmax = dfmax.with_columns((pl.col("markerduration").map_elements(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("markerduration")) data_dicts = dfmax.to_dicts() chart_data = { @@ -764,7 +766,7 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= "fatigue":df['fatigue'], "form":df['form'], "impulse":df['impulse'], - "date": df['date'].apply(lambda x: x.strftime('%Y-%m-%d')), + "date": df['date'].dt.strftime('%Y-%m-%d'), }) @@ -987,7 +989,7 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): 'trajectories': trajectories, } - script, div = get_chart("/mapcompare", mapdata, debug=True) + script, div = get_chart("/mapcompare", mapdata, debug=False) return script, div @@ -1267,7 +1269,9 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co if len(metrics) > 1: cntr = 1 for analysis in selected: - df2[cntr-1]['y'] = df2[cntr-1]['y'] / maximum_values[analysis.metric] + df2[cntr-1] = df2[cntr-1].with_columns( + (pl.col("y")/ maximum_values[analysis.metric]) + ) ytitle = 'Scaled' cntr = cntr+1 @@ -1376,7 +1380,11 @@ def interactive_chart(id=0, promember=0, intervaldata={}): columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] datadf = dataprep.getsmallrowdata_pl(columns, ids=[id]) + if datadf.is_empty(): + return "", "No Valid Data Available" + datadf = datadf.fill_nan(None).drop_nulls() + row = Workout.objects.get(id=id) if datadf.is_empty(): diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 6b20a563..a8b97447 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -674,7 +674,7 @@ class InteractivePlotTests(TestCase): def test_interactive_boxchart(self): df = pl.read_csv('rowers/tests/testdata/boxplotdata.csv') - df = df.with_columns(pl.col("date").apply(lambda x:datetime.datetime.strptime(x, "%Y-%m-%d"))) + df = df.with_columns(pl.col("date").dt.strftime("%Y-%m-%d")) script, div = interactiveplots.interactive_boxchart(df, 'spm') diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index b522fd2d..5d19c820 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -79,11 +79,6 @@ class URLTests(TestCase): '/rowers/502/', '/rowers/about/', '/rowers/workout/addmanual/', - '/rowers/agegroupcp/30/', - '/rowers/agegroupcp/30/1/', - '/rowers/agegrouprecords/male/hwt/', - '/rowers/agegrouprecords/male/hwt/2000m/', - '/rowers/agegrouprecords/male/hwt/30min/', '/rowers/ajax_agegroup/45/hwt/male/1/', '/rowers/analysis/', '/rowers/analysis/user/1/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index ac4ffb0c87e4512084761e3ace406f9fea46db4f..62c65b68b6e7bcbae639a5433d0bde8f0bbe803e 100644 GIT binary patch delta 257 zcmV+c0sj7;ADGkU5<>k-)ExnKF&?^t;!NpZ42sxO}uDI@OA07VuEVJ4Ex!e9r zdW)aEKK)~v?hN1_!0!Kp#g#91Pk#HD4)1JHuh!`~JWME;7mw1@_#<8V@Xr4LT7Xbc H#DD<+l<1LA delta 256 zcmV+b0ssD=AD$lvABzYGi0vD(2dNH!4C3sP*ui(l{SOC=-2r!YXZrXX^XJP?tKIhp z4=)nOvx^V=KI_ukb8+@|)2&~2Cnwvdl|HW4$1l6SOXux&Z>9~UjulrYL7gt`B z2j}M}tD~+@KmGE>xIFh)CrjV`^m=vj^77~Ymfpv7=#_`_VawC*Kk1U$S#b4l^Vhqr z)@iesPy1~*Tt3<_&VTSC-SpwF%adidStbto(}zcQ<-QJ{bnD~Cj}Cu+mf39o+-?6Q zy~WR7pZ>8-cLs0|VE6yQ;>s7hC%=76hj+H9SL^f~9wwB_i%02c{E;qwc;|o7@Zavl GfB^ut%#x)5 diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index 2f341b8d..245dbb17 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -1,9 +1,7 @@ ,id,view,function,anonymous,anonymous_response,own,own_response,own_nonperm,member,member_response,member_nonperm,coachee,coachee_response,coachee_nonperm,is_staff,userid,workoutid,dotest,realtest,kwargs 0,0,workouts_summaries_email_view,sends summary excel with workouts list and links to data to user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE, 1,1,rower_update_empower_view,updates old Empower Oarlock files (corrects Power bug),TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, -2,2,agegroupcpview,needs age,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 3,4,ajax_agegrouprecords,gets age group records from C2 ,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, -5,6,agegrouprecordview,shows ergo age group records,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 6,7,workouts_view,workouts list,TRUE,302,basic,200,302,basic,200,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE, 7,8,virtualevents_view,virtual races list,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 8,9,virtualevent_create_view,create virtual event,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, From 4efe88c56d1c9569e8e93c4ae2cd8371e0bef8da Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Apr 2024 13:30:35 +0200 Subject: [PATCH 093/121] fixing broken tests --- rowers/interactiveplots.py | 2 +- rowers/tests/test_unit_tests.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1fba7f43..f28c2956 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -998,7 +998,7 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, title='', type='water', wcpower=[], wcdurations=[], cpoverlay=False): - powerdf2 = powerdf.filter(pl.col("Delta") > 0, pl.col("CP") > 0) + powerdf2 = powerdf.filter((pl.col("Delta") > 0) & (pl.col("CP") > 0)) # plot tools if (promember == 1): # pragma: no cover diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index a8b97447..650362d8 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -674,7 +674,7 @@ class InteractivePlotTests(TestCase): def test_interactive_boxchart(self): df = pl.read_csv('rowers/tests/testdata/boxplotdata.csv') - df = df.with_columns(pl.col("date").dt.strftime("%Y-%m-%d")) + df = df.with_columns(pl.col("date").str.to_datetime("%Y-%m-%d")) script, div = interactiveplots.interactive_boxchart(df, 'spm') @@ -692,7 +692,7 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) def test_interactive_otwcpchart(self): - df = pd.read_csv('rowers/tests/testdata/otwcp.csv') + df = pl.read_csv('rowers/tests/testdata/otwcp.csv') script, div, p1, ratio, message = interactiveplots.interactive_otwcpchart(df,r=self.r,cpfit='data') self.assertFalse(len(script)==0) From 9568b6d132aab9bf5d160568fe99823b184203fc Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Apr 2024 13:38:22 +0200 Subject: [PATCH 094/121] start --- rowers/interactiveplots.py | 2 +- rowers/views/analysisviews.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f28c2956..8fa6c246 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1549,7 +1549,7 @@ def interactive_multiflex(datadf, xparam, yparam, groupby, extratitle='', 'groupname': groupname, } - script, div = get_chart("/trendflex", chart_data) + script, div = get_chart("/trendflex", chart_data,debug=False) return script, div diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 58566c67..418132e4 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -475,8 +475,8 @@ def trendflexdata(workouts, options, userid=0): 'groupsize': groupsize, }) - if yparam == 'pace': - df['y'] = dataprep.paceformatsecs(df['y']/1.0e3) + #if yparam == 'pace': + # df['y'] = dataprep.paceformatsecs(df['y']/1.0e3) aantal = len(df) From 0936d4ceccce5ae7a4b9c152877aecd0d3520c30 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Apr 2024 14:14:00 +0200 Subject: [PATCH 095/121] flex chart pandas to polars --- rowers/interactiveplots.py | 147 ++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 74 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8fa6c246..8b1d78e4 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1694,10 +1694,10 @@ def interactive_flexchart_stacked(id, r, xparam='time', row = Workout.objects.get(id=id) if ispromember(r.user): - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, + rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=True, workstrokesonly=False, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True, + rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=True, workstrokesonly=False, for_chart=True) if r.usersmooth > 1: # pragma: no cover @@ -1706,65 +1706,69 @@ def interactive_flexchart_stacked(id, r, xparam='time', if metricsdicts[column]['maysmooth']: nrsteps = int(log2(r.usersmooth)) for i in range(nrsteps): - rowdata[column] = utils.ewmovingaverage( - rowdata[column], 5) + rowdata = rowdata.with_columns( + utils.ewmovingaverage( + rowdata[column], 5).alias(column) + ) except KeyError: pass if len(rowdata) < 2: if ispromember(r.user): - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], + rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], + rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) - if rowdata.empty: + if rowdata.is_empty(): return "", "No valid data" try: - tseconds = rowdata.loc[:, 'time'] + tseconds = rowdata['time'] except KeyError: # pragma: no cover return '', 'No time data - cannot make flex plot' try: - rowdata['x1'] = rowdata.loc[:, xparam] + rowdata = rowdata.with_columns(x1=pl.col(xparam)) except KeyError: # pragma: no cover - rowdata['x1'] = 0*rowdata.loc[:, 'time'] + rowdata = rowdata.with_columns(x1=pl.lit(0)) try: - rowdata['y1'] = rowdata.loc[:, yparam1] + rowdata = rowdata.with_columns(y1=pl.col(yparam1)) except KeyError: # pragma: no cover - rowdata['y1'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam1] = rowdata['y1'] - - try: # pragma: no cover - rowdata['y2'] = rowdata.loc[:, yparam2] - except KeyError: - rowdata['y2'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam2] = rowdata['y2'] + rowdata = rowdata.with_columns(y1=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y1")).alias(yparam1)) try: - rowdata['y3'] = rowdata.loc[:, yparam3] + rowdata = rowdata.with_columns(y2=pl.col(yparam2)) except KeyError: # pragma: no cover - rowdata['y3'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam3] = rowdata['y3'] + rowdata = rowdata.with_columns(y2=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y2")).alias(yparam2)) try: - rowdata['y4'] = rowdata.loc[:, yparam4] + rowdata = rowdata.with_columns(y1=pl.col(yparam3)) except KeyError: # pragma: no cover - rowdata['y4'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam4] = rowdata['y4'] + rowdata = rowdata.with_columns(y3=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y3")).alias(yparam3)) + + try: + rowdata = rowdata.with_columns(y4=pl.col(yparam1)) + except KeyError: # pragma: no cover + rowdata = rowdata.with_columns(y4=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y4")).alias(yparam4)) + # replace nans - rowdata.fillna(value=0, inplace=True) + rowdata = rowdata.fill_nan(0) - data_dict = rowdata.to_dict("records") + + data_dict = rowdata.to_dicts() metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] @@ -1794,7 +1798,6 @@ def interactive_flex_chart2(id, r, promember=0, trendline=False, mode='rower'): - columns = [name for name, d in metrics.rowingmetrics] columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] columns = columns + ['spm', 'driveenergy', 'distance'] @@ -1802,10 +1805,10 @@ def interactive_flex_chart2(id, r, promember=0, datadf = pd.DataFrame() if promember: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, + rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=True, workstrokesonly=workstrokesonly, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=True, + rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=True, workstrokesonly=workstrokesonly, for_chart=True) if r.usersmooth > 1: # pragma: no cover @@ -1814,19 +1817,21 @@ def interactive_flex_chart2(id, r, promember=0, if metricsdicts[column]['maysmooth']: nrsteps = int(log2(r.usersmooth)) for i in range(nrsteps): - rowdata[column] = utils.ewmovingaverage( - rowdata[column], 5) + rowdata = rowdata.with_columns( + (utils.ewmovingaverage( + rowdata[column], 5)).alias(column) + ) except KeyError: pass try: if len(rowdata) < 2: if promember: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], + rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_db(columns_basic, ids=[id], doclean=False, + rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) workstrokesonly = False except (KeyError, TypeError): # pragma: no cover @@ -1845,67 +1850,56 @@ def interactive_flex_chart2(id, r, promember=0, try: _ = rowdata['driveenergy'].mean() except (KeyError, TypeError): - rowdata['driveenergy'] = 500. + rowdata = rowdata.with_columns(driveenergy=pl.lit(500)) # test if we have power try: _ = rowdata['power'].mean() except (KeyError, TypeError): - rowdata['power'] = 50. + rowdata = rowdata.with_columns(power=pl.lit(50)) # replace nans - rowdata.fillna(value=0, inplace=True) + rowdata = rowdata.fill_nan(0) row = Workout.objects.get(id=id) - if rowdata.empty: + if rowdata.is_empty(): return "", "No valid data", workstrokesonly workoutstatesrest = [3] if workstrokesonly: # pragma: no cover try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] + rowdata = rowdata.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) except KeyError: pass try: - tseconds = rowdata.loc[:, 'time'] + tseconds = rowdata['time'] except KeyError: # pragma: no cover return '', 'No time data - cannot make flex plot', workstrokesonly try: - rowdata['x1'] = rowdata.loc[:, xparam] + rowdata = rowdata.with_columns(x1 = pl.col(xparam)) except KeyError: # pragma: no cover - rowdata['x1'] = 0*rowdata.loc[:, 'time'] + rowdata = rowdata.with_columns(x1 = pl.col("time")) try: - rowdata['y1'] = rowdata.loc[:, yparam1] + rowdata = rowdata.with_columns(y1 = pl.col(yparam1)) except KeyError: # pragma: no cover - rowdata['y1'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam1] = rowdata['y1'] + rowdata = rowdata.with_columns(y1 = pl.col("time")) + rowdata = rowdata.with_columns(yparam1 = pl.col("y1")) + if yparam2 != 'None': try: - rowdata['y2'] = rowdata.loc[:, yparam2] + rowdata = rowdata.with_columns(y2 = pl.col(yparam2)) except KeyError: # pragma: no cover - rowdata['y2'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam2] = rowdata['y2'] - else: # pragma: no cover - rowdata['y2'] = rowdata['y1'] + rowdata = rowdata.with_columns(y2 = pl.col("time")) + rowdata = rowdata.with_columns(yparam2 = pl.col("y2")) - if xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'distance' or xparam == 'cumdist': - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() else: # pragma: no cover - try: - xaxmax = get_yaxmaxima(r, xparam, mode) - xaxmin = get_yaxminima(r, xparam, mode) - except KeyError: - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() + rowdata = rowdata.with_columns(y2=pl.col("y1")) + # average values if xparam != 'time': @@ -1921,20 +1915,23 @@ def interactive_flex_chart2(id, r, promember=0, try: - rowdata['xname'] = axlabels[xparam] + rowdata = rowdata.with_columns((pl.lit(axlabels[xparam])).alias("xname")) except KeyError: # pragma: no cover - rowdata['xname'] = xparam + rowdata = rowdata.with_columns((pl.lit(xparam)).alias("xname")) + try: - rowdata['yname1'] = axlabels[yparam1] + rowdata = rowdata.with_columns((pl.lit(axlabels[yparam1])).alias("yname1")) except KeyError: # pragma: no cover - rowdata['yname1'] = yparam1 + rowdata = rowdata.with_columns((pl.lit(yparam1)).alias("yname1")) + if yparam2 != 'None': try: - rowdata['yname2'] = axlabels[yparam2] + rowdata = rowdata.with_columns((pl.lit(axlabels[yparam2])).alias("yname2")) except KeyError: # pragma: no cover - rowdata['yname2'] = yparam2 + rowdata = rowdata.with_columns((pl.lit(yparam2)).alias("yname2")) + else: # pragma: no cover - rowdata['yname2'] = rowdata['yname1'] + rowdata = rowdata.with_columns((pl.col("yname1")).alias("yname2")) def func(x, a, b): return a*x+b @@ -1944,13 +1941,15 @@ def interactive_flex_chart2(id, r, promember=0, try: popt, pcov = optimize.curve_fit(func, x1, y1) ytrend = func(x1, popt[0], popt[1]) - rowdata['ytrend'] = ytrend + rowdata = rowdata.with_columns(ytrend=ytrend) except TypeError: # pragma: no cover - rowdata['ytrend'] = y1 + rowdata = rowdata.with_columns(ytrend=pl.col("y1")) - rowdata.replace([np.inf, -np.inf], np.nan, inplace=True) - rowdata.dropna(axis=1, how='all', inplace=True) - data_dict = rowdata.to_dict("records") + + #rowdata = rowdata.replace([np.inf, -np.inf], np.nan) + rowdata = rowdata.fill_nan(None).drop_nulls() + + data_dict = rowdata.to_dicts() metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] From eec03965c837bf972da6a3456af087e6d0b9628b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Apr 2024 15:47:03 +0200 Subject: [PATCH 096/121] fixing some tests --- rowers/interactiveplots.py | 244 +++----------------------- rowers/tests/mocks.py | 6 + rowers/tests/statements.py | 1 + rowers/tests/test_unit_tests.py | 4 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 3999 bytes 5 files changed, 29 insertions(+), 226 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8b1d78e4..d5a2842a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1731,35 +1731,35 @@ def interactive_flexchart_stacked(id, r, xparam='time', try: tseconds = rowdata['time'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return '', 'No time data - cannot make flex plot' try: rowdata = rowdata.with_columns(x1=pl.col(xparam)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(x1=pl.lit(0)) try: rowdata = rowdata.with_columns(y1=pl.col(yparam1)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y1=pl.col("time")) rowdata = rowdata.with_columns((pl.col("y1")).alias(yparam1)) try: rowdata = rowdata.with_columns(y2=pl.col(yparam2)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y2=pl.col("time")) rowdata = rowdata.with_columns((pl.col("y2")).alias(yparam2)) try: rowdata = rowdata.with_columns(y1=pl.col(yparam3)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y3=pl.col("time")) rowdata = rowdata.with_columns((pl.col("y3")).alias(yparam3)) try: rowdata = rowdata.with_columns(y4=pl.col(yparam1)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y4=pl.col("time")) rowdata = rowdata.with_columns((pl.col("y4")).alias(yparam4)) @@ -1800,8 +1800,8 @@ def interactive_flex_chart2(id, r, promember=0, columns = [name for name, d in metrics.rowingmetrics] columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] - columns = columns + ['spm', 'driveenergy', 'distance'] - columns_basic = columns_basic + ['spm', 'driveenergy', 'distance'] + columns = columns + ['spm', 'driveenergy', 'distance','workoutstate'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance','workoutstate'] datadf = pd.DataFrame() if promember: @@ -1838,24 +1838,24 @@ def interactive_flex_chart2(id, r, promember=0, workstrokesonly = False try: _ = rowdata[yparam2] - except (KeyError, TypeError): # pragma: no cover + except (KeyError, TypeError, ColumnNotFoundError): # pragma: no cover yparam2 = 'None' try: _ = rowdata[yparam1] - except (TypeError, KeyError): # pragma: no cover + except (TypeError, KeyError, ColumnNotFoundError): # pragma: no cover yparam1 = 'None' # test if we have drive energy try: _ = rowdata['driveenergy'].mean() - except (KeyError, TypeError): + except (KeyError, TypeError, ColumnNotFoundError): rowdata = rowdata.with_columns(driveenergy=pl.lit(500)) # test if we have power try: _ = rowdata['power'].mean() - except (KeyError, TypeError): + except (KeyError, TypeError, ColumnNotFoundError): rowdata = rowdata.with_columns(power=pl.lit(50)) # replace nans @@ -1870,22 +1870,22 @@ def interactive_flex_chart2(id, r, promember=0, if workstrokesonly: # pragma: no cover try: rowdata = rowdata.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) - except KeyError: + except (KeyError, ColumnNotFoundError): pass try: tseconds = rowdata['time'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return '', 'No time data - cannot make flex plot', workstrokesonly try: rowdata = rowdata.with_columns(x1 = pl.col(xparam)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(x1 = pl.col("time")) try: rowdata = rowdata.with_columns(y1 = pl.col(yparam1)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y1 = pl.col("time")) rowdata = rowdata.with_columns(yparam1 = pl.col("y1")) @@ -1893,7 +1893,7 @@ def interactive_flex_chart2(id, r, promember=0, if yparam2 != 'None': try: rowdata = rowdata.with_columns(y2 = pl.col(yparam2)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns(y2 = pl.col("time")) rowdata = rowdata.with_columns(yparam2 = pl.col("y2")) @@ -1916,18 +1916,18 @@ def interactive_flex_chart2(id, r, promember=0, try: rowdata = rowdata.with_columns((pl.lit(axlabels[xparam])).alias("xname")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns((pl.lit(xparam)).alias("xname")) try: rowdata = rowdata.with_columns((pl.lit(axlabels[yparam1])).alias("yname1")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns((pl.lit(yparam1)).alias("yname1")) if yparam2 != 'None': try: rowdata = rowdata.with_columns((pl.lit(axlabels[yparam2])).alias("yname2")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover rowdata = rowdata.with_columns((pl.lit(yparam2)).alias("yname2")) else: # pragma: no cover @@ -2185,210 +2185,6 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', script, div = get_chart("/compare", chart_data) return script, div, message, errormessage - if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': # pragma: no cover - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - elif xparam == 'time' and not startenddict: - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'time' and startenddict: # pragma: no cover - deltas = [pair[1]-pair[0] for key, pair in startenddict.items()] - xaxmin = 0 - xaxmax = pd.Series(deltas).max()*1000. - if xaxmax == 0: - xaxmax = tseconds.max() - else: - xaxmax = datadf['distance'].max() - xaxmin = datadf['distance'].min() - - if yparam == 'distance': # pragma: no cover - yaxmin = datadf['distance'].min() - yaxmax = datadf['distance'].max() - elif yparam == 'cumdist': # pragma: no cover - yaxmin = datadf['cumdist'].min() - yaxmax = datadf['cumdist'].max() - else: - yaxmin = yaxminima[yparam] - yaxmax = yaxmaxima[yparam] - - x_axis_type = 'linear' - y_axis_type = 'linear' - - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - if yparam == 'pace': - y_axis_type = 'datetime' - yaxmax = 90.*1e3 - yaxmin = 150.*1e3 - - if xparam == 'time': - x_axis_type = 'datetime' - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - width=920, height=500, - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkw = 184 - watermarkh = 35 - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'stretch_both' - - plot.image_url([watermarkurl], 0.05, 0.9, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - cntr = 0 - l1 = [] - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - group = datadf[datadf['workoutid'] == int(id)].copy() - try: - startsecond, endsecond = startenddict[id] - except KeyError: - startsecond = 0 - endsecond = 0 - - group.sort_values(by='time', ascending=True, inplace=True) - - if endsecond > 0: - group['time'] = group['time'] - 1.e3*startsecond - mask = group['time'] < 0 - group.mask(mask, inplace=True) - mask = group['time'] > 1.e3*(endsecond-startsecond) - group.mask(mask, inplace=True) - - if xparam == 'cumdist': - group['cumdist'] = group['cumdist'] - group['cumdist'].min() - res = make_cumvalues(group[xparam]) - group[xparam] = res[0] - elif xparam == 'distance': - group['distance'] = group['distance'] - group['distance'].min() - - try: - group['x'] = group[xparam] - except KeyError: # pragma: no cover - group['x'] = group['time'] - errormessage = xparam+' has no values. Plot invalid' - try: - group['y'] = group[yparam] - except KeyError: - group['y'] = 0.0*group['x'] - - ymean = group['y'].mean() - f = group['time'].diff().mean() - if f != 0 and not np.isnan(f): - windowsize = 2 * (int(20000./(f))) + 1 - else: - windowsize = 1 - - if windowsize > 3 and windowsize < len(group['y']): - try: - group['y'] = savgol_filter(group['y'], windowsize, 3) - except ValueError: # pragma: no cover - pass - - ylabel = Label(x=100, y=60+nrworkouts*20-20*cntr, - x_units='screen', y_units='screen', - text=axlabels[yparam] + - ": {ymean:6.2f}".format(ymean=ymean), - background_fill_alpha=.7, - background_fill_color='white', - text_color=color, - ) - if yparam != 'time' and yparam != 'pace': - plot.add_layout(ylabel) - - source = ColumnDataSource( - group - ) - - TIPS = OrderedDict([ - ('time', '@ftime'), - ('pace', '@fpace'), - ('hr', '@hr'), - ('spm', '@spm{1.1}'), - ('distance', '@distance{5}'), - ]) - - hover = plot.select(type=HoverTool) - hover.tooltips = TIPS - - if labeldict: - try: - legend_label = labeldict[id] - except KeyError: # pragma: no cover - legend_label = str(id) - else: # pragma: no cover - legend_label = str(id) - - if plottype == 'line': - l1.append(plot.line('x', 'y', source=source, color=color, - legend_label=legend_label, line_width=2)) - else: - l1.append(plot.scatter('x', 'y', source=source, color=color, legend_label=legend_label, - fill_alpha=0.4, line_color=None)) - - plot.add_tools(HoverTool(renderers=[l1[cntr]], tooltips=TIPS)) - cntr += 1 - - plot.legend.location = 'top_right' - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam] - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover - xrange1 = Range1d(start=yaxminima[xparam], end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - yrange1 = Range1d(start=yaxmin, end=yaxmax) - plot.y_range = yrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - script, div = components(plot) - - return [script, div, message, errormessage] - def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index b379eb83..80a12eb1 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -38,6 +38,7 @@ from nose.tools import assert_true from mock import Mock, patch #from minimocktest import MockTestCase import pandas as pd +import polars as pl import arrow from django.http import HttpResponseRedirect @@ -335,6 +336,11 @@ def mocked_getsmallrowdata_db(*args, **kwargs): return df +def mocked_getsmallrowdata_pl(*args, **kwargs): + df = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + + return df + def mocked_getsmallrowdata_db_updatecp(*args, **kwargs): # pragma: no cover df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv') diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index 3b88c2e2..c809c60e 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -65,6 +65,7 @@ from nose.tools import assert_true from mock import Mock, patch #from minimocktest import MockTestCase import pandas as pd +import polars as pl import rowers.c2stuff as c2stuff import rowers.rojabo_stuff as rojabo_stuff diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 650362d8..92903b5e 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -735,9 +735,9 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.getsmallrowdata_pl', side_effect=mocked_getsmallrowdata_pl) def test_interactive_flexchart_stacked(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_getsmallrowdata_pl): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 62c65b68b6e7bcbae639a5433d0bde8f0bbe803e..2f3f0980afdc1d7efcd86f0360156b49a73609c6 100644 GIT binary patch delta 3883 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 delta 3883 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 Date: Wed, 17 Apr 2024 12:30:48 +0200 Subject: [PATCH 097/121] zones chart --- rowers/forms.py | 21 --- rowers/interactiveplots.py | 218 ++++++++++++++++++++++++++++ rowers/templates/trainingzones.html | 8 +- rowers/views/analysisviews.py | 16 +- 4 files changed, 228 insertions(+), 35 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 27ecdd65..8985cf2b 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -74,27 +74,6 @@ class ResampleForm(forms.Form): class TrainingZonesForm(forms.Form): - zoneschoices = ( - ('power', 'Power Zones'), - ('hr', 'Heart Rate Zones') - ) - - datechoices = ( - ('month', 'By Month'), - ('week', 'By Week'), - ) - - yaxischoices = ( - ('time', 'Time'), - ('percentage', 'Percentage of Time') - ) - - zones = forms.ChoiceField( - initial='hr', label='Training Zones', choices=zoneschoices) - dates = forms.ChoiceField( - initial='week', label='Date Aggregation', choices=datechoices) - yaxis = forms.ChoiceField(initial='percentage', - label='Y axis', choices=yaxischoices) startdate = forms.DateField( initial=timezone.now()-datetime.timedelta(days=42), widget=AdminDateWidget(), # format='%Y-%m-%d'), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d5a2842a..50b3f2bf 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2185,6 +2185,194 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', script, div = get_chart("/compare", chart_data) return script, div, message, errormessage +def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg='week', + yaxis='time'): + + data = [] + + enddate = enddate + datetime.timedelta(days=1) + + workouts = Workout.objects.filter( + user=rower, + startdatetime__gte=startdate, + startdatetime__lte=enddate, + duplicate=False, + ).order_by("-startdatetime") + + ids = [w.id for w in workouts] + + columns = ['workoutid', 'hr', 'power', 'time'] + + df = dataprep.getsmallrowdata_pl(columns, ids=ids) + + df = df.with_columns((pl.col("time").diff().clip(0, 20*1.e3)).alias("deltat")).lazy() + hrzones = rower.hrzones + powerzones = rower.powerzones + + for w in workouts: + iswater = w.workouttype in mytypes.otwtypes + + pw_ut2 = rower.pw_ut2 + pw_ut1 = rower.pw_ut1 + pw_at = rower.pw_at + pw_tr = rower.pw_tr + pw_an = rower.pw_an + if iswater: + pw_ut2 = pw_ut2*rower.otwslack/100. + pw_ut1 = pw_ut1*rower.otwslack/100. + pw_at = pw_at*rower.otwslack/100. + pw_tr = pw_tr*rower.otwslack/100. + pw_an = pw_an*rower.otwslack/100. + + # 1 + time_ut2 = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") < rower.ut2, + ) + time_pw_ut2 = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") < pw_ut2, + ) + + # 2 + time_ut1 = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.ut2, + pl.col("hr") < rower.ut1, + ) + time_pw_ut1 = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_ut2, + pl.col("power") < pw_ut1, + ) + + #3 + time_at = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.ut1, + pl.col("hr") < rower.at, + ) + time_pw_at = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_ut1, + pl.col("power") < pw_at, + ) + + #4 + time_tr = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.at, + pl.col("hr") < rower.tr, + ) + time_pw_tr = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_at, + pl.col("power") < pw_tr, + ) + + #5 + time_an = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.tr, + pl.col("hr") < rower.an, + ) + time_pw_an = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_tr, + pl.col("power") < pw_an, + ) + + time_max = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.an, + ) + time_pw_max = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_an, + ) + + time_in_ut2 = time_ut2.collect()['deltat'].sum()/(60*1.e3) + time_in_ut2_pw = time_pw_ut2.collect()['deltat'].sum()/(60*1.e3) + + time_in_ut1 = time_ut1.collect()['deltat'].sum()/(60*1.e3) + time_in_ut1_pw = time_pw_ut1.collect()['deltat'].sum()/(60*1.e3) + + time_in_at = time_at.collect()['deltat'].sum()/(60*1.e3) + time_in_at_pw = time_pw_at.collect()['deltat'].sum()/(60*1.e3) + + time_in_tr = time_tr.collect()['deltat'].sum()/(60*1.e3) + time_in_tr_pw = time_pw_tr.collect()['deltat'].sum()/(60*1.e3) + + time_in_an = time_an.collect()['deltat'].sum()/(60*1.e3) + time_in_an_pw = time_pw_an.collect()['deltat'].sum()/(60*1.e3) + + time_in_max = time_max.collect()['deltat'].sum()/(60*1.e3) + time_in_max_pw = time_pw_max.collect()['deltat'].sum()/(60*1.e3) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': '<{ut2}'.format(ut2=hrzones[1]), + 'time_in_zone': time_in_ut2, + 'pw_zonename': '<{ut2}'.format(ut2=powerzones[1]), + 'pw_time_in_zone': time_in_ut2_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[1], + 'time_in_zone': time_in_ut1, + 'pw_zonename': powerzones[1], + 'pw_time_in_zone': time_in_ut1_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[2], + 'time_in_zone': time_in_at, + 'pw_zonename': powerzones[2], + 'pw_time_in_zone': time_in_at_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[3], + 'time_in_zone': time_in_tr, + 'pw_zonename': powerzones[3], + 'pw_time_in_zone': time_in_tr_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[4], + 'time_in_zone': time_in_an, + 'pw_zonename': powerzones[4], + 'pw_time_in_zone': time_in_an_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[5], + 'time_in_zone': time_in_max, + 'pw_zonename': powerzones[5], + 'pw_time_in_zone': time_in_max_pw, + }) + + + chart_data = { + 'data': data, + 'hrzones': hrzones, + 'powerzones': powerzones, + } + + return chart_data + + def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): @@ -2402,6 +2590,36 @@ def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='we return data +def interactive_zoneschart2(rower, data, startdate, enddate, trainingzones='hr', date_agg='week', + yaxis='time'): + if startdate >= enddate: # pragma: no cover + st = startdate + startdate = enddate + enddate = st + + hrzones = data['hrzones'] + powerzones = data['powerzones'] + + data['yaxis'] = yaxis + data['title'] = 'Activity {d1} to {d2} for {r}'.format( + d1=startdate.strftime("%Y-%m-%d"), + d2=enddate.strftime("%Y-%m-%d"), + r=str(rower), + ) + + data['stackBy'] = 'time_in_zone' + data['colorBy'] = 'zonename' + if trainingzones == 'power': + data['stackBy'] = 'pw_time_in_zone' + data['colorBy'] = 'pw_zonename' + data['doReduce'] = True + data['datebin'] = date_agg + data['colors'] = ['green', 'lime', 'yellow', 'blue', 'purple', 'red'] + + script, div = get_chart("/zones", data, debug=False) + + return script, div + def interactive_zoneschart(rower, data, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): diff --git a/rowers/templates/trainingzones.html b/rowers/templates/trainingzones.html index 19502c31..5ff42eb8 100644 --- a/rowers/templates/trainingzones.html +++ b/rowers/templates/trainingzones.html @@ -11,17 +11,15 @@ - +
      + +

      Training Zones

      - - -
      -
      • diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 418132e4..cbab1c80 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1144,9 +1144,6 @@ def trainingzones_view(request, userid=0): form = TrainingZonesForm({ 'startdate': startdate, 'enddate': enddate, - 'zones': zones, - 'dates': date_agg, - 'yaxis': yaxis, }) if request.method == 'POST': # pragma: no cover @@ -1155,9 +1152,6 @@ def trainingzones_view(request, userid=0): if form.is_valid(): startdate = form.cleaned_data['startdate'] enddate = form.cleaned_data['enddate'] - zones = form.cleaned_data['zones'] - date_agg = form.cleaned_data['dates'] - yaxis = form.cleaned_data['yaxis'] if date_agg == 'week': startdate = startdate - datetime.timedelta(days=startdate.weekday()) @@ -1229,12 +1223,16 @@ def trainingzones_view_data(request, userid=0): request.GET.get('enddate'), "%Y-%m-%d") enddate = arrow.get(enddate).datetime - data = get_zones_report(r, startdate, enddate, - trainingzones=zones, date_agg=date_agg, yaxis=yaxis) + if (enddate-startdate).days > 200: + date_agg = "month" - script, div = interactive_zoneschart( + data = get_zones_report_pl(r, startdate, enddate, + trainingzones=zones, date_agg=date_agg, yaxis=yaxis) + + script, div = interactive_zoneschart2( r, data, startdate, enddate, trainingzones=zones, date_agg=date_agg, yaxis=yaxis) + return JSONResponse({ 'script': script, 'div': div, From 4e05799e3521b762d2fcdfea9521f92891cba2fa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Apr 2024 12:39:31 +0200 Subject: [PATCH 098/121] tested --- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 3999 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 2f3f0980afdc1d7efcd86f0360156b49a73609c6..3826d3a2acbe5db0977e25fc8549d45c7c3e72fc 100644 GIT binary patch delta 3913 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 X#iR5z{z#WTyz@U53=t*71AqYl98vwp delta 3913 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 X7Z20Zc#$rBc;|nD>hwv(1AqYlD|_I< From af6226799625442ec3062ace168d0df3481db032 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Apr 2024 13:35:02 +0200 Subject: [PATCH 099/121] some testing, some pandas to polars in tasks.py --- rowers/dataprep.py | 3 +- rowers/dataroutines.py | 7 +- rowers/integrations/c2.py | 2 +- rowers/interactiveplots.py | 351 +------------------------- rowers/tasks.py | 131 ++-------- rowers/tests/test_async_tasks.py | 44 ---- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/views/statements.py | 2 +- 9 files changed, 35 insertions(+), 507 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index c69aeffa..c5195951 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -89,10 +89,9 @@ from rowers.dataroutines import * from rowers.tasks import ( handle_sendemail_newftp, - handle_sendemail_unrecognized, handle_setcp, + handle_sendemail_unrecognized, handle_getagegrouprecords, handle_update_wps, handle_request_post, handle_calctrimp, - handle_updatecp, handle_updateergcp, handle_sendemail_breakthrough, handle_sendemail_hard, ) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 09e70f02..dad0dfc6 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -2705,7 +2705,7 @@ def create_c2_stroke_data_db( else: power = 0 - df = pd.DataFrame({ + df = pl.DataFrame({ 'TimeStamp (sec)': unixtime, ' Horizontal (meters)': d, ' Cadence (stokes/min)': spm, @@ -2726,9 +2726,10 @@ def create_c2_stroke_data_db( 'cum_dist': d }) - df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] + df = df.with_columns((pl.col("TimeStamp (sec)")).alias(" ElapsedTime (sec)")) - _ = df.to_csv(csvfilename, index_label='index', compression='gzip') + row = rrdata_pl(df=df) + row.writecsv(csvfilename, compression=True) data = dataplep(df, id=workoutid, bands=False, debug=debug) diff --git a/rowers/integrations/c2.py b/rowers/integrations/c2.py index 1a7268ea..2a4a57ab 100644 --- a/rowers/integrations/c2.py +++ b/rowers/integrations/c2.py @@ -2,7 +2,7 @@ from .integrations import SyncIntegration, NoTokenError, create_or_update_syncre from rowers.models import User, Rower, Workout, TombStone from django.db.utils import IntegrityError -from rowingdata import rowingdata +from rowingdata import rowingdata, rowingdata_pl import numpy as np import datetime import json diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 50b3f2bf..072fe65c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -61,7 +61,7 @@ from rowers.models import ( Workout, User, Rower, WorkoutForm, RowerForm, GraphImage, GeoPolygon, GeoCourse, GeoPoint, ) -from rowers.tasks import handle_setcp + from rowingdata import rower as rrower from rowingdata import main as rmain from rowingdata import cumcpdata, histodata @@ -1380,7 +1380,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] datadf = dataprep.getsmallrowdata_pl(columns, ids=[id]) - if datadf.is_empty(): + if datadf.is_eompty(): return "", "No Valid Data Available" datadf = datadf.fill_nan(None).drop_nulls() @@ -2372,223 +2372,6 @@ def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg= return chart_data - - -def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week', - yaxis='time'): - - dates = [] - dates_sorting = [] - minutes = [] - hours = [] - zones = [] - - enddate = enddate + datetime.timedelta(days=1) - - workouts = Workout.objects.filter( - user=rower, - startdatetime__gte=startdate, - startdatetime__lte=enddate, - duplicate=False, - ).order_by("-startdatetime") - - ids = [w.id for w in workouts] - - columns = ['workoutid', 'hr', 'power', 'time'] - - df = dataprep.getsmallrowdata_db(columns, ids=ids) - try: - df['deltat'] = df['time'].diff().clip(lower=0).clip(upper=20*1e3) - except KeyError: # pragma: no cover - pass - - df = dataprep.clean_df_stats(df, workstrokesonly=False, - ignoreadvanced=True, ignorehr=False) - - hrzones = rower.hrzones - powerzones = rower.powerzones - - for w in workouts: - dd3 = w.date.strftime('%Y/%m') - dd4 = '{year}/{week:02d}'.format( - week=arrow.get(w.date).isocalendar()[1], - year=w.date.strftime('%y') - ) - dd4 = (w.date - datetime.timedelta(days=w.date.weekday()) - ).strftime('%y/%m/%d') - - # print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar()) - iswater = w.workouttype in mytypes.otwtypes - qryw = 'workoutid == {workoutid}'.format(workoutid=w.id) - - qry = 'hr < {ut2}'.format(ut2=rower.ut2) - if trainingzones == 'power': - qry = 'power < {ut2}'.format(ut2=rower.pw_ut2) - timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) - if date_agg == 'week': - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - minutes.append(timeinzone) - hours.append(timeinzone/60.) - if trainingzones == 'hr': - zones.append('<{ut2}'.format(ut2=hrzones[1])) - else: - zones.append('<{ut2}'.format(ut2=powerzones[1])) - # print(w,dd,timeinzone,'= enddate: # pragma: no cover - st = startdate - startdate = enddate - enddate = st - - hrzones = rower.hrzones - powerzones = rower.powerzones - - color_map = { - '<{ut2}'.format(ut2=hrzones[1]): 'green', - hrzones[1]: 'lime', - hrzones[2]: 'yellow', - hrzones[3]: 'blue', - hrzones[4]: 'purple', - hrzones[5]: 'red', - } - if trainingzones == 'power': - color_map = { - '<{ut2}'.format(ut2=powerzones[1]): 'green', - powerzones[1]: 'lime', - powerzones[2]: 'yellow', - powerzones[3]: 'blue', - powerzones[4]: 'purple', - powerzones[5]: 'red', - } - - zones_order = [ - '<{ut2}'.format(ut2=hrzones[1]), - hrzones[1], - hrzones[2], - hrzones[3], - hrzones[4], - hrzones[5] - ] - - if trainingzones == 'power': - zones_order = [ - '<{ut2}'.format(ut2=powerzones[1]), - powerzones[1], - powerzones[2], - powerzones[3], - powerzones[4], - powerzones[5] - ] - - df = pd.DataFrame(data) - df2 = pd.DataFrame(data) - - df.drop('minutes', inplace=True, axis='columns') - - df.sort_values('date_sorting', inplace=True) - df.drop('date_sorting', inplace=True, axis='columns') - df['totaltime'] = 0 - if df.empty: # pragma: no cover - return '', 'No Data Found' - - if yaxis == 'percentage': - dates = list(set(df['date'].values)) - for date in dates: - qry = 'date == "{d}"'.format(d=date) - - totaltime = df.query(qry)['hours'].sum() - - mask = df['date'] == date - df.loc[mask, 'totaltime'] = totaltime - - df['percentage'] = 100.*df['hours']/df['totaltime'] - df.drop('hours', inplace=True, axis='columns') - df.drop('totaltime', inplace=True, axis='columns') - - hv.extension('bokeh') - - xrotation = 0 - nrdates = len(list(set(df['date'].values))) - if nrdates > 10: - xrotation = 45 - - bars = hv.Bars(df, kdims=['date', 'zones']).aggregate( - function=np.sum).redim.values(zones=zones_order) - - bars.opts( - opts.Bars(cmap=color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, padding=(0, (0, .1)), - legend_position='bottom', - xrotation=xrotation, - show_frame=False) - ) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2} for {r}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - r=str(rower), - ) - - if date_agg == 'week': - p.xaxis.axis_label = 'Week' - else: # pragma: no cover - p.xaxis.axis_label = 'Month' - - if yaxis == 'percentage': - p.yaxis.axis_label = 'Percentage' - - p.width = 550 - p.height = 350 - p.toolbar_location = 'right' - p.y_range.start = 0 - #p.sizing_mode = 'stretch_both' - - if yaxis == 'percentage': - tidy_df = df2.groupby(['date']).sum() - - source2 = ColumnDataSource(tidy_df) - y2rangemax = tidy_df.loc[:, 'hours'].max()*1.1 - p.extra_y_ranges["yax2"] = Range1d(start=0, end=y2rangemax) - p.line('date', 'hours', source=source2, - y_range_name="yax2", color="black", width=5) - p.circle('date', 'hours', source=source2, y_range_name="yax2", color="black", size=10) - -# p.circle('date', 'hours', source=source2, y_range_name="yax2", color="black", size=10, -# legend_label='Hours') - p.add_layout(LinearAxis(y_range_name="yax2", - axis_label='Hours'), 'right') - - script, div = components(p) - - return script, div diff --git a/rowers/tasks.py b/rowers/tasks.py index f42891c9..1a54e8c3 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -56,7 +56,7 @@ from scipy.signal import savgol_filter from scipy.interpolate import griddata import rowingdata -from rowingdata import make_cumvalues +from rowingdata import make_cumvalues, make_cumvalues_array from uuid import uuid4 from rowingdata import rowingdata as rdata @@ -115,6 +115,9 @@ tpapilocation = TP_API_LOCATION from requests_oauthlib import OAuth1, OAuth1Session import pandas as pd +import polars as pl +from polars.exceptions import ColumnNotFoundError + from django_rq import job from django.utils import timezone @@ -2583,53 +2586,6 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue, return 1 -@app.task -def handle_updateergcp(rower_id, workoutfilenames, debug=False, **kwargs): - therows = [] - for f1 in workoutfilenames: - try: - rowdata = rdata(csvfile=f1) - except IOError: # pragma: no cover - try: - rowdata = rdata(csvfile=f1 + '.csv') - except IOError: - try: - rowdata = rdata(csvfile=f1 + '.gz') - except IOError: - rowdata = 0 - if rowdata != 0: - therows.append(rowdata) - - cpdata = rowingdata.cumcpdata(therows) - cpdata.columns = cpdata.columns.str.lower() - - updatecpdata_sql(rower_id, cpdata['delta'], cpdata['cp'], - table='ergcpdata', distance=cpdata['distance'], - debug=debug) - - return 1 - - -@app.task -def handle_updatecp(rower_id, workoutids, debug=False, table='cpdata', **kwargs): - columns = ['power', 'workoutid', 'time'] - df = getsmallrowdata_db(columns, ids=workoutids, debug=debug) - - if df.empty: # pragma: no cover - return 0 - - maxt = 1.05*df['time'].max()/1000. - - logarr = datautils.getlogarr(maxt) - - dfgrouped = df.groupby(['workoutid']) - - delta, cpvalue, avgpower = datautils.getcp(dfgrouped, logarr) - - updatecpdata_sql(rower_id, delta, cpvalue, debug=debug, table=table) - - return 1 - @app.task def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename, @@ -3179,43 +3135,6 @@ def handle_sendemail_invite_reject(email, name, teamname, managername, return 1 -@app.task -def handle_setcp(strokesdf, filename, workoutid, debug=False, **kwargs): - try: - os.remove(filename) - except FileNotFoundError: - pass - if not strokesdf.empty: - - try: - totaltime = strokesdf['time'].max() - except KeyError: # pragma: no cover - return 0 - try: - powermean = strokesdf['power'].mean() - except KeyError: # pragma: no cover - powermean = 0 - - if powermean != 0: - thesecs = totaltime - maxt = 1.05 * thesecs - - if maxt > 0: - logarr = datautils.getlogarr(maxt) - dfgrouped = strokesdf.groupby(['workoutid']) - delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) - - df = pd.DataFrame({ - 'delta': delta, - 'cp': cpvalues, - 'id': workoutid, - }) - df.to_parquet(filename, engine='fastparquet', - compression='GZIP') - return 1 - - return 1 # pragma: no cover - @app.task def handle_sendemail_invite_accept(email, name, teamname, managername, @@ -3647,7 +3566,7 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, loncoord = np.zeros(nr_rows) try: - strokelength = strokedata.loc[:, 'strokelength'] + strokelength = strokedata.loc[:,'strokelength'] except: # pragma: no cover strokelength = np.zeros(nr_rows) @@ -3901,7 +3820,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, pace[np.isinf(pace)] = 0.0 try: - strokedata = pd.DataFrame({'t': 10*t, + strokedata = pl.DataFrame({'t': 10*t, 'd': 10*d, 'p': 10*pace, 'spm': spm, @@ -3947,18 +3866,18 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, starttimeunix = arrow.get(rowdatetime).timestamp() - res = make_cumvalues(0.1*strokedata['t']) - cum_time = res[0] - lapidx = res[1] + res = make_cumvalues_array(0.1*strokedata['t'].to_numpy()) + cum_time = pl.Series(res[0]) + lapidx = pl.Series(res[1]) unixtime = cum_time+starttimeunix - seconds = 0.1*strokedata.loc[:, 't'] + seconds = 0.1*strokedata['t'] nr_rows = len(unixtime) try: - latcoord = strokedata.loc[:, 'lat'] - loncoord = strokedata.loc[:, 'lon'] + latcoord = strokedata['lat'] + loncoord = strokedata['lon'] if latcoord.std() == 0 and loncoord.std() == 0 and workouttype == 'water': # pragma: no cover workouttype = 'rower' except: # pragma: no cover @@ -3968,29 +3887,29 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, workouttype = 'rower' try: - strokelength = strokedata.loc[:, 'strokelength'] + strokelength = strokedata['strokelength'] except: # pragma: no cover strokelength = np.zeros(nr_rows) - dist2 = 0.1*strokedata.loc[:, 'd'] + dist2 = 0.1*strokedata['d'] try: - spm = strokedata.loc[:, 'spm'] - except KeyError: # pragma: no cover + spm = strokedata['spm'] + except (KeyError, ColumnNotFoundError): # pragma: no cover spm = 0*dist2 try: - hr = strokedata.loc[:, 'hr'] - except KeyError: # pragma: no cover + hr = strokedata['hr'] + except (KeyError, ColumnNotFoundError): # pragma: no cover hr = 0*spm - pace = strokedata.loc[:, 'p']/10. + pace = strokedata['p']/10. pace = np.clip(pace, 0, 1e4) - pace = pace.replace(0, 300) + pace = pl.Series(pace).replace(0, 300) velo = 500./pace try: - power = strokedata.loc[:, 'power'] + power = strokedata['power'] except KeyError: # pragma: no cover power = 2.8*velo**3 @@ -3999,7 +3918,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, # save csv # Create data frame with all necessary data to write to csv - df = pd.DataFrame({'TimeStamp (sec)': unixtime, + df = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': dist2, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -4019,10 +3938,10 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, 'cum_dist': dist2, }) - df.sort_values(by='TimeStamp (sec)', ascending=True) + df.sort('TimeStamp (sec)') - row = rowingdata.rowingdata(df=df) - row.write_csv(csvfilename, gzip=False) + row = rowingdata.rowingdata_pl(df=df) + row.write_csv(csvfilename, compressed=False) # summary = row.allstats() # maxdist = df['cum_dist'].max() diff --git a/rowers/tests/test_async_tasks.py b/rowers/tests/test_async_tasks.py index f644e53d..5aab5a37 100644 --- a/rowers/tests/test_async_tasks.py +++ b/rowers/tests/test_async_tasks.py @@ -484,51 +484,7 @@ class AsyncTaskTests(TestCase): res = tasks.handle_c2_import_stroke_data(c2token,c2id,workoutid,starttimeunix,csvfilename) self.assertEqual(res,1) - @patch('rowers.tasks.grpc',side_effect=mocked_grpc) - @patch('rowers.tasks.send_template_email',side_effect=mocked_send_template_email) - def test_handle_otwsetpower(self,mocked_send_template_email,mocked_grpc): - f1 = get_random_file(filename='rowers/tests/testdata/sprintervals.csv')['filename'] - boattype = '1x' - boatclass = 'water' - coastalbrand = 'other' - weightvalue = 80. - first_name = self.u.first_name - last_name = self.u.last_name - email = self.u.email - workoutid = self.wwater.id - job = fakerequest() - res = tasks.handle_otwsetpower(f1,boattype,boatclass,coastalbrand, - weightvalue,first_name,last_name,email,workoutid, - jobkey='23') - - self.assertEqual(res,1) - - @patch('rowers.dataprep.create_engine') - def test_handle_updateergcp(self,mocked_sqlalchemy): - f1 = get_random_file()['filename'] - res = tasks.handle_updateergcp(1,[f1]) - self.assertEqual(res,1) - - - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp): - rower_id = 1 - workoutids = [1] - res = tasks.handle_updatecp(rower_id,workoutids) - self.assertEqual(res,1) - - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_setcp(self,mocked_getsmallrowdata_db_setcp): - strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv') - filename = 'rowers/tests/testdata/temp/pq.gz' - workoutids = 1 - res = tasks.handle_setcp(strokesdf,filename,1) - self.assertEqual(res,1) - try: - os.remove(filename) - except FileNotFoundError: - pass @patch('rowers.dataprep.getsmallrowdata_db') def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps): diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 92903b5e..5e57721c 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -703,7 +703,7 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.getsmallrowdata_pl', side_effect=mocked_getsmallrowdata_pl) def test_interactive_chart(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3826d3a2acbe5db0977e25fc8549d45c7c3e72fc..d4c7d683121479b2057f01d3d1792b90e5e3bdf1 100644 GIT binary patch literal 4000 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 GfB^s+065D4 literal 3999 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 F0RWDHHmd*t diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 4eb83f0a..991b440f 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -252,7 +252,7 @@ from rowers.tasks import ( handle_sendemailfile, handle_sendemailkml, handle_sendemailnewresponse, handle_updatedps, - handle_updatecp, long_test_task, long_test_task2, + long_test_task, long_test_task2, handle_zip_file, handle_getagegrouprecords, handle_update_empower, handle_sendemailics, From 5a505cd51e9127e108ace819aa5d35681f22da69 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Apr 2024 13:43:23 +0200 Subject: [PATCH 100/121] fixed a test --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index d4c7d683121479b2057f01d3d1792b90e5e3bdf1..3b448907b673aa56f0cf6273d06d08abbdb3108e 100644 GIT binary patch literal 4000 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 literal 4000 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 GfB^s+065D4 From e510591dba4ed4303065aa6ef8d4849495eb3225 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Apr 2024 16:55:45 +0200 Subject: [PATCH 101/121] tested --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3b448907b673aa56f0cf6273d06d08abbdb3108e..f0e0d26a9ef6b916c8a782de10c0aa02a2355759 100644 GIT binary patch delta 257 zcmV+c0sj7=AD|xwABzYGqv5dzst$kFb4UL4%Tr)><@UbKhwM4m_J{CTJ66- z`hJr*UT)s*`%{8B=#7W-cFWW5Kk1U$S#WXl^QXJ5 z)@ies_xtTOTtC{6&VTeg-SqJ<%kyQoT_z6s)7wXPU0QUbMEpB|VfAYJ>bo^k4dbLi^;roPgx%nYIjo;Iyk01OGRXy7a H#DD<+IO&>5 delta 257 zcmV+c0sj7=AD|xwABzYGCA6^yst$iA;!e8|y@xN(`mc```vV^A&-Cs$=FiukR{QUd zzTYH{mz%fy{?w(nXLI>_+pSN#^Yh))N*`D2v(v8c(s{f6+m)!iKkn$;m;H;&&5hUO z+pDYd)k)W H#DD<+uIZk8 From eb44623e24af13026377055df2275988a7b6716a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 18 Apr 2024 08:31:08 +0200 Subject: [PATCH 102/121] some more polars, passing test_uploads --- rowers/dataprep.py | 92 ++++++++++++++------------ rowers/interactiveplots.py | 2 +- rowers/tasks.py | 2 +- rowers/tests/test_imports.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/utils.py | 27 ++++---- rowers/views/exportviews.py | 2 +- 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index c5195951..f6de443b 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -8,6 +8,7 @@ from rowers.datautils import p0 from scipy import optimize from rowers.utils import calculate_age import datetime +import gzip from scipy.signal import savgol_filter from rowers.opaque import encoder from rowers.database import * @@ -27,6 +28,7 @@ from fitparse import FitFile import itertools import numpy as np import pandas as pd +import polars as pl from zipfile import BadZipFile import zipfile import os @@ -219,18 +221,18 @@ def check_marker(workout): ids.append(w.id) gms.append(gmstandard) - df = pd.DataFrame({ + df = pl.DataFrame({ 'id': ids, 'gms': gms, }) - if df.empty: # pragma: no cover + if df.is_empty(): # pragma: no cover workout.ranking = True workout.save() return workout - indexmax = df['gms'].idxmax() - theid = df.loc[indexmax, 'id'] + theid = df.filter(pl.col("gms") == pl.col("gms").max())['id'][0] + wmax = Workout.objects.get(id=theid) # gms_max = wmax.goldmedalstandard @@ -326,7 +328,7 @@ def workout_summary_to_df( goldstandarddurations.append(int(goldstandardduration)) rankingpieces.append(w.rankingpiece) - df = pd.DataFrame({ + df = pl.DataFrame({ 'ID': ids, 'date': startdatetimes, 'name': names, @@ -420,19 +422,21 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): try: df = pl.read_parquet(cpfile) except: + df = getsmallrowdata_pl(['power'], ids=[workout.id]) background = True if settings.TESTING: background = False - df, delta, cpvalues = setcp(workout, background=background) - if df.empty: - return 0, 0 - df = pl.from_pandas(df) + if recurrance: + df, delta, cpvalues = setcp(workout, background=background) + if df.is_empty(): + return 0, 0 + else: + return 0,0 if df.is_empty() and recurrance: # pragma: no cover df, delta, cpvalues = setcp(workout, recurrance=False, background=True) - if df.empty: + if df.is_empty(): return 0, 0 - df = pl.from_pandas(df) age = calculate_age(rower.birthdate, today=workout.date) @@ -459,7 +463,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): if getrecords: # pragma: no cover durations = [1, 4, 30, 60] distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - df2 = pd.DataFrame( + df2 = pl.DataFrame( list( C2WorldClassAgePerformance.objects.filter( sex=rower.sex, @@ -467,7 +471,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): ).values() ) ) - jsondf = df2.to_json() + jsondf = df2.write_json() _ = myqueue(queuelow, handle_getagegrouprecords, jsondf, distances, durations, age, rower.sex, rower.weightcategory) @@ -511,21 +515,21 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): def setcp(workout, background=False, recurrance=True): try: filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) - df = pd.read_parquet(filename) + df = pl.read_parquet(filename) - if not df.empty: + if not df.is_empty(): # check dts tarr = datautils.getlogarr(4000) if df['delta'][0] in tarr: return(df, df['delta'], df['cp']) - except: + except Exception as e: pass - strokesdf = getsmallrowdata_db( + strokesdf = getsmallrowdata_pl( ['power', 'workoutid', 'time'], ids=[workout.id]) - if strokesdf.empty: - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + if strokesdf.is_empty(): + return pl.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') totaltime = strokesdf['time'].max() maxt = totaltime/1000. @@ -565,23 +569,27 @@ def setcp(workout, background=False, recurrance=True): dologging('metrics.log', traceback.format_exc()) return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') - delta = pd.Series(np.array(response.delta)) - cpvalues = pd.Series(np.array(response.power)) + delta = pl.Series(np.array(response.delta)) + cpvalues = pl.Series(np.array(response.power)) powermean = response.avgpower - - - df = pd.DataFrame({ + df = pl.DataFrame({ 'delta': delta, 'cp': cpvalues, 'id': workout.id, }) - df.to_parquet(filename, engine='fastparquet', compression='GZIP') + df = df.drop_nulls() + + with gzip.open(filename, 'w') as f: + df.write_parquet(f) + + + #df.to_parquet(filename, engine='fastparquet', compression='GZIP') if recurrance: goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( - workout.user, workout) + workout.user, workout, recurrance=False) workout.goldmedalstandard = goldmedalstandard workout.goldmedalduration = goldmedalduration workout.save() @@ -737,24 +745,24 @@ def fetchcp_new(rower, workouts): except: # CP data file doesn't exist yet. has to be created df, delta, cpvalues = setcp(workout) - df['workout'] = str(workout) - df['url'] = workout.url() + df = df.with_columns((pl.lit(str(workout))).alias("workout")) + df = df.with_columns((pl.lit(workout.url())).alias("url")) data.append(df) if len(data) == 0: - return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.Series(dtype='float'), pl.Series(dtype='float'), 0, pl.Series(dtype='float'), pl.Series(dtype='float') if len(data) > 1: - df = pd.concat(data, axis=0) + df = pl.concat(data) + + #df = df.to_pandas() try: - df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')] - except KeyError: # pragma: no cover + df = df.group_by(pl.col("delta")).agg(pl.max("cp"), pl.max("workout"), pl.max("url")).sort("delta") + except (KeyError, ColumnNotFoundError): # pragma: no cover return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') - df = df.sort_values(['delta']).reset_index() - df = df[df['cp']>20] - + df = df.filter(pl.col("cp")>20) return df['delta'], df['cp'], 0, df['workout'], df['url'] @@ -810,16 +818,16 @@ def update_rolling_cp(r, types, mode='water', dosend=False): delta, cp, avgpower, workoutnames, urls = fetchcp_new(r, workouts) - powerdf = pd.DataFrame({ + powerdf = pl.DataFrame({ 'Delta': delta, 'CP': cp, }) - powerdf = powerdf[powerdf['CP'] > 0] - powerdf.dropna(axis=0, inplace=True) - powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True) - powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True) - if powerdf.empty: + powerdf = powerdf.filter(pl.col("CP")>0) + powerdf = powerdf.fill_nan(None).drop_nulls().sort(["Delta", "CP"]) + powerdf = powerdf.unique(subset=["Delta"], keep="first") + + if powerdf.is_empty(): return False res2 = datautils.cpfit(powerdf) @@ -1062,7 +1070,7 @@ def checkbreakthrough(w, r): workouttype = w.workouttype if workouttype in rowtypes: cpdf, delta, cpvalues = setcp(w) - if not cpdf.empty: + if not cpdf.is_empty(): if workouttype in otwtypes: try: res, btvalues, res2 = utils.isbreakthrough( diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 072fe65c..80517b36 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1380,7 +1380,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] datadf = dataprep.getsmallrowdata_pl(columns, ids=[id]) - if datadf.is_eompty(): + if datadf.is_empty(): return "", "No Valid Data Available" datadf = datadf.fill_nan(None).drop_nulls() diff --git a/rowers/tasks.py b/rowers/tasks.py index 1a54e8c3..f2a7355f 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -562,7 +562,7 @@ def handle_sporttracks_workout_from_data(user, importid, source, strokedata = pd.DataFrame.from_dict({ key: pd.Series(value, dtype='object') for key, value in data.items() }) - + try: workouttype = data['type'] except KeyError: # pragma: no cover diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 16346575..d5e9470d 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -1307,7 +1307,7 @@ class STObjects(DjangoTestCase): self.r.sporttrackstoken = '12' - self.r.sporttracksrefreshtoken = '12' + self.r.sporttroacksrefreshtoken = '12' self.r.sporttrackstokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime self.r.save() diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index f0e0d26a9ef6b916c8a782de10c0aa02a2355759..132867e104e5cbf19a16c3afdbd4a693272e648a 100644 GIT binary patch literal 4000 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 GfB^v86B%Ow literal 4000 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 GfB^vhXh2{9 diff --git a/rowers/utils.py b/rowers/utils.py index 8ea2225b..7cb1413b 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -6,6 +6,7 @@ from django.utils import timezone import math import numpy as np import pandas as pd +import polars as pl import colorsys from django.conf import settings import collections @@ -342,22 +343,22 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr *= ratio - delta = delta.astype(int, errors='ignore').values - cpvalues = cpvalues.astype(int, errors='ignore').values - pwr = pwr.astype(int, errors='ignore').values + delta = delta.cast(pl.Int32) + cpvalues = cpvalues.cast(pl.Int32) + pwr = pwr.cast(pl.Int32) - res = np.sum(cpvalues > pwr+1) - res2 = np.sum(cpvalues > pwr2+1) + btdf = pl.DataFrame({ + 'delta': delta, + 'cpvalues': cpvalues, + 'pwr': pwr, + 'pwr2': pwr2 + }) - btdf = pd.DataFrame( - { - 'delta': delta[cpvalues > pwr], - 'cpvalues': cpvalues[cpvalues > pwr], - 'pwr': pwr[cpvalues > pwr], - } - ) + res = btdf.select(pl.col("cpvalues")>pl.col("pwr")+1)['cpvalues'].sum() + res2 = btdf.select(pl.col("cpvalues")> pl.col("pwr2")+1)['cpvalues'].sum() - btdf.sort_values('delta', axis=0, inplace=True) + btdf = btdf.filter(pl.col("cpvalues")>pl.col("pwr")) + btdf = btdf.sort('delta') return res >= 1, btdf, res2 >= 1 diff --git a/rowers/views/exportviews.py b/rowers/views/exportviews.py index 15bf47f6..593fa23d 100644 --- a/rowers/views/exportviews.py +++ b/rowers/views/exportviews.py @@ -208,7 +208,7 @@ def workouts_summaries_email_view(request): ) df = dataprep.workout_summary_to_df( r, startdate=startdate, enddate=enddate) - df.to_csv(filename, encoding='utf-8') + df.write_csv(filename) _ = myqueue(queuehigh, handle_sendemailsummary, r.user.first_name, r.user.last_name, From a53d932c597242f983e9c9aefb22043523624bec Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 18 Apr 2024 19:44:33 +0200 Subject: [PATCH 103/121] passing tests --- rowers/dataprep.py | 32 +++++++++----------------- rowers/dataroutines.py | 26 +++++++++++++++++---- rowers/interactiveplots.py | 18 +++++++-------- rowers/tests/mocks.py | 6 ++--- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/utils.py | 6 +++-- rowers/views/analysisviews.py | 16 ++++++++----- 8 files changed, 59 insertions(+), 47 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f6de443b..5983bb91 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -529,7 +529,7 @@ def setcp(workout, background=False, recurrance=True): ['power', 'workoutid', 'time'], ids=[workout.id]) if strokesdf.is_empty(): - return pl.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) totaltime = strokesdf['time'].max() maxt = totaltime/1000. @@ -544,7 +544,7 @@ def setcp(workout, background=False, recurrance=True): elif os.path.exists(csvfilename+'.gz'): # pragma: no cover csvfile = csvfilename+'.gz' else: # pragma: no cover - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) csvfile = os.path.abspath(csvfile) @@ -558,7 +558,7 @@ def setcp(workout, background=False, recurrance=True): grpc.channel_ready_future(channel).result(timeout=10) except grpc.FutureTimeoutError: # pragma: no cover dologging('metrics.log','grpc channel time out in setcp') - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) stub = metrics_pb2_grpc.MetricsStub(channel) req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr) @@ -567,7 +567,7 @@ def setcp(workout, background=False, recurrance=True): response = stub.GetCP(req, timeout=60) except Exception as e: dologging('metrics.log', traceback.format_exc()) - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) delta = pl.Series(np.array(response.delta)) cpvalues = pl.Series(np.array(response.power)) @@ -735,32 +735,22 @@ def fetchcp_new(rower, workouts): data = [] for workout in workouts: - cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) - try: - df, delta, cpvalues = setcp(workout) - df = pd.read_parquet(cpfile) - df['workout'] = str(workout) - df['url'] = workout.url() - data.append(df) - except: - # CP data file doesn't exist yet. has to be created - df, delta, cpvalues = setcp(workout) - df = df.with_columns((pl.lit(str(workout))).alias("workout")) - df = df.with_columns((pl.lit(workout.url())).alias("url")) + df, delta, cpvalues = setcp(workout) + df = df.drop('id') + df = df.with_columns((pl.lit(str(workout))).alias("workout")) + df = df.with_columns((pl.lit(workout.url())).alias("url")) + if not df.is_empty(): data.append(df) if len(data) == 0: - return pl.Series(dtype='float'), pl.Series(dtype='float'), 0, pl.Series(dtype='float'), pl.Series(dtype='float') + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) if len(data) > 1: df = pl.concat(data) - #df = df.to_pandas() - - try: df = df.group_by(pl.col("delta")).agg(pl.max("cp"), pl.max("workout"), pl.max("url")).sort("delta") except (KeyError, ColumnNotFoundError): # pragma: no cover - return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) df = df.filter(pl.col("cp")>20) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index dad0dfc6..9f9e571c 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -32,6 +32,7 @@ import zipfile import os from rowers.models import strokedatafields import polars as pl +import polars.selectors as cs from polars.exceptions import ( ColumnNotFoundError, SchemaError, ComputeError, InvalidOperationError @@ -176,6 +177,19 @@ columndict = { 'cumdist': 'cum_dist', } +def remove_nulls_pl(data): + data = data.lazy().fill_nan(None) + data = data.select(cs.by_dtype(pl.NUMERIC_DTYPES)).collect() + data = data[[s.name for s in data if not s.is_infinite().sum()]] + data = data[[s.name for s in data if not (s.null_count() == data.height)]] + if not data.is_empty(): + try: + data = data.drop_nulls() + except: + pass + + return data + def get_video_data(w, groups=['basic'], mode='water'): modes = [mode, 'both', 'basic'] @@ -798,14 +812,11 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, datadf = datadf.filter( pl.col("spm") >=0, - pl.col("efficiency")<=200, pl.col("spm")>=10, pl.col("pace")<=300*1000., - pl.col("efficiency")>=0, pl.col("pace")>=60*1000, pl.col("power")<=5000, pl.col("spm")<=120, - pl.col("wash")>=1 ) @@ -823,7 +834,10 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, datadf = datadf.filter(pl.col("rhythm")>=0, pl.col("rhythm")<=70, pl.col("power")>=20, + pl.col("efficiency")<=200, pl.col("drivelength")>=0.5, + pl.col("wash")>=1, + pl.col("efficiency")>=0, pl.col("forceratio")>=0.2, pl.col("forceratio")<=1.0, pl.col("drivespeed")>=0.5, @@ -833,6 +847,7 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, pl.col("catch")<=-30) + # workoutstateswork = [1, 4, 5, 8, 9, 6, 7] workoutstatesrest = [3] # workoutstatetransition = [0, 2, 10, 11, 12, 13] @@ -850,6 +865,8 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, if ratio < 0.01 or after[workoutid] < 2: return data_orig + + return datadf @@ -1541,9 +1558,8 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp workstrokesonly=workstrokesonly, for_chart=for_chart) - data = data.fill_nan(None).drop_nulls() - return data + data = remove_nulls_pl(data) if not df.is_empty(): df = df.fill_nan(None).drop_nulls() diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 80517b36..c5d79779 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1588,19 +1588,19 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, except (KeyError, ColumnNotFoundError): yparam1 = 'None' - datadf = datadf.fill_nan(None).drop_nulls() + datadf = dataprep.remove_nulls_pl(datadf) # test if we have drive energy try: # pragma: no cover _ = datadf['driveenergy'].mean() - except KeyError: # pragma: no cover - datadf['driveenergy'] = 500. + except (KeyError, ColumnNotFoundError): # pragma: no cover + datadf = datadf.with_columns((pl.lit(500)).alias("driveenergy")) # test if we have power try: # pragma: no cover _ = datadf['power'].mean() - except KeyError: # pragma: no cover - datadf['power'] = 50. + except (KeyError, ColumnNotFoundError): # pragma: no cover + datadf = datadf.with_columns((pl.lit(50)).alias("power")) yparamname1 = axlabels[yparam1] if yparam2 != 'None': @@ -1687,8 +1687,8 @@ def interactive_flexchart_stacked(id, r, xparam='time', columns = [name for name, d in metrics.rowingmetrics] columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] - columns = columns + ['spm', 'driveenergy', 'distance'] - columns_basic = columns_basic + ['spm', 'driveenergy', 'distance'] + columns = columns + ['spm', 'driveenergy', 'distance','workoutid','workoutstate'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance','workoutid','workoutstate'] rowdata = pd.DataFrame() row = Workout.objects.get(id=id) @@ -1763,7 +1763,7 @@ def interactive_flexchart_stacked(id, r, xparam='time', rowdata = rowdata.with_columns(y4=pl.col("time")) rowdata = rowdata.with_columns((pl.col("y4")).alias(yparam4)) - + # replace nans rowdata = rowdata.fill_nan(0) @@ -1783,7 +1783,7 @@ def interactive_flexchart_stacked(id, r, xparam='time', 'metrics': metrics_list, } - script, div = get_chart("/stacked", chart_data) + script, div = get_chart("/stacked", chart_data, debug=False) return script, div diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 80a12eb1..012dddf2 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -272,7 +272,7 @@ def mocked_fetchcperg(*args, **kwargs): return df -import pandas as pd + def mocked_read_df_sql(id): # pragma: no cover df = pd.read_csv('rowers/tests/testdata/fake_strokedata.csv') @@ -310,7 +310,7 @@ def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover return df, row def mocked_getsmallrowdata_uh(*args, **kwargs): # pragma: no cover - df = pd.read_csv('rowers/tests/testdata/uhfull.csv') + df = pl.read_csv('rowers/tests/testdata/uhfull.csv') return df @@ -390,7 +390,7 @@ def mocked_read_cols_df_sql(*args, **kwargs): def mock_workout_summaries(*args, **kwargs): - df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv') + df = pl.read_csv('rowers/tests/testdata/workout_summaries.csv') return df def mocked_read_df_cols_sql_multi(ids, columns, convertnewtons=True): # pragma: no cover diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 5e57721c..113faeb6 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -582,7 +582,7 @@ class DataPrepTests(TestCase): age = dataprep.calculate_age(born,today=today) self.assertEqual(age,49) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh) + @patch('rowers.dataprep.getsmallrowdata_pl',side_effect=mocked_getsmallrowdata_uh) def test_goldmedalstandard(self,mocked_getsmallrowdata_uh): maxvalue, delta = dataprep.calculate_goldmedalstandard(self.r,self.wuh_otw) records = CalcAgePerformance.objects.filter( diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 132867e104e5cbf19a16c3afdbd4a693272e648a..664305075f62deffbed4cd82b8f8d786cd9359b3 100644 GIT binary patch delta 456 zcmV;(0XP1jAD|xxABzYGMOh({2QmW343RrXe`mK@?xmgAn|pU(_5Jz&V)6FvTXWo9 zoUYd9=yMDROH+ba)?>4K? z&hVDa*~RgK0C4fglgH1tr}$yD-t^u2Xqk@MtoC>3eSCJ*_1j;2qZ`=MAN7XJyUmNg z566VLa`}+#Ti`DUQ zy0vBh>g>g{Gy;1Je=nhvmsUFa623d`e>_<14!E;B)5qVKKVN=Y?Y=*FaFIBkU3}R0 zX_wxfi?g?zZvCP=IoUp~^l`O5e$n+^I&ZgoyAqX8#~s{%-M>1!xbm9ZKR-WN9d&*B z>6b6Y<+;B)S^Dnh*Q=8kmp}LS^ggCTuRNR&Tb^|PNtevda)PU$zus-NPMf`a+Hbqz z^3i^A{)6Y~rVoExo-Dh~GI7YCK0LZB_jT~NTOU7qc=*fH%x3%NZu_t4Eq?m?^v`9w yGk_lecK;tNu6(h3^4rIBcxQ`xwNB6BK|;B_c$l8XpXt(vcm4+o)fi>OfB^vEdj%!{ delta 456 zcmV;(0XP1jAD|xxABzYG=M^B42QmUv3UFTtvT*4 zPFHJlbauK(Uv7?GEl<16;`+lEZ|Lss6#Lz~Z#c{Sqki>f)xZDcUB6uC8@zIYcbnB` zXL!rz?BaMq0J!+$$>V3+Q~a=6Z~AV1v`j~BR{Oj2K0Z6@`t7g1(GBeBk9tGq-R9+A z5+C^Yu7A*-oL#Jzo5dUa>;3g?sSGy;1Je}AT#=m?hXMS$;)`yUS$y94g*&h+s&=FgX(R=e*H9$X}jXBQv# zecGkB=i=<`rdz-4PENK@D}7w8k6(6um(JVm-mXOD({Ts)U-z%hF0Q;L_s`EyR!3c* ze){E$ae3~qPL{s=`St4L<>k-)J-v_V&?^t;!NpZ42s zxO}u)7Z20Zc#$rBc;|l&JZ&t*fB^vW4g}l) diff --git a/rowers/utils.py b/rowers/utils.py index 7cb1413b..95e5fb78 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -440,17 +440,19 @@ def wavg(group, avg_name, weight_name): """ try: d = group[avg_name] - except KeyError: + except (KeyError, ColumnNotFoundError): return 0 try: w = group[weight_name] - except KeyError: + except (KeyError, ColumnNotFoundError): return d.mean() try: return (d * w).sum() / w.sum() except ZeroDivisionError: # pragma: no cover return d.mean() + return 0 + from string import Formatter def totaltime_sec_to_string(totaltime, shorten=False): diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index cbab1c80..86ed09d3 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -932,12 +932,16 @@ def boxplotdata(workouts, options): datadf = dataprep.clean_df_stats_pl(datadf, workstrokesonly=workstrokesonly) - datadf = datadf.filter( - pl.col("spm")>spmmin, - pl.col("spm")workmin, - pl.col("driveenergy")spmmin, + pl.col("spm")workmin, + pl.col("driveenergy") Date: Thu, 18 Apr 2024 20:30:07 +0200 Subject: [PATCH 104/121] passing tests --- rowers/dataroutines.py | 16 +++++++++++++--- rowers/interactiveplots.py | 6 ++++-- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 9f9e571c..aed97603 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -685,7 +685,6 @@ def replace_zeros_with_nan(x): def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, ignoreadvanced=False, for_chart=False): # clean data remove zeros and negative values - try: _ = datadf['workoutid'].unique() except KeyError: @@ -2047,8 +2046,14 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar df = df.with_columns((500./pl.col(' Stroke500mPace (sec/500m)')).alias(' AverageBoatSpeed (m/s)')) if ' WorkoutState' not in df.columns: df = df.with_columns((pl.lit(0)).alias(" WorkoutState")) - df = df.with_columns((100.*pl.col(" DriveTime (ms)")/(pl.col(" DriveTime (ms)")+pl.col(" StrokeRecoveryTime (ms)"))).alias("rhythm")) - df = df.with_columns((pl.col(" AverageDriveForce (lbs)")/pl.col(" PeakDriveForce (lbs)")).alias("forceratio")) + if df[" DriveTime (ms)"].mean() > 0: + df = df.with_columns((100.*pl.col(" DriveTime (ms)")/(pl.col(" DriveTime (ms)")+pl.col(" StrokeRecoveryTime (ms)"))).alias("rhythm")) + else: + df = df.with_columns((pl.lit(0)).alias("rhythm")) + if df[" PeakDriveForce (lbs)"].mean() > 0: + df = df.with_columns((pl.col(" AverageDriveForce (lbs)")/pl.col(" PeakDriveForce (lbs)")).alias("forceratio")) + else: + df = df.with_columns((pl.lit(0)).alias("forceratio")) f = df['TimeStamp (sec)'].diff().mean() if f != 0 and not np.isinf(f): try: @@ -2085,6 +2090,9 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar pass df = df.with_columns((pl.col(" DriveLength (meters)") / pl.col(" DriveTime (ms)") * 1.0e3).alias("drivespeed")) + if df[" DriveTime (ms)"].mean() == 0: + df = df.with_columns((pl.lit(0)).alias("drivespeed")) + if 'driveenergy' not in df.columns: if forceunit == 'lbs': @@ -2213,6 +2221,8 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar ergpw = 2.8*data['velo']**3 efficiency = 100. * ergpw / data['power'] + if data['power'].mean() == 0: + efficiency = 100.+0.0*data['power'] data = data.with_columns(efficiency=efficiency) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c5d79779..b71a7260 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1811,6 +1811,7 @@ def interactive_flex_chart2(id, r, promember=0, rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=True, workstrokesonly=workstrokesonly, for_chart=True) + if r.usersmooth > 1: # pragma: no cover for column in columns: try: @@ -1858,8 +1859,9 @@ def interactive_flex_chart2(id, r, promember=0, except (KeyError, TypeError, ColumnNotFoundError): rowdata = rowdata.with_columns(power=pl.lit(50)) + # replace nans - rowdata = rowdata.fill_nan(0) + rowdata = dataprep.remove_nulls_pl(rowdata) row = Workout.objects.get(id=id) if rowdata.is_empty(): @@ -1963,7 +1965,7 @@ def interactive_flex_chart2(id, r, promember=0, 'trendline': trendline, 'plottype': plottype, } - + script, div = get_chart("/flex", chart_data, debug=False) return script, div, workstrokesonly diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 664305075f62deffbed4cd82b8f8d786cd9359b3..6170c42cfcd250386d352fa73fcb8a039af814e5 100644 GIT binary patch literal 4001 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 H#DD<+{U|`a literal 4000 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 GfB^tX!5OUp From ac7c114649a0383c560d2c298e1a79cfca829eb7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 19 Apr 2024 18:17:54 +0200 Subject: [PATCH 105/121] fix --- rowers/dataroutines.py | 146 ++++++++++++++++++++++++-- rowers/interactiveplots.py | 60 +++++------ rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes 3 files changed, 166 insertions(+), 40 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index aed97603..8671ee65 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1,4 +1,4 @@ -from rowers.metrics import axes, calc_trimp, rowingmetrics, dtypes, metricsgroups +from rowers.metrics import axes, calc_trimp, rowingmetrics, dtypes, metricsgroups, metricsdicts from rowers.utils import lbstoN, wavg, dologging from rowers.mytypes import otwtypes, otetypes, rowtypes import glob @@ -35,7 +35,7 @@ import polars as pl import polars.selectors as cs from polars.exceptions import ( ColumnNotFoundError, SchemaError, ComputeError, - InvalidOperationError + InvalidOperationError, ShapeError ) from rowingdata import ( @@ -178,10 +178,18 @@ columndict = { } def remove_nulls_pl(data): - data = data.lazy().fill_nan(None) + data = data.lazy().with_columns( + pl.when( + pl.all().is_infinite() + ).then(None).otherwise(pl.all()).keep_name() + ) + data = data.select(pl.all().forward_fill()) + data = data.select(pl.all().backward_fill()) + data = data.fill_nan(None) data = data.select(cs.by_dtype(pl.NUMERIC_DTYPES)).collect() data = data[[s.name for s in data if not s.is_infinite().sum()]] data = data[[s.name for s in data if not (s.null_count() == data.height)]] + if not data.is_empty(): try: data = data.drop_nulls() @@ -1566,7 +1574,134 @@ def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, comp return df - +def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True): + if ids: + csvfilenames = [ + 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] + else: + return pl.DataFrame() + + data = [] + columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid','workoutstate', 'driveenergy'] + columns = list(set(columns)) + + for id, f in zip(ids, csvfilenames): + if os.path.isfile(f): + df = pl.scan_parquet(f) + else: + rowdata, row = getrowdata(id=id) + try: + shutil.rmtree(f) + except: + pass + if rowdata and len(rowdata.df): + _ = dataplep(rowdata.df, id=id, + bands=True, otwpower=True, barchart=True, + polars=True) + df = pl.scan_parquet(f) + data.append(df) + + data = pl.collect_all(data) + + try: + datadf = pl.concat(data).select(columns) + except (SchemaError, ShapeError): + data = [ + df.select(columns) + for df in data] + + # float columns + floatcolumns = [] + intcolumns = [] + for c in columns: + try: + if metricsdicts[c]['numtype'] == 'float': + floatcolumns.append(c) + if metricsdicts[c]['numtype'] == 'integer': + intcolumns.append(c) + except KeyError: + pass + data = [ + df.with_columns( + cs.float().cast(pl.Float64) + ).with_columns( + cs.integer().cast(pl.Int64) + ).with_columns( + cs.by_name(intcolumns).cast(pl.Int64) + ).with_columns( + cs.by_name(floatcolumns).cast(pl.Float64) + ) + for df in data + ] + + datadf = pl.concat(data) + + + exprs = [] + + if workstrokesonly: + workoutstatesrest = [3] + exprs.append(~pl.col("workoutstate").is_in(workoutstatesrest)) + + # got data + if not doclean: + if exprs: + datadf2 = datadf.filter(exprs) + if not datadf2.is_empty(): + return datadf2 + + return datadf + + # do clean + if "spm" in datadf.columns: + exprs.append(pl.col("spm") >= 10 ) + exprs.append(pl.col("spm") <= 120) + if "pace" in datadf.columns: + exprs.append(pl.col("pace") <= 300*1000.) + exprs.append(pl.col("pace") >= 60*1000.) + if "power" in datadf.columns: + exprs.append(pl.col("power") <= 5000) + exprs.append(pl.col("power")>=20) + + if "rhythm" in datadf.columns: + exprs.append(pl.col("rhythm")>=0) + exprs.append(pl.col("rhythm")<=70) + if "efficiency" in datadf.columns: + exprs.append(pl.col("efficiency")<=200) + exprs.append(pl.col("efficiency")>=0) + if "wash" in datadf.columns: + exprs.append(pl.col("wash")>=1) + if "drivelength" in datadf.columns: + exprs.append(pl.col("drivelength")>=0.5) + if "forceratio" in datadf.columns: + exprs.append(pl.col("forceratio")>=0.2) + exprs.append(pl.col("forceratio")<=1.0) + if "drivespeed" in datadf.columns: + exprs.append(pl.col("drivespeed")>=0.5) + exprs.append(pl.col("drivespeed")<=4) + if "driveenergy" in datadf.columns: + exprs.append(pl.col("driveenergy")<=2000) + exprs.append(pl.col("driveenergy")>=100) + if "catch" in datadf.columns: + exprs.append(pl.col("catch")<=-30) + + if exprs: + datadf2 = datadf.filter(exprs) + + if not datadf2.is_empty(): + return datadf2 + + exprs = [] + if workstrokesonly: + workoutstatesrest = [3] + exprs.append(~pl.col("workoutstate").is_in(workoutstatesrest)) + + if exprs: + datadf2 = datadf.filter(exprs) + if not datadf2.is_empty(): + return datadf2 + + return datadf def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, debug=False, for_chart=False): @@ -2105,7 +2240,6 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar df = df.with_columns((pl.col(" AverageDriveForce (lbs)") * lbstoN).alias(" AverageDriveForce (lbs)")) df = df.with_columns((pl.col(" PeakDriveForce (lbs)") * lbstoN).alias(" PeakDriveForce (lbs)")) - if df["driveenergy"].mean() == 0 and df["driveenergy"].std() == 0: df = df.with_columns((0.0*pl.col("driveenergy")+100).alias("driveenergy")) @@ -2113,7 +2247,7 @@ def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchar t2 = df["TimeStamp (sec)"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) p2 = df[" Stroke500mPace"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) - + data = pl.DataFrame( dict( time=df["TimeStamp (sec)"] * 1e3, diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index b71a7260..f5aa990b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -459,13 +459,13 @@ def interactive_forcecurve(theworkouts): columns = columns + [name for name, d in metrics.rowingmetrics] - rowdata = dataprep.getsmallrowdata_pl(columns, ids=ids, + rowdata = dataprep.read_data(columns, ids=ids, workstrokesonly=False) if rowdata.is_empty(): return "", "No Valid Data Available" - rowdata = rowdata.fill_nan(None).drop_nulls() + rowdata = dataprep.remove_nulls_pl(rowdata) data_dict = rowdata.to_dicts() @@ -796,10 +796,10 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, ids = [int(w.id) for w in theworkouts] - columns = [name for name, d in metrics.rowingmetrics]+['spm', 'driveenergy', 'distance', 'workoutstate', 'workoutid'] + columns = [histoparam, 'spm', 'driveenergy', 'distance', 'workoutstate', 'workoutid'] workstrokesonly = not includereststrokes - rowdata = dataprep.getsmallrowdata_pl( + rowdata = dataprep.read_data( columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly) rowdata = rowdata.fill_nan(None).drop_nulls() @@ -1190,7 +1190,7 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover 'workoutstate', 'workoutid', 'driveenergy', 'cumdist'] columns = columns + [name for name, d in metrics.rowingmetrics] - rowdata = dataprep.getsmallrowdata_pl(columns, ids=workoutids, + rowdata = dataprep.read_data(columns, ids=workoutids, workstrokesonly=False) rowdata = rowdata.fill_nan(None).drop_nulls() @@ -1379,7 +1379,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] - datadf = dataprep.getsmallrowdata_pl(columns, ids=[id]) + datadf = dataprep.read_data(columns, ids=[id]) if datadf.is_empty(): return "", "No Valid Data Available" @@ -1572,10 +1572,10 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, datadf = pd.DataFrame() if promember: - datadf = dataprep.getsmallrowdata_pl(columns, ids=ids, doclean=True, + datadf = dataprep.read_data(columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) else: - datadf = dataprep.getsmallrowdata_pl(columns_basic, ids=ids, doclean=True, + datadf = dataprep.read_data(columns_basic, ids=ids, doclean=True, workstrokesonly=workstrokesonly, for_chart=True) try: @@ -1803,15 +1803,15 @@ def interactive_flex_chart2(id, r, promember=0, columns = columns + ['spm', 'driveenergy', 'distance','workoutstate'] columns_basic = columns_basic + ['spm', 'driveenergy', 'distance','workoutstate'] - datadf = pd.DataFrame() if promember: - rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=True, - workstrokesonly=workstrokesonly, for_chart=True) + rowdata = dataprep.read_data(columns, ids=[id], doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=True, - workstrokesonly=workstrokesonly, for_chart=True) + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) + if r.usersmooth > 1: # pragma: no cover for column in columns: try: @@ -1825,18 +1825,16 @@ def interactive_flex_chart2(id, r, promember=0, except KeyError: pass - try: - if len(rowdata) < 2: - if promember: - rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], - doclean=False, - workstrokesonly=False, for_chart=True) - else: - rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=False, - workstrokesonly=False, for_chart=True) - workstrokesonly = False - except (KeyError, TypeError): # pragma: no cover - workstrokesonly = False + if len(rowdata) < 2: + if promember: + rowdata = dataprep.read_data(columns, ids=[id], + doclean=False, + workstrokesonly=False, for_chart=True) + else: + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=False, + workstrokesonly=False, for_chart=True) + workstrokesonly = False + try: _ = rowdata[yparam2] except (KeyError, TypeError, ColumnNotFoundError): # pragma: no cover @@ -1867,14 +1865,6 @@ def interactive_flex_chart2(id, r, promember=0, if rowdata.is_empty(): return "", "No valid data", workstrokesonly - workoutstatesrest = [3] - - if workstrokesonly: # pragma: no cover - try: - rowdata = rowdata.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) - except (KeyError, ColumnNotFoundError): - pass - try: tseconds = rowdata['time'] except (KeyError, ColumnNotFoundError): # pragma: no cover @@ -2205,9 +2195,11 @@ def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg= columns = ['workoutid', 'hr', 'power', 'time'] - df = dataprep.getsmallrowdata_pl(columns, ids=ids) + df = dataprep.read_data(columns, ids=ids, workstrokesonly=False, doclean=False) + df = dataprep.remove_nulls_pl(df) df = df.with_columns((pl.col("time").diff().clip(0, 20*1.e3)).alias("deltat")).lazy() + hrzones = rower.hrzones powerzones = rower.powerzones diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 6170c42cfcd250386d352fa73fcb8a039af814e5..76c3e0acba2bb4cbbe0ea247f1e10a75215adc2a 100644 GIT binary patch literal 4000 zcmV;R4`1*fiwFoaXCY<+|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVPN)K__!#F z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7Xtb{`Gw?|pal;PB;Dw_2~(r;nHGetGqz>$~5&&2lg8yxx4f|EljV4;G8J zZ{M2J?&^HCHpds|i}dB@_|@{f+bnKBeDQ|%cc(b$-u;EMJUZ@IZ&v;LAKvxLb-uwH zCwRA6eR77kY%Z=&7X*NdU!Onwd3%b7tM#Vu*2l|q)Mj5_~8Mh zpOHU)-0*(y=gW(${@eY3_OF&#`@1jG*H2H52+%zQc}VaQ`M}}|L$-6h;*7?y0h2IBedxvyFWi(VR6ePZhZZ`d$&4&eg1#C z0dcq7z%7@!@%7X7_EqiQA8p@?Wg>gM<>rGtS*8bkM3^7r?(Z8EuU4n0>DHG0tBaGP z=iT~sca|uAvi0WP-SK}vx%=&w%Zt^zzqvVok=}v7@Gfq5`}D}qUI{UF_%+!ca8Cj^ zDUkny!j8o42tIkhaLe=E?r=SNZN9kd%O8LJ%QL~PWBBK1Oy~Sp*Y`Fz3^KQSz{36_jS4*A2Bxx6`ppI;C8rbuLu82Fiinu3;2rekNQ_`CXau0XsPIbw< zm5R7KLyW3XJ`E9Ekb9gBcV{c&9+SLV@fz;2q`WiChP$tbyQS?o?-bl^FjSB`-h(?< z#XUep+$C{OLZ#8>y>S!aUYEQFu86xg!GqK2^Ujqt)f;mWy$kB$WvY9 zshTOC4j`FwqBG?XrKInM?R;dOr6Tgy#H7@#WS+!?g5+gJkIWc-)(-1+&;^8yu-XJZ+KHRKEX?&6HdQ$^&RL8o)#pz|f?c?)f{ zABB7{k;H1kbLc}!|J}XFx6yu(JfU#}UNzc>vgGFzp+l+2sv~Oz5LFspj3w>)Igz)a zBJxR3XF^r-6~vNJI!iMe?Xe>A(F6o_d0x;YkyqLC3{W&; zGo#VICYcv&(z6B9kVl-1Zr`G`!{nXuf{jjd{)XGRkzLLS>Z ze>-{3xwi~R8c$AoQA%cATi5*dqmUN^3_&IHLPaAsjr=&|y$MO;QA6HwlleQ6=X%H^ z7zq$mO)@+em3iNE`%%cVVT+=g=K~kj@O+c`QOG-k5k)2Q(H8aIHkluX{5JPi)dJ0y zt*E>gd2BwsA@Vu*R;)_5w`D6TZD=w-3VCbNP#-nAy(L#v=0mgUMj`JFMRXePtz}m< z-#)a_ehl&q+m=`w-QHSPRJRW;K0gY1GL9jtn&3%U@^h-FbQO^oV})5Y&%2_vw6h{F z6_F1naaUzv+Oidu0!`+}AaBtmjnk2;C3!W6OwMPl6hM; z>pHet+EK`RBdm+qh?FJYe0U>}w+XgEZ&g{>;u`t8lD8F+ClkD`x=K7wCO+R}ewe%% zOQ@=@l2Ep;uJynihkP)EsOp>`v22ZvZ@T>$B~H_kwB!0(mI?D zd2XJZLGmtVU8_1FI>Ms5{hY|xG}=c4h(QydcT7dAsC|pik3l}?-UbYszPHX&(cIfP zk*~;}Cu_(dtLAy3$>iR0tDhev?+scc)oAais69U;@{TGZpF1ZIYx>(e&qe3?IgzhO zw@=y{M^vp^<^s1U?F`6^0T5M}a~HWqX{SHhvo|8TPKX{F`MZ*@NVm6O*o(@OT5>LE{%dt25_xMV{Zo;rqU+e?`qP$~F#>s?^YB#t@FEvo$42aox}VlWKAPY#s${-! zHkNIDZ^s~S$vDwfSH;5FSaVvJwxTF)l3!t^Ztq-Ct8Px@EAr&nU?>u3^6jJdMMrzr z)}kJR{4V!aH_wMq)T)~k`HDO_k}NbPP?3+JXiko6^ZY2}oe7p$6QxCfvSHdep|8l5 zBQYo3Lr|e7Xz1_Q|FoL%-sR4>F6zd61jeE%_xC{$m7$l4&`WNZ0=fa;(qzzM>vuhf zJ{UnlQlU?(Ay34pZ&E*qKDRR^WF2~Ei`sV5=K4|4TVn-PK3oJZP3rH6o+?7`bBipG zD)hlkM(Qg=&#oTy$xgF@z^c$kUsTPT(}0f^q31N$6R75TgfJQOwGH?+Nxd@+NUFIW zv23}`oX~Sc=wsIKqfY9}u3Fg8kApsULUpXl%)&HdH4T3T=p_&Ms*o+BP|;}poYcIE zj5`j7JO-79kFI(1?rPkHdeEn_8(i*>r$O(F_QAbZMkX}lZVu^D%R z=zT)3Yod{}xr(CahJFbZx^~31Bp&&sw;Cm=qSdUH38v{Ki0ag~=AqAXFM_g=anS^e8pYVO8BpEE24b@lZuW!tGxQ}aea&qe^FPU>Bl z%z%$AL2D4bGZuAg86x{wv~e0WyM7e((NILI)9^#t@C`R_-WcdD<^EYZZyv|k)V%2r z_*M*vvhd0xOeS7mTQYV)*QLR=(jBsy-LlqVw+26`i|Uf%S80Lx!%cS60}-c<^cMzZJ7wVC2!DrX~x~u z?fQ^gCR!0xIdW}i)i-xVA1Xp0atAG4R}CA>hHtnH_+y|)G|b{uQZG<;xq6dmulz#juWfk9$q6?zMkS=+FA^M=uHi>yf% zuTK_1E3%$})VthOBkIsQDr(%dt{UT@4_WHHZov0E8L4jr{vdir6NGi~dI`3uhTnp= z0QhD6)EdDC~~vNxW|s)Du-++CN*{ zb~$5cy>v!F?=t$RvgbYzQ2DG5< zDCi|OR3V+zTPYg3gQn(imBi>w(`;Ts=(v+HMoJ{n2ZolfebpNw5kP0brZ&t$|}UHB$IXmBK{m(HxvOGW5oUegwIQtz?ofbUxg#u(^@jI$V2 zSvnyUt+c%t`c`u{j9zl}O;BazhFG-H_Fe;i>!mY{KIAoRb&6TjMzG3&j7tOX{J{#fIPUOk6!n$F0O98 zCXX&J&sN7>pMLuFi*bGKFHV-e`~LOn?Bx3A{*vCubm)zT^I^;L?my|0*;#P&^Ow7= z)@ieskNa&mTtC{+&VTqa-Sp8<%d=&-Stbto(}zcQH)dC%=76M-R5BSL^f~9w(H`t0(Dc{FW|#^x%KNZ#CD% GfB^twggg%b literal 4001 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 H#DD<+{U|`a From db0bf05ab2a2be3c74b04190a692939d81fddd67 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 20 Apr 2024 15:11:28 +0200 Subject: [PATCH 106/121] running tests --- rowers/dataroutines.py | 3 ++- rowers/interactiveplots.py | 12 ++++++++---- rowers/tests/mocks.py | 2 +- rowers/tests/test_unit_tests.py | 8 ++++---- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 8671ee65..ce986ac3 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1588,6 +1588,7 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for id, f in zip(ids, csvfilenames): if os.path.isfile(f): df = pl.scan_parquet(f) + data.append(df) else: rowdata, row = getrowdata(id=id) try: @@ -1599,7 +1600,7 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, bands=True, otwpower=True, barchart=True, polars=True) df = pl.scan_parquet(f) - data.append(df) + data.append(df) data = pl.collect_all(data) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f5aa990b..0098c007 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1694,12 +1694,14 @@ def interactive_flexchart_stacked(id, r, xparam='time', row = Workout.objects.get(id=id) if ispromember(r.user): - rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], doclean=True, + rowdata = dataprep.read_data(columns, ids=[id], doclean=True, workstrokesonly=False, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], doclean=True, + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=True, workstrokesonly=False, for_chart=True) + rowdata = dataprep.remove_nulls_pl(rowdata) + if r.usersmooth > 1: # pragma: no cover for column in columns: try: @@ -1715,16 +1717,18 @@ def interactive_flexchart_stacked(id, r, xparam='time', if len(rowdata) < 2: if ispromember(r.user): - rowdata = dataprep.getsmallrowdata_pl(columns, ids=[id], + rowdata = dataprep.read_data(columns, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) else: - rowdata = dataprep.getsmallrowdata_pl(columns_basic, ids=[id], + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=False, workstrokesonly=False, for_chart=True) + rowdata = dataprep.remove_nulls_pl(rowdata) + if rowdata.is_empty(): return "", "No valid data" diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 012dddf2..cfae2db4 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -336,7 +336,7 @@ def mocked_getsmallrowdata_db(*args, **kwargs): return df -def mocked_getsmallrowdata_pl(*args, **kwargs): +def mocked_read_data(*args, **kwargs): df = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') return df diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 113faeb6..3c59620a 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -582,7 +582,7 @@ class DataPrepTests(TestCase): age = dataprep.calculate_age(born,today=today) self.assertEqual(age,49) - @patch('rowers.dataprep.getsmallrowdata_pl',side_effect=mocked_getsmallrowdata_uh) + @patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_uh) def test_goldmedalstandard(self,mocked_getsmallrowdata_uh): maxvalue, delta = dataprep.calculate_goldmedalstandard(self.r,self.wuh_otw) records = CalcAgePerformance.objects.filter( @@ -703,7 +703,7 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_pl', side_effect=mocked_getsmallrowdata_pl) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_interactive_chart(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] @@ -735,9 +735,9 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_pl', side_effect=mocked_getsmallrowdata_pl) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_interactive_flexchart_stacked(self, mocked_sqlalchemy, - mocked_getsmallrowdata_pl): + mocked_read_data): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 76c3e0acba2bb4cbbe0ea247f1e10a75215adc2a..5bf43e1e916e3a856305c8da0fc7f1e54de12fcf 100644 GIT binary patch literal 3999 zcmV;Q4`A>giwFqxyd!1;|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!-|@7sZ$rn zI3keMA^{r(l+D|!ViX@kz0nQq>5qCt z=H2GyUmy7Qu7B8_oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dKR>(Z@8A7r|7v-0clTxb`ti#{0(AF4?h(8|-nY0<&O}EX;co!@00)b- z)5ravbmx1|`|hHDu{vGezx!9mUS232`r@qbPV!;Tmq%yomzzUjf3Wy!>!$-At#)tF z(`EXxn{=P~UcT7)Y44Bt4iUq_;_}m{9Udf6>yDTISf;!Gwdds{rTw%i)$`%A zh`Z(nuDQgOuOF|suWJAPaQjv)6WPlxS0CidWqQDeg!wV<{=P!-Vs(6+Zf)7WI(vEe zv|GRKP7=k>wqD)4JNoZucfZ|oezscoS2yP`(mU`C@8Wj1&yW1#l@McxUz6_}XX;Ijt|*F4|t4ws|X=BvxT_~rL+o(ZlU!@oRZI_JN-zE5Hk4;H(>zP#ol zUG&SJcl|Bq7vVa*TIviYNxN7Db!3Cnz@8s;Mcm0%#63Yoa6!SHlHOF1d$>7ws!QIj zRK(pGVpNUtX^7y0+~aJxJ6jR=nB?7x*Km&|<(*+R+cdn!v$21@Az8>xZ#s{<-dG}&uBHSw* z=PR<~giQn+HQa-gwBP3>@1Y{@&Nv{@=yg$w>htrFcZ7-*JBfx_=cYv{OP+5*p6Vh` z)lBiU56P4hohgSXC4Dz+=Ognh6_K|lCZ%2_^CTt|Brh`}A1flC%sD|0s_={}YR9|z zkhiWL@(9LRB$do3c7iUDCDkAR;I-L^-oi91hTWF*G zDCC2QBvuoiLmx`|?`}oDjrN1&35_H0s?k1_B|o1C9ZE%39a$rQsM7FaENRcriM$OJ zkxzO$6RMK0AeM~MS(?#kj}?)RCLnmLO1o?1XF#6GxQJ0jJ}2iEBtIj+eNCf1i(v;| zGtZMPOWw|id_@r(4h8_cXvhncjLzHJ`0cq_bwlKn{91>qG%S)>(uX%I@)h~;EE`6& znkX&#q8av${7B@T0jne*U@|ga+dN-Ul-360)2pbm=OY;3G`y+L^MWRcyvm+ufT9tb z8IATe$-G#To-L4uJmO?@`xd1gChv?F44Nn{6Bf1S=S03Ddp=ueY*q6-GnzON^4RA2 z>&bJ@y=6escyiKy5_eZg}fMG2r8KuDjKnAk3yaeTNKqiAGoN7=bOxrLf#pSC@Ptcwy5{E$^1Cvx4E~f7HGC? zMdhu?WAot+k-(}8}D>cS{q}@oSZq4ucyX_ zbMCG4h?>z}5GEt@&7L179}NYFK}9|`-G2HJ8=>4SMHQtLhNjz3U*^S{kaT;M%-gbA z*Rj>ojzZoWVO_*Vq%8U7!yAFTO|T7mtIE0-*T~ zbMxd3l6N`lTGa{B5f;_$=S04y(LNeL44U}7V=7uj?OS|)4DvbmHek^7y>*s~=HAYU zd`0#=Swjw4HO~u8Cij+G{rn(#Z_pyCMtes^?fDszcT^Gi+&O_*)8F2CE;`T8iF`%6 zebUxAqH5JL7q~@fXFy&IfT+5hyT~m{JN?m~y%EuMLiEtc-;{hsy1fO%UR0hOkG7W~~oRK|WQGDJ8W06slljGCd zVkbhrHXmN7hrD2}rB$tI;iYK({G4RICJj&6cp=pI@Vs*+{r0n(=PR=3ll*cdRgI1J zu2odtkbFf^T8RdjKsDM2HyPc&d2a{E%RZp-N*|t&MOPu8mCQ>;&x z$-JwGyf+908mlf~n2gLf^26k#acQvEjP`*-QG0$)qkX7{Jd&{%qbBP*aBMPvL-MgA z@?sz;M|)dzrNUWxaw;0_(Hk$K>Xs5*(bY-k(Dz})V7m0*N=kU8Y`&s;UaixQh!7AR1tcgTV#1u zp$~2{QePQ*cJ-i7cA5YZUgQqA>< zWy@{mgq|xxAG3xZby8n;)xw5;9Q3&ps$*4V7N!}iY4|fhFL}UMg=`Upibm__q~=v* z+;K4EF{m_rbj_Q0Q{yhwgFcPj;BtRF4SHX+5ALlpGNBoFgXpaRAl6BJu#-WL&A1yx z?-P1m6OEkBRTMop^rN7UCR(LShrksz?x=b5#z3Ex^@zH%8>H;`Z&E*b^kSUXqNlR0 z(&i-fP!alIL`c;D8!1_YzM-GCOULKVnAVA?j5|sSf=r}+4z^|K2hm%@Ue`V+FN`sn z&PmO#A4cyD1qr$V-$T*DdTeUm80awxu(IF|Dd1!};$ln797N9sovv#ZQY^Y~>8u8P zstCO^U`dye%fORaKkS^nWkwiOJ^9pH{J@XG<>#Y*SVoqb2kS1oM9=btFLD%+fId=nl}o1HUb!RQt!fK z27GJ@T7&4Fv8Y?i5ZT9~jnk;v^`oGVh9X*>h9Am?Z@78$#z1c=_s`OK^Ek$)=1qUV zw_-q)g;y3~GV%J_lCdia+S;VCA;qXdPt@l6>7y4gQMqt2TBf3ryXM0m2R)nUkaXxR zPiA?u=ExmJzipZ5Rcf9U+gv)+cjRtcCZgBP^-d;}pw-ec2hfLY%S6yEd4twVGw!Bt z*N5CP(TbqTk!wS%zPTy-P!al&J80>;YS>sde8X+P9|Jw2VHT&7dV#Xb)$@$hJg5l0 z-L_Uxsd-YkjnbUz>up8oy&YW1vUM4ONs<&t*$6papG5 zK`*(X3hAWYO3}z2G&OG&^ue%T&`G_xqNy59%^L+hCF72itrfg;&5=8Gmrl$r6G6MC zt@nO1b3L`7?I8MGWX+%o-}vC0T|Wc#(MYoHbW$JvWbArsYTgifCL`AB!Z!gzv+HMo zUUK0T=+I+P^m(bEHsFtdJ{Vxs^}`Q2d4D44sd@8;(C3w`0fP=b$H|~?>(>vXXA^{U zk-PMwl+^HZCyIuC1oX~$wo&EM2@XrTbY_KKDncLgnzo>mdXGg1eBVki#y~G*oW-EZ z(g~qxrR}ZIx0<_Q^pdM@f+{07#G;k9w;J$UFP&lZA+Kqxi`+#hy4cyQH2hk!bOg35 zSp#~NBR8T<#-#(TZ{8sK?Ml|Vb?zdS4cbDR>qkNFbM=i9J^RV5ZrSGgLG*bgYerIe z^P=@dt8b|F%^L+hZx=CF8Fx`)TitTv-n@+7<-#i^dKZgIeQT6D4*Hl2uXJ{O4A7dV zP28K8&~I0=Rz~h(#MT*m2I#FxGrej76ar{Ha&L;>kA?oj){h$w7EikK%MZ`a`sL>E zyOaKhpHG&j%XPo^{dUv$dtJW=@BE+91_}~o3Ev&}KO8J}2i)16>EmzApD#bHcHbX7 zyht3+E($B2%b)vOdLPrFS02uXEl<1uq)TRJ!PU=S@3vZ} z&0aq3x7~30XumlB!Haa$hrcdQmfdEVIOI zKYM-p$1>d+z&(K7{|AdJU+kXz_Awpa*`i*p({p&3P%bYXrKj;ny7b|l{{ijikJ7|| F0RVuREEfO( literal 4000 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 GfB^twggg%b From 30bf61cbe16b8aa43b00e99e13a9bb6341693230 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 20 Apr 2024 17:14:22 +0200 Subject: [PATCH 107/121] testing --- rowers/dataprep.py | 17 ++--- rowers/dataroutines.py | 96 +++----------------------- rowers/interactiveplots.py | 70 ++++++++----------- rowers/tests/mocks.py | 4 +- rowers/tests/test_analysis.py | 12 ++-- rowers/tests/test_emails.py | 4 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/utils.py | 1 + rowers/views/analysisviews.py | 27 ++++---- rowers/views/statements.py | 2 +- 10 files changed, 76 insertions(+), 157 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 5983bb91..275f8684 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -29,6 +29,7 @@ import itertools import numpy as np import pandas as pd import polars as pl +from polars.exceptions import ColumnNotFoundError from zipfile import BadZipFile import zipfile import os @@ -422,7 +423,8 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): try: df = pl.read_parquet(cpfile) except: - df = getsmallrowdata_pl(['power'], ids=[workout.id]) + df = read_data(['power'], ids=[workout.id]) + df = remove_nulls_pl(df) background = True if settings.TESTING: background = False @@ -525,8 +527,9 @@ def setcp(workout, background=False, recurrance=True): except Exception as e: pass - strokesdf = getsmallrowdata_pl( + strokesdf = read_data( ['power', 'workoutid', 'time'], ids=[workout.id]) + strokesdf = remove_nulls_pl(strokesdf) if strokesdf.is_empty(): return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) @@ -617,14 +620,10 @@ def update_wps(r, types, mode='water', asynchron=True): mode ) - df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids) + df = read_data(['time', 'driveenergy'], ids=ids) try: - mask = df['driveenergy'] > 100 - except (KeyError, TypeError): - return False - try: - wps_median = int(df.loc[mask, 'driveenergy'].median()) + wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median()) if mode == 'water': r.median_wps = wps_median else: # pragma: no cover @@ -635,6 +634,8 @@ def update_wps(r, types, mode='water', asynchron=True): pass except OverflowError: pass + except ColumnNotFoundError: + pass return True diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index ce986ac3..90f50aeb 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1488,90 +1488,6 @@ def getrowdata_pl(id=0, doclean=False, convertnewtons=True, return data, row -# Fetch a subset of the data from the DB - -def getsmallrowdata_pl(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, - debug=False, for_chart=False): - if ids: - csvfilenames = [ - 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] - else: - return pl.DataFrame() - - data = [] - columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid'] - columns = list(set(columns)) - - df = pl.DataFrame() - if len(ids) > 1: - for id, f in zip(ids, csvfilenames): - try: - df = pl.read_parquet(f, columns=columns) - data.append(df) - except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover - rowdata, row = getrowdata(id=id) - try: - shutil.rmtree(f) - except: - pass - if rowdata and len(rowdata.df): - _ = dataplep(rowdata.df, id=id, - bands=True, otwpower=True, barchart=True, - polars=True) - try: - df = pl.read_parquet(f, columns=columns) - data.append(df) - except (OSError, ArrowInvalid, IndexError): - pass - try: - df = pl.concat(data, rechunk=True) - except ValueError: # pragma: no cover - return pl.DataFrame() - except SchemaError: - df = pl.concat(data, rechunk=True, how='vertical_relaxed') - - - else: - try: - df = pl.read_parquet(csvfilenames[0], columns=columns) - rowdata, row = getrowdata(id=ids[0]) - except (OSError, IndexError, ArrowInvalid): - rowdata, row = getrowdata(id=ids[0]) - if rowdata and len(rowdata.df): # pragma: no cover - data = dataplep( - rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) - try: - df = pl.read_parquet(csvfilenames[0], columns=columns) - except: - df = pl.DataFrame - else: - df = pl.DataFrame() - except: - rowdata, row = getrowdata(id=ids[0]) - if rowdata and len(rowdata.df): # pragma: no cover - data = dataplep( - rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) - try: - df = pl.read_parquet(csvfilenames[0], columns=columns) - except: - df = pl.DataFrame() - else: - df = pl.DataFrame() - - if compute and len(df): - data = df.clone() - if doclean: - data = clean_df_stats_pl(data, ignorehr=True, - workstrokesonly=workstrokesonly, - for_chart=for_chart) - - - data = remove_nulls_pl(data) - - if not df.is_empty(): - df = df.fill_nan(None).drop_nulls() - - return df def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True): @@ -1603,6 +1519,8 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, data.append(df) data = pl.collect_all(data) + if len(data)==0: + return pl.DataFrame() try: datadf = pl.concat(data).select(columns) @@ -1635,7 +1553,15 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for df in data ] - datadf = pl.concat(data) + try: + datadf = pl.concat(data) + except SchemaError: + data = [ + df.with_columns(cs.integer().cast(pl.Float64)) for df in data + ] + datadf = pl.concat(data) + + exprs = [] diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 0098c007..06fe2637 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1612,26 +1612,26 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, try: datadf = datadf.with_columns(pl.col(xparam).alias("x1")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: datadf = datadf.with_columns(pl.col("distance").alias("x1")) - except KeyError: + except (KeyError, ColumnNotFoundError): try: datadf = datadf.with_columns(pl.col('time').alias("x1")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return ['', '

        No non-zero data in selection

        ', '', ''] try: datadf = datadf.with_columns(pl.col(yparam1).alias("y1")) - except KeyError: + except (KeyError, ColumnNotFoundError): try: datadf = datadf.with_columns(pl.col('pace').alias("y1")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return ['', '

        No non-zero data in selection

        ', '', ''] if yparam2 != 'None': try: datadf = datadf.with_columns(pl.col(yparam2).alias("y2")) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover datadf = datadf.with_columns(pl.col("y1").alias("y2")) else: # pragma: no cover datadf = datadf.with_columns(pl.col("y1").alias("y2")) @@ -2099,14 +2099,11 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', promember=0, workstrokesonly=True, labeldict=None, startenddict={}): - message = '' - errormessage = '' - - columns = [name for name, d in metrics.rowingmetrics] - columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] + columns = [xparam,yparam] + columns_basic = [xparam,yparam] add_columns = [ 'ftime', 'distance', 'fpace', - 'power', 'hr', 'spm', + 'spm', 'time', 'pace', 'workoutstate', 'workoutid' ] @@ -2122,47 +2119,37 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', datadf = pd.DataFrame() if promember: - datadf = dataprep.getsmallrowdata_db(columns, ids=ids, doclean=doclean, - compute=compute, - workstrokesonly=workstrokesonly, for_chart=True) + datadf = dataprep.read_data(columns, ids=ids, doclean=doclean, + compute=compute, + workstrokesonly=workstrokesonly, for_chart=True) else: - datadf = dataprep.getsmallrowdata_db(columns_basic, ids=ids, doclean=doclean, - compute=compute, - workstrokesonly=workstrokesonly, for_chart=True) + datadf = dataprep.read_data(columns_basic, ids=ids, doclean=doclean, + compute=compute, + workstrokesonly=workstrokesonly, for_chart=True) - + + datadf = dataprep.remove_nulls_pl(datadf) # check if dataframe not empty - if datadf.empty: # pragma: no cover + if datadf.is_empty(): # pragma: no cover return ['

        No non-zero data in selection

        ', ''] - datadf['workoutid'] = datadf['workoutid'].astype(int) - datadf.dropna(axis=1, how='all', inplace=True) - datadf.dropna(axis=0, how='all', inplace=True) + datadf = datadf.with_columns(pl.col("workoutid").cast(pl.UInt32).keep_name()) nrworkouts = len(ids) try: - tseconds = datadf.loc[:, 'time'] - except KeyError: # pragma: no cover + tseconds = datadf['time'] + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - tseconds = datadf.loc[:, xparam] + tseconds = datadf[xparam] except: return ['

        A chart data error occurred

        ', ''] - # check if dataframe not empty - if datadf.empty: # pragma: no cover - return ['

        No non-zero data in selection

        ', ''] - - if (xparam == 'time'): - datadf[xparam] = datadf[xparam] - datadf[xparam].iloc[0] + datadf = datadf.with_columns((pl.col(xparam)-datadf[0,xparam]).alias(xparam)) - datadf = datadf.fillna(0) - datadf.replace([np.inf, -np.inf], np.nan, inplace=True) - datadf = datadf.fillna(0) - - data_dict = datadf.to_dict("records") + data_dict = datadf.to_dicts() metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] @@ -2178,8 +2165,8 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', 'workouts': workoutsdict, } - script, div = get_chart("/compare", chart_data) - return script, div, message, errormessage + script, div = get_chart("/compare", chart_data, debug=False) + return script, div def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): @@ -2202,7 +2189,10 @@ def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg= df = dataprep.read_data(columns, ids=ids, workstrokesonly=False, doclean=False) df = dataprep.remove_nulls_pl(df) - df = df.with_columns((pl.col("time").diff().clip(0, 20*1.e3)).alias("deltat")).lazy() + try: + df = df.with_columns((pl.col("time").diff().clip(0, 20*1.e3)).alias("deltat")).lazy() + except ColumnNotFoundError: + pass hrzones = rower.hrzones powerzones = rower.powerzones diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index cfae2db4..617c7c38 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -301,7 +301,7 @@ def mocked_getrowdata_db(*args, **kwargs): return df,row def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover - df = pd.read_csv('rowers/tests/testdata/uhfull.csv') + df = pl.read_csv('rowers/tests/testdata/uhfull.csv') id = kwargs['id'] @@ -315,7 +315,7 @@ def mocked_getsmallrowdata_uh(*args, **kwargs): # pragma: no cover return df def mocked_getsmallrowdata_forfusion(*args, **kwargs): - df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv') + df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv') return df diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index f7101bb7..69d98787 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -169,7 +169,7 @@ class ForcecurveTest(TestCase): pass - @patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db) + @patch('rowers.dataprep.read_data',side_effect = mocked_read_data) def test_forcecurve_plot(self, mocked_getsmallrowdata_db): login = self.c.login(username=self.u.username, password = self.password) self.assertTrue(login) @@ -600,9 +600,9 @@ class History(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_workouts_history(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -916,7 +916,7 @@ class WorkoutStatsTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data(self, mocked_sqlalchemy, @@ -1257,8 +1257,8 @@ class MarkerPerformanceTest(TestCase): self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_uh) - def test_trainingzones_view(self,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data', side_effect=mocked_getsmallrowdata_uh) + def test_trainingzones_view(self,mocked_getsmallrowdata_uh): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py index 6523754b..a606263d 100644 --- a/rowers/tests/test_emails.py +++ b/rowers/tests/test_emails.py @@ -55,8 +55,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi(self,mocked_sqlalchemy,mocked_read_data): form_data = { 'title': 'test', 'workouttype':'rower', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 5bf43e1e916e3a856305c8da0fc7f1e54de12fcf..d8a327745c9963644d6235569cd8ced867417261 100644 GIT binary patch delta 3913 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 delta 3912 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 WrKj;ny7b|l{{ijikJ7{gfB^uPN%If@ diff --git a/rowers/utils.py b/rowers/utils.py index 95e5fb78..7f96a94a 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -7,6 +7,7 @@ import math import numpy as np import pandas as pd import polars as pl +from polars.exceptions import ColumnNotFoundError import colorsys from django.conf import settings import collections diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 86ed09d3..3d0323f6 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -571,7 +571,7 @@ def flexalldata(workouts, options): workstrokesonly = not includereststrokes columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] ids = [int(w.id) for w in workouts] - df = dataprep.getsmallrowdata_pl(columns, ids=ids, + df = dataprep.read_data(columns, ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) @@ -928,9 +928,9 @@ def boxplotdata(workouts, options): ids = [w.id for w in workouts] # prepare data frame - datadf = getsmallrowdata_pl(fieldlist, ids) + datadf = dataprep.read_data(fieldlist, ids) - datadf = dataprep.clean_df_stats_pl(datadf, workstrokesonly=workstrokesonly) + datadf = dataprep.remove_nulls_pl(datadf) try: datadf = datadf.filter( @@ -2361,17 +2361,16 @@ def history_view_data(request, userid=0): ids = [w.id for w in g_workouts] - # columns = ['hr', 'power', 'time'] - columns = [name for name, d in metrics.rowingmetrics]+['workoutstate', 'workoutid'] + columns = ['hr', 'power', 'time', 'workoutstate', 'workoutid'] + + df = dataprep.read_data(columns, ids=ids) + df = dataprep.remove_nulls_pl(df) - df = getsmallrowdata_pl(columns, ids=ids) try: df = df.with_columns(pl.col('time').diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover pass - df = dataprep.clean_df_stats_pl(df, workstrokesonly=True, - ignoreadvanced=True, ignorehr=False) totalmeters, totalhours, totalminutes, totalseconds = get_totals( g_workouts) @@ -2400,7 +2399,8 @@ def history_view_data(request, userid=0): whours=whours, wminutes=wminutes, wseconds=wseconds, ) - ddf = getsmallrowdata_pl(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.remove_nulls_pl(ddf) try: ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover @@ -2421,7 +2421,7 @@ def history_view_data(request, userid=0): ddict['powermean'] = int(wavg(ddf, 'power', 'deltat')) try: ddict['powermax'] = int(ddf['power'].max()) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover ddict['powermax'] = 0 ddict['nrworkouts'] = a_workouts.count() listofdicts.append(ddict) @@ -2436,13 +2436,13 @@ def history_view_data(request, userid=0): try: totalsdict['powermean'] = int(wavg(df, 'power', 'deltat')) totalsdict['powermax'] = int(df['power'].max()) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover totalsdict['powermean'] = 0 totalsdict['powermax'] = 0 try: totalsdict['hrmean'] = int(wavg(df, 'hr', 'deltat')) totalsdict['hrmax'] = int(df['hr'].max()) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover totalsdict['hrmean'] = 0 totalsdict['hrmax'] = 0 @@ -2461,7 +2461,8 @@ def history_view_data(request, userid=0): a_workouts = g_workouts.filter(workouttype=typeselect) meters, hours, minutes, seconds = get_totals(a_workouts) totalseconds = 3600 * hours + 60 * minutes + seconds - ddf = getsmallrowdata_pl(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.remove_nulls_pl(ddf) if ddf.is_empty(): totalscript = "" totaldiv = "No data" diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 991b440f..83e22708 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -16,7 +16,7 @@ from rowers.utils import ( from rowers.celery import result as celery_result from rowers.interactiveplots import * from scipy.interpolate import griddata -from rowers.dataprep import getsmallrowdata_db, getsmallrowdata_pl +from rowers.dataprep import getsmallrowdata_db, read_data from rowers.dataprep import timedeltaconv from scipy.special import lambertw from io import BytesIO From 85bd89d3d2ee66d8642f72ad742d498834f92bbf Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 20 Apr 2024 19:18:56 +0200 Subject: [PATCH 108/121] passing tests --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index d8a327745c9963644d6235569cd8ced867417261..4ed63f161db8901eb27d7a5dd48e39e93f0af9e5 100644 GIT binary patch delta 35 rcmZ1=zd)W%zMF&N>$i<;tNGaedb%rq;GVpl?;(eGLC=dL4U7x`^QjH~ delta 35 rcmZ1=zd)W%zMF&N{k4s3tNGaU`wwx;TTkB3_mIO&`FY%t21W(|;0O$t From 409f4aff6333c001efeca3fb13c28ab8fbb3ffe0 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 21 Apr 2024 10:56:37 +0200 Subject: [PATCH 109/121] alerts, analysis, api tested --- rowers/alerts.py | 7 +- rowers/dataroutines.py | 10 +-- rowers/tasks.py | 3 +- rowers/tests/test_analysis.py | 80 +++++++++++----------- rowers/tests/test_api.py | 9 +-- rowers/views/analysisviews.py | 4 +- rowers/views/apiviews.py | 121 +++++++++++++++++----------------- rowers/views/statements.py | 7 +- 8 files changed, 124 insertions(+), 117 deletions(-) diff --git a/rowers/alerts.py b/rowers/alerts.py index ef74ddf0..bf3587bd 100644 --- a/rowers/alerts.py +++ b/rowers/alerts.py @@ -1,6 +1,6 @@ from rowers.models import Alert, Condition, User, Rower, Workout from rowers.teams import coach_getcoachees -from rowers.dataprep import getsmallrowdata_db, getrowdata_db +from rowers.dataprep import getrowdata_db, read_data, remove_nulls_pl import datetime import numpy as np import math @@ -101,8 +101,9 @@ def alert_get_stats(alert, nperiod=0): # pragma: no cover ids = [w.id for w in workouts] try: - df = getsmallrowdata_db(columns, ids=ids, doclean=True, - workstrokesonly=workstrokesonly) + df = read_data(columns, ids=ids, doclean=True, + workstrokesonly=workstrokesonly) + df = remove_nulls_pl(df) except: return { 'workouts': workouts.count(), diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 90f50aeb..8fe36699 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1417,10 +1417,10 @@ def update_strokedata(id, df, debug=False): def testdata(time, distance, pace, spm): # pragma: no cover - t1 = np.issubdtype(time, np.number) - t2 = np.issubdtype(distance, np.number) - t3 = np.issubdtype(pace, np.number) - t4 = np.issubdtype(spm, np.number) + t1 = time.dtype in pl.NUMERIC_DTYPES + t2 = distance.dtype in pl.NUMERIC_DTYPES + t3 = pace.dtype in pl.NUMERIC_DTYPES + t4 = spm.dtype in pl.NUMERIC_DTYPES return t1 and t2 and t3 and t4 @@ -1630,7 +1630,7 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, return datadf -def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, +def getsmallrowdata_pd(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, debug=False, for_chart=False): # prepmultipledata(ids) diff --git a/rowers/tasks.py b/rowers/tasks.py index f2a7355f..104045e5 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -130,7 +130,8 @@ from rowers import mytypes from rowers.dataroutines import ( - getsmallrowdata_db, updatecpdata_sql, update_c2id_sql, + getsmallrowdata_pd, updatecpdata_sql, update_c2id_sql, + read_data, #update_workout_field_sql, update_agegroup_db, update_strokedata, add_c2_stroke_data_db, totaltime_sec_to_string, diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index 69d98787..4bd98e36 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -43,10 +43,10 @@ class ListWorkoutTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_list_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -170,7 +170,7 @@ class ForcecurveTest(TestCase): @patch('rowers.dataprep.read_data',side_effect = mocked_read_data) - def test_forcecurve_plot(self, mocked_getsmallrowdata_db): + def test_forcecurve_plot(self, mocked_read_data): login = self.c.login(username=self.u.username, password = self.password) self.assertTrue(login) @@ -296,9 +296,9 @@ class WorkoutCompareTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_compare(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -309,9 +309,9 @@ class WorkoutCompareTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_compare_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -398,9 +398,9 @@ class WorkoutBoxPlotTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_boxplot(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -411,9 +411,9 @@ class WorkoutBoxPlotTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_boxplot_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -499,9 +499,9 @@ class WorkoutHistoTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_histo(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -512,9 +512,9 @@ class WorkoutHistoTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_histo_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -618,9 +618,9 @@ class History(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_history_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -713,9 +713,9 @@ class GoldMedalScores(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_goldmedalscores(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): ws = Workout.objects.filter(rankingpiece=True) self.assertEqual(ws.count(),2) @@ -729,9 +729,9 @@ class GoldMedalScores(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_goldmedalscores_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -795,9 +795,9 @@ class WorkoutFlexallTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_flexall(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -808,9 +808,9 @@ class WorkoutFlexallTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_flexall_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -903,9 +903,9 @@ class WorkoutStatsTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_stats(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -920,7 +920,7 @@ class WorkoutStatsTestNew(TestCase): @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -979,11 +979,11 @@ class WorkoutStatsTestNew(TestCase): script, div = comparisondata(workouts,options) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -1042,11 +1042,11 @@ class WorkoutStatsTestNew(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -1120,9 +1120,9 @@ class WorkoutStatsTestNew(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_stats_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1241,9 +1241,9 @@ class MarkerPerformanceTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_create_marker_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1326,9 +1326,9 @@ class MarkerPerformanceTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_performancemanager_view(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1413,9 +1413,9 @@ class AlertTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.alerts.getsmallrowdata_db') + @patch('rowers.alerts.read_data') def test_alerts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 69279f53..e17c3532 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -67,10 +67,11 @@ class OwnApi(TestCase): self.assertEqual(response.status_code,200) # response must be json + strokedata = json.loads(response.content) - df = pd.DataFrame(strokedata) + df = pl.from_dict(strokedata) - self.assertFalse(df.empty) + self.assertFalse(df.is_empty()) form_data = { "distance": [23, 46, 48], @@ -124,8 +125,8 @@ class OwnApi(TestCase): # response must be json strokedata = json.loads(response.content) - df = pd.DataFrame(strokedata) - self.assertFalse(df.empty) + df = pl.from_dicts(strokedata['data']) + self.assertFalse(df.is_empty()) form_data = { diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 3d0323f6..2b27ed6e 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -612,7 +612,7 @@ def histodata(workouts, options): if savedata: # pragma: no cover workstrokesonly = not includereststrokes ids = [int(w.id) for w in workouts] - df = dataprep.getsmallrowdata_db([plotfield], ids=ids, + df = dataprep.read_data([plotfield], ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) @@ -885,7 +885,7 @@ def comparisondata(workouts, options): 'time', 'pace', 'workoutstate', 'workoutid'] - df = dataprep.getsmallrowdata_db(columns, ids=ids, + df = dataprep.read_data(columns, ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 1f2f87bb..37bd0733 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -68,102 +68,102 @@ def strokedataform(request, id=0): def api_get_dataframe(startdatetime, df): try: time = df['time']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: time = df['t']/10. - except KeyError: - return 400, "Missing time", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing time", pl.DataFrame() try: spm = df['spm'] - except KeyError: # pragma: no cover - return 400, "Missing spm", pd.DataFrame() + except (KeyError, ColumnNotFoundError): # pragma: no cover + return 400, "Missing spm", pl.DataFrame() try: distance = df['distance'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: distance = df['d']/10. - except KeyError: - return 400, "Missing distance", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing distance", pl.DataFrame() try: pace = df['pace']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: pace = df['p']/10. - except KeyError: - return 400, "Missing pace", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing pace", pl.DataFrame() try: power = df['power'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover power = 0*time try: drivelength = df['drivelength'] - except KeyError: + except (KeyError, ColumnNotFoundError): drivelength = 0*time try: dragfactor = df['dragfactor'] - except KeyError: + except (KeyError, ColumnNotFoundError): dragfactor = 0*time try: drivetime = df['drivetime'] - except KeyError: + except (KeyError, ColumnNotFoundError): drivetime = 0*time try: strokerecoverytime = df['strokerecoverytime'] - except KeyError: + except (KeyError, ColumnNotFoundError): strokerecoverytime = 0*time try: averagedriveforce = df['averagedriveforce'] - except KeyError: + except (KeyError, ColumnNotFoundError): averagedriveforce = 0*time try: peakdriveforce = df['peakdriveforce'] - except KeyError: + except (KeyError, ColumnNotFoundError): peakdriveforce = 0*time try: wash = df['wash'] - except KeyError: + except (KeyError, ColumnNotFoundError): wash = 0*time try: catch = df['catch'] - except KeyError: + except (KeyError, ColumnNotFoundError): catch = 0*time try: finish = df['finish'] - except KeyError: + except (KeyError, ColumnNotFoundError): finish = 0*time try: peakforceangle = df['peakforceangle'] - except KeyError: + except (KeyError, ColumnNotFoundError): peakforceangle = 0*time try: driveenergy = df['driveenergy'] - except KeyError: + except (KeyError, ColumnNotFoundError): driveenergy = 60.*power/spm try: slip = df['slip'] - except KeyError: + except (KeyError, ColumnNotFoundError): slip = 0*time try: lapidx = df['lapidx'] - except KeyError: + except (KeyError, ColumnNotFoundError): lapidx = 0*time try: hr = df['hr'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover hr = 0*df['time'] try: latitude = df['latitude'] - except KeyError: + except (KeyError, ColumnNotFoundError): latitude = 0*df['time'] try: longitude = df['longitude'] - except KeyError: + except (KeyError, ColumnNotFoundError): longitude = 0*df['time'] starttime = totimestamp(startdatetime)+time[0] @@ -171,7 +171,7 @@ def api_get_dataframe(startdatetime, df): dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)") - data = pd.DataFrame({'TimeStamp (sec)': unixtime, + data = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': distance, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -522,7 +522,7 @@ def strokedatajson_v3(request): title = request.data.get('name','') try: elapsedTime = request.data['elapsedTime'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: duration = request.data['duration'] try: @@ -534,7 +534,7 @@ def strokedatajson_v3(request): return HttpResponse("Missing Elapsed Time", status=400) try: totalDistance = request.data['distance'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("Missing Total Distance", status=400) timeZone = request.data.get('timezone','UTC') workouttype = request.data.get('workouttype','rower') @@ -552,23 +552,21 @@ def strokedatajson_v3(request): dologging('apilog.log',totalDistance) dologging('apilog.log',elapsedTime) - df = pd.DataFrame() + df = pl.DataFrame() try: strokes = request.data['strokes'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("No Stroke Data in JSON", status=400) try: - df = pd.DataFrame(strokes['data']) - except KeyError: # pragma: no cover + df = pl.DataFrame(strokes['data']) + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - df = pd.DataFrame(request.data['strokedata']) + df = pl.DataFrame(request.data['strokedata']) except: return HttpResponse("No JSON Object could be decoded", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) - + df = df.sort("time") status, comment, data = api_get_dataframe(startdatetime, df) if status != 200: # pragma: no cover @@ -576,7 +574,8 @@ def strokedatajson_v3(request): csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16]) - _ = data.to_csv(csvfilename, index_label='index', compression='gzip') + with gzip.open(csvfilename, 'w') as f: + _ = data.write_csv(f) duration = datetime.time(0,0,1) w = Workout( @@ -667,10 +666,11 @@ def strokedatajson_v2(request, id): if request.method == 'GET': columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + datadf = dataprep.read_data(columns, ids=[id]) + datadf = dataprep.remove_nulls_pl(datadf) dologging('apilog.log',request.user.username+"(strokedatajson_v2 GET)") - data = datadf.to_json(orient='records') + data = datadf.write_json(row_oriented=True) data2 = json.loads(data) data2 = {"data": data2} @@ -681,28 +681,27 @@ def strokedatajson_v2(request, id): try: for d in request.data['data']: dologging('apilog.log',json.dumps(d)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: for d in request.data['strokedata']: dologging('apilog.log',json.dumps(d)) - except KeyError: + except (KeyError, ColumnNotFoundError): dologging('apilog.log','No data in request.data') checkdata, r = dataprep.getrowdata_db(id=row.id) if not checkdata.empty: # pragma: no cover return HttpResponse("Duplicate Error", status=409) - df = pd.DataFrame() + df = pl.DataFrame() try: - df = pd.DataFrame(request.data['data']) - except KeyError: # pragma: no cover + df = pl.DataFrame(request.data['data']) + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - df = pd.DataFrame(request.data['strokedata']) + df = pl.DataFrame(request.data['strokedata']) except: return HttpResponse("No JSON object could be decoded", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) + df = df.sort("time") status, comment, data = api_get_dataframe(row.startdatetime, df) if status != 200: # pragma: no cover @@ -724,7 +723,8 @@ def strokedatajson_v2(request, id): row.duplicate = True row.save() - _ = data.to_csv(csvfilename+'.gz', index_label='index', compression='gzip') + with gzip.open(csvfilename+'.gz', 'w') as f: + _ = data.write_csv(f) row.csvfilename = csvfilename row.save() @@ -796,7 +796,11 @@ def strokedatajson(request, id=0): if request.method == 'GET': # currently only returns a subset. columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + + datadf = dataprep.read_data(columns, ids=[id]) + datadf = dataprep.remove_nulls_pl(datadf) + datadf = datadf.to_pandas() + dologging("apilog.log",request.user.username+"(strokedatajson GET) ") return JSONResponse(datadf) @@ -817,14 +821,13 @@ def strokedatajson(request, id=0): return HttpResponse("No JSON object could be decoded", status=400) try: - df = pd.DataFrame(strokedata) + df = pl.DataFrame(strokedata) except ValueError: # pragma: no cover return HttpResponse("Arrays must all be same length", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) + df = df.sort("time") try: time = df['time']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("There must be time values", status=400) aantal = len(time) pace = df['pace']/1.e3 @@ -863,7 +866,7 @@ def strokedatajson(request, id=0): dologging("apilog.log",request.user.username+"(POST)") - data = pd.DataFrame({'TimeStamp (sec)': unixtime, + data = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': distance, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -890,8 +893,8 @@ def strokedatajson(request, id=0): timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S") csvfilename = 'media/Import_'+timestr+'.csv' - res = data.to_csv(csvfilename+'.gz', index_label='index', - compression='gzip') + with gzip.open(csvfilename+'.gz','w') as f: + res = data.write_csv(f) row.csvfilename = csvfilename row.save() diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 83e22708..ac88517b 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -16,7 +16,7 @@ from rowers.utils import ( from rowers.celery import result as celery_result from rowers.interactiveplots import * from scipy.interpolate import griddata -from rowers.dataprep import getsmallrowdata_db, read_data +from rowers.dataprep import getsmallrowdata_pd, read_data from rowers.dataprep import timedeltaconv from scipy.special import lambertw from io import BytesIO @@ -35,6 +35,7 @@ import threading import redis import colorsys import re +import gzip import zipfile import bleach import arrow @@ -1345,9 +1346,9 @@ def trydf(df, aantal, column): # pragma: no cover s = df[column] if len(s) != aantal: return np.zeros(aantal) - if not np.issubdtype(s, np.number): + if not s.dtype in pl.NUMERIC_DTYPES: return np.zeros(aantal) - except KeyError: + except (KeyError, ColumnNotFoundError): s = np.zeros(aantal) return s From 64b4d35be77d10d6b0541f4eb233f393e35f23aa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 21 Apr 2024 11:04:06 +0200 Subject: [PATCH 110/121] tasks --- rowers/tasks.py | 32 ++++++++++++++++---------------- rowers/tests/mocks.py | 4 ++-- rowers/tests/test_async_tasks.py | 4 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/rowers/tasks.py b/rowers/tasks.py index 104045e5..8c46ca9f 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -2553,9 +2553,10 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue, totaltime = totaltime + rowdata.df.loc[0, ' ElapsedTime (sec)'] except KeyError: # pragma: no cover pass - df = getsmallrowdata_db( + df = getsmallrowdata_pd( ['power', 'workoutid', 'time'], ids=[workoutid], debug=debug) + thesecs = totaltime maxt = 1.05 * thesecs logarr = datautils.getlogarr(maxt) @@ -3171,23 +3172,22 @@ graphql_url = "https://rp3rowing-app.com/graphql" @app.task def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs): - df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids) + df = read_data(['time', 'driveenergy'], ids=ids) try: - mask = df['driveenergy'] > 100 - except (KeyError, TypeError): # pragma: no cover - return 0 - try: - wps_median = int(df.loc[mask, 'driveenergy'].median()) + wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median()) + rower = Rower.objects.get(id=rid) + if mode == 'water': + rower.median_wps = wps_median + else: # pragma: no cover + rower.median_wps_erg = wps_median + + rower.save() except ValueError: # pragma: no cover - return 0 - - rower = Rower.objects.get(id=rid) - if mode == 'water': - rower.median_wps = wps_median - else: - rower.median_wps_erg = wps_median - - rower.save() + wps_median = 0 + except OverflowError: + wps_median = 0 + except ColumnNotFoundError: + wps_median = 0 return wps_median diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 617c7c38..ed73b421 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -357,8 +357,8 @@ def mocked_getsmallrowdata_db_water(*args, **kwargs): # pragma: no cover return df -def mocked_getsmallrowdata_db_wps(*args, **kwargs): # pragma: no cover - df = pd.read_csv('rowers/tests/testdata/driveenergies.csv') +def mocked_read_data_wps(*args, **kwargs): # pragma: no cover + df = pl.read_csv('rowers/tests/testdata/driveenergies.csv') return df diff --git a/rowers/tests/test_async_tasks.py b/rowers/tests/test_async_tasks.py index 5aab5a37..f7af8923 100644 --- a/rowers/tests/test_async_tasks.py +++ b/rowers/tests/test_async_tasks.py @@ -486,8 +486,8 @@ class AsyncTaskTests(TestCase): - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps): + @patch('rowers.dataprep.read_data') + def test_handle_update_wps(self,mocked_read_data_wps): ids = [1,2,3] result = tasks.handle_update_wps(self.r.id,['water'],ids,mode='water') From cbef135d861af6d6bff87f43dd1bd76ea19bfc3a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 21 Apr 2024 12:05:39 +0200 Subject: [PATCH 111/121] passing tests on workouts --- rowers/dataroutines.py | 2 +- rowers/tests/test_aworkouts.py | 66 +++++++++++++++++----------------- rowers/views/workoutviews.py | 10 ++---- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 8fe36699..467fc5ce 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1943,7 +1943,7 @@ def datafusion(id1, id2, columns, offset): df1[' latitude'] = latitude df1[' longitude'] = longitude - df2 = getsmallrowdata_db(['time'] + columns, ids=[id2], doclean=False) + df2 = getsmallrowdata_pd(['time'] + columns, ids=[id2], doclean=False) forceunit = 'N' diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 5bb15c68..79c139b1 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -62,10 +62,10 @@ class ListWorkoutTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_list_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -87,10 +87,10 @@ class ListWorkoutTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_bulk_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -234,9 +234,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.middleware.myqueue') - def test_forcecurve(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_forcecurve(self, mocked_sqlalchemy, mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -255,9 +255,9 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.middleware.myqueue') - def test_resample(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_resample(self, mocked_sqlalchemy, mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -283,9 +283,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @pytest.mark.django_db(transaction=True) - def test_joins(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + def test_joins(self, mocked_sqlalchemy, mocked_read_data): with transaction.atomic(): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -350,10 +350,10 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.weather.requests.get', side_effect=mocked_requests) def test_waterworkout_view(self, - mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests): + mocked_sqlalchemy, mocked_read_data, mocked_requests): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -406,8 +406,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_smoothen(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_smoothen(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -447,8 +447,8 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_commentview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -473,8 +473,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_mapview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_mapview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -485,9 +485,9 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def notworking_test_workout_image(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -513,10 +513,10 @@ class WorkoutViewTest(TestCase): status_code=302,target_status_code=200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) @pytest.mark.django_db(transaction=True) - def test_workout_split(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_split(self, mocked_sqlalchemy, mocked_read_data, mocked_getrowdata_db): with transaction.atomic(): login = self.c.login(username=self.u.username, password=self.password) @@ -541,9 +541,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_forfusion) + @patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_forfusion) def test_workout_fusion(self, mocked_sqlalchemy, mocked_getrowdata_db, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -572,9 +572,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_video_view(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -634,9 +634,9 @@ class WorkoutViewTest(TestCase): self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -698,7 +698,7 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_video_selectworkout(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_video_selectworkout(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -729,8 +729,8 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_editsummaryview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_editsummaryview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -795,8 +795,8 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_workout_delete(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_workout_delete(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index c64c1bac..6734e606 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2177,10 +2177,6 @@ def workouts_view(request, message='', successmessage='', for w in workoutsnohr: # pragma: no cover _ = dataprep.workout_trimp(w) - # ids = [w.id for w in workouts] - # df = dataprep.getsmallrowdata_db(['time','power'],ids=ids) - # polarization = dataprep.polarization_index(df,r) - if query: # pragma: no cover query_list = query.split() workouts = workouts.filter( @@ -3196,7 +3192,7 @@ def workout_erase_column_view(request, id=0, column=''): return HttpResponseRedirect(url) try: - data = dataprep.getsmallrowdata_db([column], ids=[w.id]) + data = dataprep.read_data([column], ids=[w.id]) except: # pragma: no cover messages.error(request, 'Invalid column') url = reverse('workout_data_view', kwargs={ @@ -4326,7 +4322,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""): row = get_workoutuser(id, request) if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: # pragma: no cover - data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) + data = read_data(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') @@ -4339,7 +4335,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""): pass if request.user.rower.rowerplan == 'basic' and 'nklinklogbook' in row.workoutsource: # pragma: no cover - data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) + data = read_data(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') From f6c535d2d627d3d63f0801a742d1bac334706854 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 29 Apr 2024 20:06:41 +0200 Subject: [PATCH 112/121] going through tests --- rowers/courses.py | 57 ---------- rowers/dataprep.py | 39 ------- rowers/dataroutines.py | 6 +- rowers/interactiveplots.py | 16 +-- rowers/plannedsessions.py | 2 - rowers/tests/test_api.py | 37 ++++--- rowers/tests/test_emails.py | 12 +-- rowers/tests/test_flexchart.py | 8 +- rowers/tests/test_imports.py | 24 ++--- rowers/tests/test_interactivecharts.py | 32 +++--- rowers/tests/test_permissions.py | 16 +-- rowers/tests/test_permissions2.py | 16 +-- rowers/tests/test_urls.py | 6 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4003 bytes rowers/views/analysisviews.py | 144 ------------------------- 15 files changed, 89 insertions(+), 326 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index d3eab241..060d15ae 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -1,5 +1,4 @@ from rowers.courseutils import coursetime_paths, coursetime_first, time_in_path -import pandas as pd from rowers.models import ( Rower, Workout, GeoPoint, GeoPolygon, GeoCourse, @@ -420,62 +419,6 @@ def createcourse( return c -def get_time_course(ws, course): # pragma: no cover - coursetimeseconds = 0.0 - coursecompleted = False - - w = ws[0] - columns = ['time', ' latitude', ' longitude', 'cum_dist'] - rowdata = dataprep.getsmallrowdata_db( - columns, - ids=[w.id], - doclean=False, - workstrokesonly=False - ) - - rowdata.rename(columns={ - ' latitude': 'latitude', - ' longitude': 'longitude', - }, inplace=True) - - rowdata['time'] = rowdata['time']/1000. - - rowdata.fillna(method='backfill', inplace=True) - - rowdata['time'] = rowdata['time']-rowdata.ix[0, 'time'] - # we may want to expand the time (interpolate) - rowdata['dt'] = rowdata['time'].apply( - lambda x: timedelta(seconds=x) - ) - rowdata = rowdata.resample('100ms', on='dt').mean() - rowdata = rowdata.interpolate() - - # create path - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - paths = [] - for polygon in polygons: - path = polygon_to_path(polygon) - paths.append(path) - - ( - coursetimeseconds, - coursemeters, - coursecompleted, - - ) = coursetime_paths(rowdata, paths) - ( - coursetimefirst, - coursemetersfirst, - firstcompleted - ) = coursetime_first( - rowdata, paths) - - coursetimeseconds = coursetimeseconds-coursetimefirst - coursemeters = coursemeters-coursemetersfirst - - return coursetimeseconds, coursemeters, coursecompleted - def replacecourse(course1, course2): ps = PlannedSession.objects.filter(course=course1) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 275f8684..4c8e94a9 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -758,45 +758,6 @@ def fetchcp_new(rower, workouts): return df['delta'], df['cp'], 0, df['workout'], df['url'] -def fetchcp(rower, theworkouts, table='cpdata'): # pragma: no cover - # get all power data from database (plus workoutid) - theids = [int(w.id) for w in theworkouts] - columns = ['power', 'workoutid', 'time'] - df = getsmallrowdata_db(columns, ids=theids) - df.dropna(inplace=True, axis=0) - if df.empty: - avgpower2 = {} - for id in theids: - avgpower2[id] = 0 - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - try: - dfgrouped = df.groupby(['workoutid']) - except KeyError: - avgpower2 = {} - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - try: - avgpower2 = dict(dfgrouped.mean()['power'].astype(int)) - except KeyError: - avgpower2 = {} - for id in theids: - avgpower2[id] = 0 - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - cpdf = getcpdata_sql(rower.id, table=table) - - if not cpdf.empty: - return cpdf['delta'], cpdf['cp'], avgpower2 - else: - _ = myqueue(queuelow, - handle_updatecp, - rower.id, - theids, - table=table) - - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 def update_rolling_cp(r, types, mode='water', dosend=False): diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 467fc5ce..083dce8d 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -205,7 +205,7 @@ def get_video_data(w, groups=['basic'], mode='water'): columns += [name for name, d in rowingmetrics if d['group'] in groups and d['mode'] in modes] columns = list(set(columns)) - df = getsmallrowdata_db(columns, ids=[w.id], + df = getsmallrowdata_pd(columns, ids=[w.id], workstrokesonly=False, doclean=False, compute=False) df['time'] = (df['time']-df['time'].min())/1000. @@ -1772,7 +1772,7 @@ def read_cols_pl(ids, columns): if len(ids) == 0: return pl.DataFrame() - df = getsmallrowdata_pl(columns, ids=ids, doclean=False, compute=False) + df = read_data(columns, ids=ids, doclean=False, compute=False) if 'peakforce' in columns: funits = ((w.id, w.forceunit) @@ -1980,7 +1980,7 @@ def datafusion(id1, id2, columns, offset): def fix_newtons(id=0, limit=3000): # pragma: no cover # rowdata,row = getrowdata_db(id=id,doclean=False,convertnewtons=False) - rowdata = getsmallrowdata_db(['peakforce'], ids=[id], doclean=False) + rowdata = read_data(['peakforce'], ids=[id], doclean=False) try: peakforce = rowdata['peakforce'] if peakforce.mean() > limit: diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 06fe2637..597b5158 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1973,7 +1973,7 @@ def thumbnails_set(r, id, favorites): columns += [f.yparam2 for f in favorites] columns += ['time'] - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True) + rowdata = dataprep.getsmallrowdata_pd(columns, ids=[id], doclean=True) try: rowdata.dropna(axis=1, how='all', inplace=True) @@ -1987,7 +1987,7 @@ def thumbnails_set(r, id, favorites): if rowdata.empty: try: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=False, + rowdata = dataprep.getsmallrowdata_pd(columns, ids=[id], doclean=False, workstrokesonly=False) except: # pragma: no cover return [ @@ -1996,12 +1996,12 @@ def thumbnails_set(r, id, favorites): 'notes': "" }] - if rowdata.empty: - return [ - {'script': "", - 'div': "", - 'notes': "" - }] + if rowdata.empty: + return [ + {'script': "", + 'div': "", + 'notes': "" + }] lengte = len(rowdata) maxlength = 50 diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index dc2c4a08..5d49e59e 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -18,13 +18,11 @@ import rowers.dataprep as dataprep import numpy as np import rowers.metrics as metrics import rowers.mytypes as mytypes -from rowers.courses import get_time_course from rowers.utils import to_pace from rowers.opaque import encoder from rowingdata import rower as rrower from rowingdata import rowingdata as rrdata import arrow -import pandas as pd import polars as pl import json diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index e17c3532..3aee087e 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -58,20 +58,22 @@ class OwnApi(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - url = reverse('strokedatajson',kwargs={'id':w.id}) + with patch('rowers.dataprep.read_data') as mock_read_data: + mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + url = reverse('strokedatajson',kwargs={'id':w.id}) - request = self.factory.get(url) - request.user = self.u - force_authenticate(request, user=self.u) - response = strokedatajson(request,id=w.id) - self.assertEqual(response.status_code,200) + request = self.factory.get(url) + request.user = self.u + force_authenticate(request, user=self.u) + response = strokedatajson(request,id=w.id) + self.assertEqual(response.status_code,200) - # response must be json - - strokedata = json.loads(response.content) - df = pl.from_dict(strokedata) + # response must be json + + strokedata = json.loads(response.content) + df = pl.from_dict(strokedata) - self.assertFalse(df.is_empty()) + self.assertFalse(df.is_empty()) form_data = { "distance": [23, 46, 48], @@ -120,12 +122,15 @@ class OwnApi(TestCase): request = self.factory.get(url) request.user = self.u force_authenticate(request, user=self.u) - response = strokedatajson_v2(request,id=w.id) - self.assertEqual(response.status_code,200) + with patch('rowers.dataprep.read_data') as mock_read_data: + mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + response = strokedatajson_v2(request,id=w.id) + self.assertEqual(response.status_code,200) + + # response must be json + strokedata = json.loads(response.content) + df = pl.from_dicts(strokedata['data']) - # response must be json - strokedata = json.loads(response.content) - df = pl.from_dicts(strokedata['data']) self.assertFalse(df.is_empty()) diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py index a606263d..ae26ef5e 100644 --- a/rowers/tests/test_emails.py +++ b/rowers/tests/test_emails.py @@ -108,8 +108,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi2(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi2(self,mocked_sqlalchemy,mocked_read_data): form_data = {"secret": settings.UPLOAD_SERVICE_SECRET, "user": 1, "file": 'media/mailbox_attachments/colin3.csv', @@ -140,8 +140,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi3(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi3(self,mocked_sqlalchemy,mocked_read_data): with transaction.atomic(): form_data = { 'title': 'test', @@ -167,8 +167,8 @@ class EmailUpload(TestCase): self.assertEqual(w.notes,'aap noot mies') @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_read_data): form_data = { 'title': 'test', 'workouttype':'rower', diff --git a/rowers/tests/test_flexchart.py b/rowers/tests/test_flexchart.py index d66d20ea..455eb1d8 100644 --- a/rowers/tests/test_flexchart.py +++ b/rowers/tests/test_flexchart.py @@ -103,8 +103,8 @@ class WorkoutViewTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_flexchart_water(self, mocked_sqlalechemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_flexchart_water(self, mocked_sqlalechemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -151,8 +151,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_flexchart_erg(self, mocked_sqlalechemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_flexchart_erg(self, mocked_sqlalechemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index d5e9470d..78818eb5 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -720,8 +720,8 @@ class NKObjects(DjangoTestCase): ) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_nk_intervals(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_nk_intervals(self,mocked_sqlalchemy,mocked_read_data): with open('rowers/tests/testdata/carlos_workout.json','r') as f: workoutdata = json.load(f) with open('rowers/tests/testdata/carlos_strokes.json','r') as f: @@ -817,10 +817,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): integration = NKIntegration(self.u) result = integration.token_refresh() @@ -854,10 +854,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import_task(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): alldata = { 469: {'elapsedTime': 3901900, 'totalDistanceImp': 0, @@ -904,10 +904,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import_impeller(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): integration = NKIntegration(self.u) result = integration.token_refresh() @@ -1083,9 +1083,9 @@ class RP3Objects(DjangoTestCase): @patch('rowers.integrations.rp3.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.rp3.requests.post', side_effect=mocked_requests) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_rp3_import(self, mock_get, mockpost, - mocked_getsmallrowdata_db): + mocked_read_data): response = self.c.get('/rowers/workout/rp3import/591621',follow=True) @@ -1268,9 +1268,9 @@ class StravaObjects(DjangoTestCase): @patch('rowers.utils.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests) - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_strava_import(self, mock_get, mock_post, - mocked_getsmallrowdata_db): + mocked_read_data): response = self.c.get('/rowers/workout/stravaimport/12',follow=True) expected_url = reverse('workout_import_view',kwargs={'source':'strava'}) diff --git a/rowers/tests/test_interactivecharts.py b/rowers/tests/test_interactivecharts.py index 2d217f0b..c46855f8 100644 --- a/rowers/tests/test_interactivecharts.py +++ b/rowers/tests/test_interactivecharts.py @@ -51,16 +51,16 @@ class InteractiveChartTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart1(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_chart(self.wote.id) @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart2(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_chart(self.wote.id,promember=1) @@ -80,52 +80,52 @@ class InteractiveChartTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart7(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r,promember=0, xparam='time', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart8(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=0,xparam='distance', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart9(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='time', yparam1='pace',yparam2='hr',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart10(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='distance', yparam1='pace',yparam2='hr',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart11(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='time', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart12(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1, xparam='distance', diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py index 2f527fda..4ee00a3b 100644 --- a/rowers/tests/test_permissions.py +++ b/rowers/tests/test_permissions.py @@ -911,8 +911,8 @@ class PermissionsViewTests(TestCase): ## Coach can upload on behalf of athlete - if team allows @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rbasic.team.add(self.teamcoach) self.rbasic.coachinggroups.add(self.coachinggroup) @@ -957,8 +957,8 @@ class PermissionsViewTests(TestCase): ## Coach can upload on behalf of athlete - if team allows @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_read_data): self.rbasic.team.add(self.teamcoach) login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) @@ -1168,8 +1168,8 @@ class PermissionsViewTests(TestCase): ## Self Coach cannot upload on behalf of athlete @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rpro.team.add(self.teamplan) login = self.c.login(username=self.uplan2.username, password=self.uplan2password) @@ -1291,8 +1291,8 @@ class PermissionsViewTests(TestCase): ## Self Coach cannot upload on behalf of athlete @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rpro.team.add(self.teamplan) login = self.c.login(username=self.uplan2.username, password=self.uplan2password) diff --git a/rowers/tests/test_permissions2.py b/rowers/tests/test_permissions2.py index 52d495aa..074f4e7f 100644 --- a/rowers/tests/test_permissions2.py +++ b/rowers/tests/test_permissions2.py @@ -218,7 +218,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -228,7 +228,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -253,7 +253,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -263,7 +263,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -335,7 +335,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -345,7 +345,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -418,7 +418,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -428,7 +428,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index 5d19c820..4f2d6d9c 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -193,7 +193,7 @@ class URLTests(TestCase): @parameterized.expand(lijst) @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -201,7 +201,7 @@ class URLTests(TestCase): def test_url_generator(self,url,expected, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -212,7 +212,7 @@ class URLTests(TestCase): self.assertTrue(login) response = self.c.get(url,follow=True) if response.status_code != expected: - print(url ) + print(url) print(response.status_code) self.assertEqual(response.status_code, diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 4ed63f161db8901eb27d7a5dd48e39e93f0af9e5..fc88d4937d88aac8c7941b1fc71f93d7b02ffc45 100644 GIT binary patch literal 4003 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 0] - powerdf.dropna(axis=0, inplace=True) - powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True) - powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True) - powerdf.to_csv(csvfilename) - - _ = myqueue(queuehigh, - handle_sendemailfile, - 'Sander', - 'Roosendaal', - 'roosendaalsander@gmail.com', - csvfilename, - delete=True) - - successmessage = "The CSV file was sent to the site admin per email" - messages.info(request, successmessage) - response = HttpResponseRedirect('/rowers/list-workouts/') - - return response - - -@login_required() -def otwcp_toadmin_view(request, theuser=0, - startdate=timezone.now() - datetime.timedelta(days=365), - enddate=timezone.now(), - startdatestring="", - enddatestring="", - ): # pragma: no cover - - if startdatestring != "": - try: - startdate = iso8601.parse_date(startdatestring) - except ParseError: - pass - - if enddatestring != "": - try: - enddate = iso8601.parse_date(enddatestring) - except ParseError: - pass - - if theuser == 0: - theuser = request.user.id - - u = User.objects.get(id=theuser) - r = Rower.objects.get(user=u) - - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - - theworkouts = Workout.objects.filter( - user=r, rankingpiece=True, - workouttype='water', - startdatetime__gte=startdate, - startdatetime__lte=enddate - ).order_by("-startdatetime") - - delta, cpvalue, avgpower = dataprep.fetchcp( - r, theworkouts, table='cpdata' - ) - - powerdf = pd.DataFrame({ - 'Delta': delta, - 'CP': cpvalue, - }) - - csvfilename = 'CP_data_user_{id}.csv'.format( - id=theuser - ) - - powerdf = powerdf[powerdf['CP'] > 0] - powerdf.dropna(axis=0, inplace=True) - powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True) - powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True) - powerdf.to_csv(csvfilename) - - _ = myqueue(queuehigh, - handle_sendemailfile, - 'Sander', - 'Roosendaal', - 'roosendaalsander@gmail.com', - csvfilename, - delete=True) - - successmessage = "The CSV file was sent to the site admin per email" - messages.info(request, successmessage) - response = HttpResponseRedirect('/rowers/list-workouts/') - - return response - - def agegrouprecordview(request, sex='male', weightcategory='hwt', distance=2000, duration=None): From e83d0a26f6a80621ed197bef221c0687b81e8dda Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 29 Apr 2024 21:41:34 +0200 Subject: [PATCH 113/121] passing tests ... --- rowers/datautils.py | 43 +++++++++----------------- rowers/plannedsessions.py | 13 +++++--- rowers/tests/mocks.py | 6 ++++ rowers/tests/test_unit_tests.py | 4 +-- rowers/tests/test_units.py | 17 +++++----- rowers/tests/test_uploads.py | 8 ++--- rowers/tests/testdata/testdata.tcx.gz | Bin 4003 -> 3999 bytes 7 files changed, 44 insertions(+), 47 deletions(-) diff --git a/rowers/datautils.py b/rowers/datautils.py index c156f307..0b24d252 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -1,8 +1,10 @@ import pandas as pd +import polars as pl import numpy as np from scipy.interpolate import griddata from scipy import optimize + from rowers.mytypes import otwtypes, otetypes, rowtypes from rowers.models import Workout @@ -358,8 +360,8 @@ def getmaxwattinterval(tt, ww, i): def getfastest(df, thevalue, mode='distance'): - tt = df['time'].copy() - dd = df['cumdist'].copy() + tt = df['time'].clone() + dd = df['cumdist'].clone() tmax = tt.max() if mode == 'distance': # pragma: no cover @@ -370,40 +372,28 @@ def getfastest(df, thevalue, mode='distance'): return 0 -# if tmax > 500000: -# newlen=int(tmax/2000.) -# newt = np.arange(newlen)*tmax/float(newlen) -# deltat = newt[1]-newt[0] -# else: -# newt = np.arange(0,tmax,10.) -# deltat = 10. - newlen = 1000 newt = np.arange(newlen)*tmax/float(newlen) deltat = newt[1]-newt[0] - dd = griddata(tt.values, - dd.values, newt, method='linear', rescale=True) + dd = griddata(tt.to_numpy(), + dd.to_numpy(), newt, method='linear', rescale=True) - tt = pd.Series(newt, dtype='float') - dd = pd.Series(dd, dtype='float') + tt = pl.Series(newt, dtype=pl.Float64) + dd = pl.Series(dd, dtype=pl.Float64) + + G = pl.concat([pl.Series([0.0]), dd]) - G = pd.concat([pd.Series([0]), dd]) - # T = pd.concat([pd.Series([0]), dd]) - # h = np.mgrid[0:len(tt)+1:1, 0:len(tt)+1:1] - # distances = pd.DataFrame(h[1]-h[0]) ones = 1+np.zeros(len(G)) Ghor = np.outer(ones, G) - # Thor = np.outer(ones, T) - # Tver = np.outer(T, ones) + Gver = np.outer(G, ones) Gdif = Ghor-Gver Gdif = np.tril(Gdif.T).T - Gdif = pd.DataFrame(Gdif) + Gdif = pl.DataFrame(Gdif) F = Gdif - F.fillna(inplace=True, method='ffill', axis=1) - F.fillna(inplace=True, value=0) + F = F.fill_nan(0) restime = [] distance = [] @@ -414,7 +404,7 @@ def getfastest(df, thevalue, mode='distance'): restime.append(deltat*i) cp = np.diag(F, i).max() loc = np.argmax(np.diag(F, i)) - thestarttime = tt[loc] + thestarttime = tt.to_numpy()[loc] starttimes.append(thestarttime) distance.append(cp) @@ -424,10 +414,6 @@ def getfastest(df, thevalue, mode='distance'): distance = np.array(distance) starttimes = np.array(starttimes) - # for i in range(len(restime)): - # if restime[i] 0: w.plannedsession = ps w.save() @@ -1685,8 +1686,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False): record.coursecompleted = True record.workoutid = ws[0].id if race.sessiontype == 'fastest_distance': - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[ws[0].id]) + df = dataprep.remove_nulls_pl(df) fastest_milliseconds, startsecond, endsecond = datautils.getfastest( df, race.sessionvalue, mode='distance') velo = race.sessionvalue/fastest_milliseconds @@ -1702,8 +1704,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False): record.endsecond = endsecond record.save() if race.sessiontype == 'fastest_time': # pragma: no cover - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[ws[0].id]) + df = dataprep.remove_nulls_pl(df) fastest_meters, startsecond, endsecond = datautils.getfastest( df, race.sessionvalue, mode='time') velo = fastest_meters/(60.*race.sessionvalue) diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index ed73b421..cd73dd7a 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -309,11 +309,17 @@ def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover return df, row + def mocked_getsmallrowdata_uh(*args, **kwargs): # pragma: no cover df = pl.read_csv('rowers/tests/testdata/uhfull.csv') return df +def mocked_getsmallrowdata_uh_pd(*args, **kwargs): # pragma: no cover + df = pd.read_csv('rowers/tests/testdata/uhfull.csv') + + return df + def mocked_getsmallrowdata_forfusion(*args, **kwargs): df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv') diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 3c59620a..e1311d81 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -597,8 +597,8 @@ class DataPrepTests(TestCase): result = getagegrouprecord(25) self.assertEqual(int(result),590) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh) - def test_get_videodata(self,mocked_getsmallrowdata_uh): + @patch('rowers.dataprep.getsmallrowdata_pd',side_effect=mocked_getsmallrowdata_uh_pd) + def test_get_videodata(self,mocked_getsmallrowdata_uh_pd): data, metrics, maxtime = dataprep.get_video_data(self.wuh_otw) self.assertEqual(len(data),9) diff --git a/rowers/tests/test_units.py b/rowers/tests/test_units.py index 68754ddc..9fb66dac 100644 --- a/rowers/tests/test_units.py +++ b/rowers/tests/test_units.py @@ -71,9 +71,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=1) self.assertEqual(w.forceunit,'lbs') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,398) + self.assertEqual(average_N,400) data = dataprep.read_df_sql(13) average_N = int(data['averageforce'].mean()) @@ -119,9 +120,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=13) self.assertEqual(w.forceunit,'N') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,263) + self.assertEqual(average_N,271) def test_upload_speedcoach_colin(self): login = self.c.login(username=self.u.username, password=self.password) @@ -155,9 +157,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=13) self.assertEqual(w.forceunit,'N') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,105) + self.assertEqual(average_N,122) @override_settings(TESTING=True) class TestForceUnit(TestCase): @@ -206,7 +209,7 @@ class TestForceUnit(TestCase): rowdata = dataprep.rdata('rowers/tests/testdata/PainsledForce.csv') df = dataprep.dataprep(rowdata.df) - #df = dataprep.getsmallrowdata_db(['averageforce'],ids=[w[0].id],doclean=False, + #df = dataprep.read_data(['averageforce'],ids=[w[0].id],doclean=False, # compute=False) try: average_N = int(df['averageforce'].mean()) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index b36335c1..b27a198a 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -29,8 +29,8 @@ class ViewTest(TestCase): self.nu = datetime.datetime.now() @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_upload_view_sled(self, mocked_sqlalchemy,mocked_read_data): login = self.c.login(username='john',password='koeinsloot') self.assertTrue(login) @@ -120,8 +120,8 @@ class ViewTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_read_data): login = self.c.login(username='john',password='koeinsloot') self.assertTrue(login) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index fc88d4937d88aac8c7941b1fc71f93d7b02ffc45..ef4b2add63ebad16a95789e1316cacca12240742 100644 GIT binary patch literal 3999 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 F0RU~;Ja7O2 literal 4003 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 Date: Mon, 29 Apr 2024 22:20:34 +0200 Subject: [PATCH 114/121] coverage exceptions --- rowers/dataroutines.py | 46 +++++++++++++------------- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4001 bytes 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 083dce8d..40333cdf 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -193,7 +193,7 @@ def remove_nulls_pl(data): if not data.is_empty(): try: data = data.drop_nulls() - except: + except: # pragma: no cover pass return data @@ -401,7 +401,7 @@ def filter_df(datadf, fieldname, value, largerthan=True): mask = datadf[fieldname] >= value datadf.loc[mask, fieldname] = np.nan - except TypeError: + except TypeError: # pragma: no cover pass return datadf @@ -430,7 +430,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, except KeyError: try: datadf['workoutid'] = 0 - except TypeError: + except TypeError: # pragma: no cover datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) before = {} @@ -525,7 +525,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, pass # clean data for useful ranges per column - if not ignorehr: + if not ignorehr: # pragma: no cover try: mask = datadf['hr'] < 30 datadf.mask(mask, inplace=True) @@ -676,7 +676,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, after = {} - if for_chart: + if for_chart: # pragma: no cover return datadf for workoutid in data_orig['workoutid'].unique(): after[workoutid] = len( @@ -685,22 +685,22 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, if ratio < 0.01 or after[workoutid] < 2: return data_orig - return datadf + return datadf # pragma: no cover -def replace_zeros_with_nan(x): +def replace_zeros_with_nan(x): # pragma: no cover return np.nan if x == 0 else x def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, - ignoreadvanced=False, for_chart=False): + ignoreadvanced=False, for_chart=False): # pragma: no cover # clean data remove zeros and negative values try: _ = datadf['workoutid'].unique() - except KeyError: + except KeyError: # pragma: no cover try: datadf['workoutid'] = 0 except TypeError: datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) - except ColumnNotFoundError: + except ColumnNotFoundError: # pragma: no cover datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) before = {} @@ -711,23 +711,23 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, data_orig = datadf.clone() # bring metrics which have negative values to positive domain - if len(datadf) == 0: + if len(datadf) == 0: # pragma: no cover return data_orig try: datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) - except (KeyError, TypeError): + except (KeyError, TypeError): # pragma: no cover pass except(ComputeError, InvalidOperationError, ColumnNotFoundError): return data_orig - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('peakforceangle')+1000).alias('peakforceangle')) except (KeyError, TypeError): pass except(ComputeError, InvalidOperationError, ColumnNotFoundError): return data_orig - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) except (KeyError, TypeError): pass @@ -735,7 +735,7 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, return data_orig # protect 0 spm values from being nulled - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('spm')+1.0).alias('spm')) except (KeyError, TypeError): pass @@ -743,14 +743,14 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, return data_orig # protect 0 workoutstate values from being nulled - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('workoutstate')+1).alias('workoutstate')) except (KeyError, TypeError): pass except(ComputeError, InvalidOperationError, ColumnNotFoundError): return data_orig - try: + try: # pragma: no cover datadf = datadf.select(pl.all().clip(lower_bound=0)) # datadf = datadf.clip(lower=0) except (TypeError): @@ -774,9 +774,9 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, 'wash', 'peakforceangle', 'effectiveangle', - ] + ] # pragma: no cover - for col in datadf.columns: + for col in datadf.columns: # pragma: no cover datadf = datadf.with_columns( pl.when(datadf[col] == 0).then(pl.lit(np.nan)).otherwise(datadf[col]), name=col @@ -785,24 +785,24 @@ def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, # datadf = datadf.map_partitions(lambda df:df.replace(to_replace=0,value=np.nan)) # bring spm back to real values - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('spm')-1.0).alias('spm')) except (TypeError, KeyError): pass # bring workoutstate back to real values - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('workoutstate')-1).alias('workoutstate')) except (TypeError, KeyError): pass # return from positive domain to negative - try: + try: # pragma: no cover datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) except (KeyError, TypeError): pass - try: + try: # pragma: no cover datadf = datadf.with_columns((pl.col('peakforceangle')-1000).alias('peakforceangle')) except (KeyError, TypeError): pass diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index e1311d81..4903774b 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -705,7 +705,7 @@ class InteractivePlotTests(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_interactive_chart(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index ef4b2add63ebad16a95789e1316cacca12240742..22b60c9ffc8bf0f1fdc46f4fd42add531d16ebf9 100644 GIT binary patch literal 4001 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 F0RU~;Ja7O2 From ebea0047cccb8e47c46aa79adb8003404b082822 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 3 May 2024 16:56:01 +0200 Subject: [PATCH 115/121] some fixes, after manual testing --- rowers/dataroutines.py | 43 ++++++++++++++++++++++++++-- rowers/interactiveplots.py | 13 ++++++--- rowers/templates/embedded_video.html | 8 +++--- rowers/templates/multicompare.html | 10 ++----- rowers/views/analysisviews.py | 3 ++ rowers/views/workoutviews.py | 2 ++ 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 40333cdf..9e9f1add 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -186,10 +186,12 @@ def remove_nulls_pl(data): data = data.select(pl.all().forward_fill()) data = data.select(pl.all().backward_fill()) data = data.fill_nan(None) + data = data.select(cs.by_dtype(pl.NUMERIC_DTYPES)).collect() data = data[[s.name for s in data if not s.is_infinite().sum()]] data = data[[s.name for s in data if not (s.null_count() == data.height)]] + if not data.is_empty(): try: data = data.drop_nulls() @@ -207,6 +209,8 @@ def get_video_data(w, groups=['basic'], mode='water'): columns = list(set(columns)) df = getsmallrowdata_pd(columns, ids=[w.id], workstrokesonly=False, doclean=False, compute=False) + df.dropna(axis=0, how='all', inplace=True) + df.dropna(axis=1, how='all', inplace=True) df['time'] = (df['time']-df['time'].min())/1000. @@ -245,7 +249,6 @@ def get_video_data(w, groups=['basic'], mode='water'): coordinates['time'] = coordinates['time']-coordinates['time'].min() latitude = coordinates['latitude'] longitude = coordinates['longitude'] - # bundle data data = { 'boatspeed': boatspeed.values.tolist(), @@ -263,7 +266,10 @@ def get_video_data(w, groups=['basic'], mode='water'): else: sigfigs = dict(rowingmetrics)[c]['sigfigs'] if (c != 'pace'): - da = ((10**sigfigs)*df2[c]).astype(int)/(10**sigfigs) + try: + da = ((10**sigfigs)*df2[c]).astype(int)/(10**sigfigs) + except: + da = df2[c] else: da = df2[c] data[c] = da.values.tolist() @@ -281,6 +287,14 @@ def get_video_data(w, groups=['basic'], mode='water'): maxtime = coordinates['time'].max() + data = pd.DataFrame(data) + data.replace([np.inf, -np.inf], np.nan, inplace=True) + data.dropna(inplace=True) + + data = pl.from_pandas(data) + + data = data.to_dict(as_series=False) + return data, metrics, maxtime @@ -1490,7 +1504,8 @@ def getrowdata_pl(id=0, doclean=False, convertnewtons=True, -def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True): +def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True, + startenddict={}): if ids: csvfilenames = [ 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] @@ -1504,6 +1519,17 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for id, f in zip(ids, csvfilenames): if os.path.isfile(f): df = pl.scan_parquet(f) + if startenddict: + try: + startsecond, endsecond = startenddict[id] + df = df.filter(pl.col("time") >= 1.0e3*startsecond, + pl.col("time") <= 1.0e3*endsecond) + df = df.with_columns(time = pl.col("time")-1.0e3*startsecond) + if 'cumdist' in columns: + df = df.collect() + df = df.with_columns(cumdist = pl.col("cumdist")-df[0, "cumdist"]).lazy() + except KeyError: + pass data.append(df) else: rowdata, row = getrowdata(id=id) @@ -1516,6 +1542,17 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, bands=True, otwpower=True, barchart=True, polars=True) df = pl.scan_parquet(f) + if startenddict: + try: + startsecond, endsecond = startenddict[id] + df = df.filter(pl.col("time") >= 1.0e3*startsecond, + pl.col("time") <= 1.0e3*endsecond) + df = df.with_columns(time = pl.col("time")-1.0e3*startsecond) + if 'cumdist' in columns: + df = df.collect() + df = df.with_columns(cumdist = pl.col("cumdist")-df[0, "cumdist"]).lazy() + except KeyError: + pass data.append(df) data = pl.collect_all(data) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 597b5158..b7156e66 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2099,11 +2099,12 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', promember=0, workstrokesonly=True, labeldict=None, startenddict={}): + print(startenddict) columns = [xparam,yparam] columns_basic = [xparam,yparam] add_columns = [ 'ftime', 'distance', 'fpace', - 'spm', + 'spm','cumdist', 'time', 'pace', 'workoutstate', 'workoutid' ] @@ -2117,15 +2118,17 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', doclean = True - datadf = pd.DataFrame() + datadf = pl.DataFrame() if promember: datadf = dataprep.read_data(columns, ids=ids, doclean=doclean, compute=compute, - workstrokesonly=workstrokesonly, for_chart=True) + workstrokesonly=workstrokesonly, for_chart=True, + startenddict=startenddict) else: datadf = dataprep.read_data(columns_basic, ids=ids, doclean=doclean, compute=compute, - workstrokesonly=workstrokesonly, for_chart=True) + workstrokesonly=workstrokesonly, for_chart=True, + startenddict=startenddict) datadf = dataprep.remove_nulls_pl(datadf) @@ -2135,6 +2138,8 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', datadf = datadf.with_columns(pl.col("workoutid").cast(pl.UInt32).keep_name()) + # filter for start end dict + nrworkouts = len(ids) diff --git a/rowers/templates/embedded_video.html b/rowers/templates/embedded_video.html index 0ff08fbe..0fe35134 100644 --- a/rowers/templates/embedded_video.html +++ b/rowers/templates/embedded_video.html @@ -116,7 +116,7 @@ function copyText() {
      • {% if user.is_authenticated and user == workout.user.user and not locked %}
      • -

        Paste link to you tube video below

        +

        Paste link to Youtube video below

        Use the slider to locate start point for video on workout map

        Playing the video will advance the data in synchonization. Use the regular YouTube controls @@ -169,9 +169,8 @@ function copyText() {

      • - - + + -

        Interactive Comparison

        @@ -22,6 +17,7 @@
      • {{ the_div|safe }} + {{ interactiveplot |safe }}
      • diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index ec51ac2d..587db08d 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -387,6 +387,9 @@ def trendflexdata(workouts, options, userid=0): datadf = dataprep.filter_df(datadf, 'driveneergy', workmax, largerthan=False) + if yparam == 'power': + datadf = dataprep.filter_df(datadf, 'power', 1, largerthan=True) + datadf.dropna(axis=0, how='any', inplace=True) datemapping = { diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 6734e606..e4b49aef 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -347,6 +347,7 @@ def workout_video_create_view(request, id=0): # get data data, metrics, maxtime = dataprep.get_video_data( w, groups=metricsgroups, mode=mode) + hascoordinates = pd.Series(data['latitude']).std() > 0 # create map @@ -377,6 +378,7 @@ def workout_video_create_view(request, id=0): template = 'embedded_video.html' + return render(request, template, { From 2544ea16a9e0e1561720642681d9f19035a0ddee Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 4 May 2024 11:01:33 +0200 Subject: [PATCH 116/121] temp --- rowers/dataroutines.py | 3 +++ rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes 2 files changed, 3 insertions(+) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 9e9f1add..cc433e31 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -281,6 +281,9 @@ def get_video_data(w, groups=['basic'], mode='water'): except KeyError: # pragma: no cover pass + for key, value in data.items(): + print(key, len(value)) + metrics['boatspeed'] = metrics.pop('velo') # metrics['workperstroke'] = metrics.pop('driveenergy') metrics = collections.OrderedDict(sorted(metrics.items())) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 22b60c9ffc8bf0f1fdc46f4fd42add531d16ebf9..3aebd4a6621f2e05038dbd8055c473f3eeebc4de 100644 GIT binary patch literal 4000 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 GfB^s@l|Wbk literal 4001 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 Date: Sat, 4 May 2024 11:31:25 +0200 Subject: [PATCH 117/121] fixed alerts, manual testing completed --- rowers/alerts.py | 7 ++++--- rowers/dataroutines.py | 15 +++++++-------- rowers/interactiveplots.py | 2 ++ rowers/plannedsessions.py | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/rowers/alerts.py b/rowers/alerts.py index bf3587bd..2beeca2e 100644 --- a/rowers/alerts.py +++ b/rowers/alerts.py @@ -101,9 +101,10 @@ def alert_get_stats(alert, nperiod=0): # pragma: no cover ids = [w.id for w in workouts] try: - df = read_data(columns, ids=ids, doclean=True, - workstrokesonly=workstrokesonly) - df = remove_nulls_pl(df) + df = getsmallrowdata_pd(columns, ids=ids, doclean=True, + workstrokesonly=workstrokesonly) + df.dropna(axis=1,how='all',inplace=True) + df.dropna(axis=0,how='all',inplace=True) except: return { 'workouts': workouts.count(), diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index cc433e31..b08fc1a7 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -231,7 +231,6 @@ def get_video_data(w, groups=['basic'], mode='water'): df2 = df2.round(decimals=2) - boatspeed = (100*df2['velo']).astype(int)/100. try: coordinates = get_latlon_time(w.id) @@ -245,10 +244,13 @@ def get_video_data(w, groups=['basic'], mode='water'): coordinates.set_index(pd.to_timedelta( coordinates['time'], unit='s'), inplace=True) - coordinates = coordinates.resample('1s').mean().interpolate() - coordinates['time'] = coordinates['time']-coordinates['time'].min() - latitude = coordinates['latitude'] - longitude = coordinates['longitude'] + coordinates = coordinates.resample('1s').first().interpolate().fillna(method='ffill') + #coordinates['time'] = coordinates['time']-coordinates['time'].min() + df2 = pd.concat([df2, coordinates], axis=1) + latitude = df2['latitude'] + longitude = df2['longitude'] + boatspeed = (100*df2['velo']).astype(int)/100. + # bundle data data = { 'boatspeed': boatspeed.values.tolist(), @@ -281,9 +283,6 @@ def get_video_data(w, groups=['basic'], mode='water'): except KeyError: # pragma: no cover pass - for key, value in data.items(): - print(key, len(value)) - metrics['boatspeed'] = metrics.pop('velo') # metrics['workperstroke'] = metrics.pop('driveenergy') metrics = collections.OrderedDict(sorted(metrics.items())) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index b7156e66..65fd28ed 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -365,6 +365,8 @@ def interactive_planchart(data, startdate, enddate): 'data': data_dict, } + print(data_dict) + script, div = get_chart("/plan", chart_data, debug=False) return script, div diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 7f23e078..c8ca0ab2 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -281,7 +281,7 @@ def get_execution_report(rower, startdate, enddate, plan=None): else: plannedscore += 60 actualscore += 0 - elif w.hrtss != 0: + elif w.hrtss >= 0: if ratio > 0: plannedscore += w.hrtss/ratio actualscore += w.hrtss From 5d8ff0029269bef872aae58f1c5a080a4e891c55 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 4 May 2024 15:08:57 +0200 Subject: [PATCH 118/121] still some print statements --- rowers/interactiveplots.py | 2 -- rowers/templates/instroke_interactive.html | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 65fd28ed..b7156e66 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -365,8 +365,6 @@ def interactive_planchart(data, startdate, enddate): 'data': data_dict, } - print(data_dict) - script, div = get_chart("/plan", chart_data, debug=False) return script, div diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index acc2a8d1..e66bf295 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -140,7 +140,7 @@ $( function() {

        - +

        diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3aebd4a6621f2e05038dbd8055c473f3eeebc4de..d2ab1e31a9fe3dad76d42e230817da35c75d9111 100644 GIT binary patch literal 4000 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 GfB^tIfIlPv literal 4000 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 GfB^s@l|Wbk From 7a0d28ca5a63059a99d9e4b9ebfa02c0451cbd25 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 4 May 2024 15:10:27 +0200 Subject: [PATCH 119/121] removed print statement --- rowers/interactiveplots.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index b7156e66..c17d39ac 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2099,7 +2099,6 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', promember=0, workstrokesonly=True, labeldict=None, startenddict={}): - print(startenddict) columns = [xparam,yparam] columns_basic = [xparam,yparam] add_columns = [ From 201cb881310f8edda845b1cb8f74d3483c887693 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 4 May 2024 15:57:31 +0200 Subject: [PATCH 120/121] tested --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index d2ab1e31a9fe3dad76d42e230817da35c75d9111..c81943c74035ee5c8daef2ee7508fa58fd513cb6 100644 GIT binary patch literal 4001 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 H#DD<+rW-{S literal 4000 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 GfB^tIfIlPv From 4a61ecc258ed69d33b7d906429534c4ad7a315d2 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 4 May 2024 16:18:07 +0200 Subject: [PATCH 121/121] fix before release --- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index c81943c74035ee5c8daef2ee7508fa58fd513cb6..d6f2656097b9790a35447edeb41622843541b3c8 100644 GIT binary patch delta 3773 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 delta 3733 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