diff --git a/GSS/GSSutils/data_read.py b/GSS/GSSutils/data_read.py index 461de70..6f56df4 100755 --- a/GSS/GSSutils/data_read.py +++ b/GSS/GSSutils/data_read.py @@ -26,17 +26,19 @@ cols = ['Activity number','Activity Type','Date','Distance','Time','Shoes','Rise','Fall','1km','1 mile','1.5 mile','3 mile','5km','5 mile','10km','10 mile','20km','Half','Full','C10k','C20k','C50k','C100k','C200k','C250k','Run Rankings','Notes','Admin'] -dist_dict = {'1km': 1000, - '1 mile': 1609.34, - '1.5 mile': 2414.02, - '3 mile': 4828.03, - '5km': 5000, - '5 mile': 8046.72, - '10km': 10000, - '10 mile': 16093.40, - '20km': 20000, - 'Half': 21097.7, - 'Full': 42195} +dist_dict = { + '1km': 1000, + '1 mile': 1609.34, + '1.5 mile': 2414.02, + '3 mile': 4828.03, + '5km': 5000, + '5 mile': 8046.72, + '10km': 10000, + '10 mile': 16093.40, + '20km': 20000, + 'Half': 21097.7, + 'Full': 42195 +} dist_list = list(dist_dict.keys()) @@ -546,10 +548,14 @@ def route_data(activity_number): return(df) ''' -def pull_csv_pd(activity_number,option='column_name'): +def generate_gpx_archive_filename(activity_number: str)->str: fileDir = os.path.dirname(os.path.realpath('__file__')) + + return os.path.join(fileDir, 'GPXarchive.gitignore/activity_{}.csv'.format(activity_number)) - filename = os.path.join(fileDir, 'GPXarchive.gitignore/activity_{}.csv'.format(activity_number)) +def pull_csv_pd(activity_number, option='column_name'): + + filename = generate_gpx_archive_filename(activity_number) #I think I only need distance and time @@ -567,11 +573,9 @@ def pull_csv_pd(activity_number,option='column_name'): return(df) -def route_data(activity_number,option='column_name'): - #if len(activity_number) == 10: - # df = pull_gpx(activity_number) - #if len(activity_number) == 8 or len(activity_number) == 9: - df = pull_csv_pd(activity_number,option) +def route_data(activity_number, option='column_name'): + + df = pull_csv_pd(activity_number, option) return(df) @@ -601,7 +605,7 @@ def __init__(self, activity_id): #Date-related qualities self.date_str = self.activity_dict['Date'] - self.date_dt = datetime.strptime(self.date_str, '%Y-%m-%d %H:%M:%S') + self.date_dt = datetime.strptime(self.date_str, '%Y-%m-%d %H:%M:%S')# if isinstance(self.date_str, str) else self.date_str self.date = self.strftime('%Y-%m-%d') self.time = self.strftime('%H:%M:%S') self.year, self.month, self.day = (round(float(self.strftime(fmt))) for fmt in ('%Y', '%m', '%d')) diff --git a/GSS/GSSutils/editing.py b/GSS/GSSutils/editing.py index cf82c3f..faa91aa 100755 --- a/GSS/GSSutils/editing.py +++ b/GSS/GSSutils/editing.py @@ -1,10 +1,3 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Feb 21 09:03:26 2021 - -@author: WS -""" - import haversine import os import pandas as pd @@ -12,8 +5,7 @@ from math import sqrt from datetime import datetime -from typing import List - +from typing import Dict, List, Optional, Tuple from . import data_read as dr from . import loading_modules as lm @@ -67,23 +59,55 @@ def reset_distance_column(a_df: pd.DataFrame, include_altitude=False)->List[floa distances.append(new) return distances + +def recalculate_and_return_best_distances(df: pd.DataFrame, activity_number: str, final_distance: Optional[float] = None)->Tuple[pd.DataFrame, Dict[str, str]]: + + filename = dr.generate_gpx_archive_filename(activity_number) + + if not final_distance: + final_distance = df['Distance'].tolist()[-1] + + times = {} + + for i in dr.dist_list: + if final_distance >= dr.dist_dict[i]: + df = lm.best_time_ws(i, df) + df.to_csv(r'{}'.format(filename)) + print(df.columns) + + times[i] = lm.best_time_ws(i, df, True) -def reset_distances(user_df,activity_number,column,new, include_distance=False): - """ - Turning off the distance resetting in favour of just resetting the times + return df, times + +def reset_best_times(user_df: pd.DataFrame, activity_number: str, best_times: Dict[str, str])->pd.DataFrame: + for d in dr.dist_list: + user_df.loc[user_df['Activity number']==activity_number, d] = best_times.get(d) or 'NONE' + return user_df + +def reset_activity_time(user_df: pd.DataFrame, activity_df: pd.DataFrame, activity_number: str)->pd.DataFrame: + times = activity_df['time'].tolist() + + duration = times[-1] - times[0] + + full_secs = duration.total_seconds() + + user_df.loc[user_df['Activity number']==activity_number, ['Time']] = time.strftime('%H:%M:%S',time.gmtime(full_secs)) + return user_df + +def reset_distances(user_df, activity_number, column, new, include_distance=False)->None: + """ + NOTE: Turning off the distance resetting in favour of just resetting the times """ location = user_df.loc[user_df['Activity number'] == activity_number].index.values[0] - fileDir = os.path.dirname(os.path.realpath('__file__')) - - filename = os.path.join(fileDir, 'GPXarchive.gitignore/activity_{}.csv'.format(activity_number)) + filename = dr.generate_gpx_archive_filename(activity_number) a_df = pd.read_csv(r'{}'.format(filename)) - archive = os.path.join(fileDir, 'GPXarchive.gitignore/activity_{}_a.csv'.format(activity_number)) + archive = dr.generate_gpx_archive_filename(activity_number+'_a') a_df.to_csv(archive) @@ -91,7 +115,6 @@ def reset_distances(user_df,activity_number,column,new, include_distance=False): cols = [c for c in cols if c in list(a_df.columns)] a_df = a_df[cols] - if include_distance: a_df['distance'] = reset_distance_column(a_df) @@ -101,21 +124,14 @@ def reset_distances(user_df,activity_number,column,new, include_distance=False): a_df['time'] = a_df['time'].apply(lambda x: datetime.strptime(x,'%Y-%m-%d %H:%M:%S')) - times = {} - + best_times = {} + if activity == 'Running': - for i in dr.dist_list: - if final_distance >= dr.dist_dict[i]: - a_df = lm.best_time_ws(i, a_df) - - a_df.to_csv(r'{}'.format(filename)) - - times[i] = lm.best_time_ws(i, a_df, True) - - a_df = a_df[cols+list(times.keys())] + a_df, best_times = recalculate_and_return_best_distances(a_df, activity_number, final_distance) + + a_df = a_df[cols+list(best_times.keys())] - for i in dr.dist_list: - user_df.at[location, i] = times.get(i) or 'NONE' + user_df = reset_best_times(user_df, activity_number, best_times) user_df.at[location, 'Distance'] = round(final_distance/1000,2) @@ -131,18 +147,85 @@ def reset_distances(user_df,activity_number,column,new, include_distance=False): user_df.at[location, 'Notes'] = notes - times = a_df['time'].tolist() + user_df.at[location, 'Date'] = datetime.strftime(a_df['time'].tolist()[0], '%Y-%m-%d %H:%M:%S') - user_df.at[location, 'Date'] = times[0] + user_df = reset_activity_time(user_df, a_df, activity_number) - duration = times[-1] - times[0] - full_secs = duration.total_seconds() + user_df.to_csv('activities.csv',index=False) + +def remove_activity_from_df(user_df: pd.DataFrame, activity_number: str)->None: + """ + Removes the database + Does not remove the activity.csv file + """ + + user_df = user_df[user_df['Activity number'] != activity_number] - user_df.at[location, 'Time'] = time.strftime('%H:%M:%S',time.gmtime(full_secs)) - user_df.to_csv('activities.csv',index=False) + +def merge_activities(user_df: pd.DataFrame, this_activity: str, activity_to_merge: str, **kwargs)->None: + """ + Adds records for activity_to_merge to this_activity + Removes this activity_to_merge from activities df + Runs best times if appropriate + """ + archive_name = dr.generate_gpx_archive_filename(this_activity+'_a') + this_activity_bool = user_df['Activity number'] == this_activity + to_merge_bool = user_df['Activity number'] == activity_to_merge + + this_activity_df = dr.route_data(this_activity) + this_activity_filename = dr.generate_gpx_archive_filename(this_activity) + + this_activity_df.to_csv(archive_name) + + to_merge = dr.route_data(activity_to_merge) + + df = pd.concat([this_activity_df, to_merge]) + + df = df.sort_values(by=['time'], ascending=True) -def download_as_csv(activity_number,suffix): + df['distance'] = df['distance'].diff(periods=1).fillna(0).apply(lambda x: 0 if x<0 else x).cumsum().apply(lambda x: round(x, 2)) + #shift(1).fillna(0)#.cumsum() # from 0->X, 0-Y to 0->X+Y + # there is no elapsed time equivalent + + if 'alt' in df.columns: + df['alt'] = df['alt'].fillna(0) + # a hack, admittedly, but when I wrote this it was a coastal route that kept returning na + + final_distance = df['distance'].tolist()[-1] + + user_df.loc[this_activity_bool, ['Distance']] = round(final_distance/1000, 2) + user_df = reset_activity_time(user_df, df, this_activity) + + for measure in ['Rise', 'Fall']: + this_activity_measure = user_df.loc[this_activity_bool, measure].tolist()[0] + to_merge_measure = user_df.loc[to_merge_bool, measure].tolist()[0] + if this_activity_measure != 'NONE' and to_merge_measure != 'NONE': + if isinstance(this_activity_measure, str): + this_activity_measure = float(this_activity_measure) + if isinstance(to_merge_measure, str): + to_merge_measure = float(to_merge_measure) + # would be good to convert to the classes with typing + + new_measure = this_activity_measure + to_merge_measure + new_measure = f'{round(new_measure,1)}' + else: + new_measure = 'NONE' + user_df.loc[this_activity_bool, measure] = new_measure + + activity_type = user_df.loc[this_activity_bool, 'Activity Type'].tolist()[0] + + if activity_type == 'Running': + df, best_times = recalculate_and_return_best_distances(df, this_activity, final_distance) + user_df = reset_best_times(user_df, this_activity, best_times) + else: + df.to_csv(r'{}'.format(this_activity_filename)) + + user_df.to_csv('activities.csv', index=False) + + # remove_activity_from_df(user_df, activity_to_merge) + +def download_as_csv(activity_number, suffix): fileDir = os.path.dirname(os.path.realpath('__file__')) @@ -165,14 +248,18 @@ def add_csv(x:str)->str: return output_filename - def gen_trkpt(route_data: pd.Series)->str: time = str(route_data['time']).replace(' ', 'T') + '.000Z' + + if 'alt' in route_data.index: + elevation = f"{route_data['alt']}" + else: + elevation = "" return f''' - {route_data['alt']} + {elevation} @@ -181,8 +268,7 @@ def gen_trkpt(route_data: pd.Series)->str: ''' - -def download_as_gpx(activity: dr.Activity)->None: +def download_as_gpx(activity: dr.Activity)->str: s = f''' @@ -214,10 +300,25 @@ def download_as_gpx(activity: dr.Activity)->None: return activity.activity_id +def edit_prompt(user_df, column): -def edit_prompt(user_df,column): - - if column == 'Shoes': + download_as_csv_prompt = """Download or archive +
or type the desired filename into the url bar""" + download_as_gpx_prompt = "Download" + removal_prompt = "If you're sure, confirm removal" + + simple_prompt_conversion = { + 'download_csv': download_as_csv_prompt, + 'download_gpx': download_as_gpx_prompt, + 'merge_with': 'Enter id of activity to merge with', + 'Remove': removal_prompt + } + + if column in simple_prompt_conversion: + + text = simple_prompt_conversion[column] + + elif column == 'Shoes': s_df = user_df[['Date','Shoes']].sort_values(by='Date',ascending=False) s_df['Shoes'] = s_df['Shoes'].fillna('NONE') @@ -234,31 +335,25 @@ def shoes_link(shoe):

Remember that dictionaries must be in the form {"Shoes 1": 5.00, "Shoes 2": 5.00} for 5km in Shoes 1 and Shoes 2, with double and not single quotes ''' - - - - elif column == 'download_csv': - text = """Download or archive -
or type the desired filename into the url bar - """ - - elif column == 'download_gpx': - text = "Download" else: + text = 'To amend, add to url with underscores in lieu of spaces' return text -def edit_field(user_df,activity_number,column,new, activity: dr.Activity): +def edit_field(user_df, activity_number, column, new, activity: dr.Activity): + """ + Note column is capitalised + """ location = user_df.loc[user_df['Activity number'] == activity_number].index.values[0] - allowed = ['Notes','Admin','Activity Type','Shoes']#At some point do type, but, for the moment, that contains a space + edit_fields = ['Notes','Admin','Activity Type','Shoes']#At some point do type, but, for the moment, that contains a space column = format_url(column) - if column in allowed: + if column in edit_fields: print(user_df[column].dtype) @@ -277,7 +372,7 @@ def edit_field(user_df,activity_number,column,new, activity: dr.Activity): elif column == 'Reset': - reset_distances(user_df,activity_number,column,new) + reset_distances(user_df,activity_number,column,new, False) out = 'File reset' @@ -291,8 +386,14 @@ def edit_field(user_df,activity_number,column,new, activity: dr.Activity): path = download_as_gpx(activity) out = 'GPX downloaded' - + + elif column == 'Remove': + remove_activity_from_df(user_df, activity_number) + out = 'Activity removed' + elif column == 'Merge with': + merge_activities(user_df, activity_number, new) + out = f'{new} activity merged with {activity_number}' else: out = f'{column} not editable' diff --git a/GSS/htmls.py b/GSS/htmls.py index 589304f..f52a52a 100755 --- a/GSS/htmls.py +++ b/GSS/htmls.py @@ -451,7 +451,7 @@ def edit_prompt(field): return prompt -def return_edit(ac_no,field,new_string, activity): +def return_edit(ac_no, field, new_string, activity): user_df = dr.pull_data() diff --git a/GSS/templates/edit_index.html b/GSS/templates/edit_index.html index a451904..7aff072 100755 --- a/GSS/templates/edit_index.html +++ b/GSS/templates/edit_index.html @@ -13,14 +13,20 @@ -

Type
+

+ Type
Shoes
- Notes
- Admin
- Download csv
- Download GPX
- Reset

-

Activity | Home

+ Notes
+ Admin
+ Download csv
+ Download GPX
+ Reset
+ Merge with
+ Delete +

+

+ Activity | Home +

diff --git a/GSS/views.py b/GSS/views.py index e6be0b5..bfedc5e 100755 --- a/GSS/views.py +++ b/GSS/views.py @@ -188,11 +188,11 @@ def edit_func(request,activity,field,new_string): ac = dr.Activity(activity) - edit_complete = htmls.return_edit(activity,field,new_string, ac) - - dictionary = {'edit_complete': edit_complete} - - return render(request, 'edit_func.html', dictionary) + edit_complete = htmls.return_edit(activity, field, new_string, ac) + + return render(request, 'edit_func.html', { + 'edit_complete': edit_complete + }) def edit_field(request,activity,field):