├── .coveragerc ├── .deepsource.toml ├── .gitignore ├── .travis.yml ├── README.md ├── requirements.txt ├── running_calculator ├── __init__.py ├── core │ ├── __init__.py │ ├── calculator.py │ ├── constants.py │ ├── control.py │ ├── running_index.py │ └── utils.py ├── data │ ├── daniels_table_races.csv │ └── paces.csv └── running_calculator.py ├── sonar-project.properties └── tests ├── __init__.py ├── test_calculator.py ├── test_running_index.py └── test_utils.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | tests/* 4 | running_calculator/running_calculator.py 5 | *__init__* 6 | running_calculator/core/control.py 7 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | test_patterns = [ 8 | "tests/**", 9 | ] 10 | 11 | 12 | [analyzers.meta] 13 | runtime_version = "3.x.x" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=python,visualstudiocode 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # pipenv 75 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 76 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 77 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 78 | # install all needed dependencies. 79 | #Pipfile.lock 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | .spyproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | # Mr Developer 95 | .mr.developer.cfg 96 | .project 97 | .pydevproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | env/* 103 | 104 | # mypy 105 | .mypy_cache/ 106 | .dmypy.json 107 | dmypy.json 108 | 109 | # Pyre type checker 110 | .pyre/ 111 | 112 | ### VisualStudioCode ### 113 | .vscode/* 114 | !.vscode/tasks.json 115 | !.vscode/launch.json 116 | !.vscode/extensions.json 117 | 118 | ### VisualStudioCode Patch ### 119 | # Ignore all local history of files 120 | .history 121 | 122 | # End of https://www.gitignore.io/api/python,visualstudiocode 123 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: "3.6" 3 | 4 | cache: pip 5 | 6 | install: 7 | - pip install -r requirements.txt 8 | 9 | addons: 10 | sonarcloud: 11 | organization: "ronek22-github" 12 | 13 | script: 14 | - pytest --cov-config .coveragerc --cov-report xml:coverage.xml --cov=running_calculator tests/ 15 | - pytest --cov-config .coveragerc --cov=running_calculator tests/ 16 | - sonar-scanner 17 | 18 | after_success: 19 | - codecov 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![sonar](https://sonarcloud.io/api/project_badges/quality_gate?project=ronek22_runningCalculator)](https://sonarcloud.io/dashboard?id=ronek22_runningCalculator) 2 | 3 | [![DeepSource](https://static.deepsource.io/deepsource-badge-light-mini.svg)](https://deepsource.io/gh/ronek22/runningCalculator/?ref=repository-badge) 4 | [![Build Status](https://travis-ci.org/ronek22/runningCalculator.svg?branch=master)](https://travis-ci.org/ronek22/runningCalculator) 5 | [![Maintainability](https://api.codeclimate.com/v1/badges/8da171e47d7d47e95c0a/maintainability)](https://codeclimate.com/github/ronek22/runningCalculator/maintainability) 6 | [![BCH compliance](https://bettercodehub.com/edge/badge/ronek22/runningCalculator?branch=master)](https://bettercodehub.com/) 7 | [![codecov](https://codecov.io/gh/ronek22/runningCalculator/branch/master/graph/badge.svg)](https://codecov.io/gh/ronek22/runningCalculator) 8 | 9 | 10 | # runningCalculator 11 | Application allows us to calculate: 12 | * How long should be long run depending on weekly mileage (formula from Jack Daniels book) 13 | * pace (from time & distance) 14 | * time (from distance & pace) calculate (pace, time,long run, vdot, paces for vdot) 15 | * table with paces according to Jack Daniels book, depending on latest race time 16 | 17 | ## Requirements 18 | I recommend to create virtual environment for this project, then you can easily install all requirements with following command 19 | ``` 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | # Test online 24 | You can test it online [here.](https://repl.it/@ronek22/runningCalculator) 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numexpr==2.6.5 2 | numpy==1.14.5 3 | pandas==0.23.2 4 | python-dateutil==2.7.3 5 | pytz==2018.5 6 | six==1.11.0 7 | pytest 8 | pytest-cov 9 | coverage 10 | codecov -------------------------------------------------------------------------------- /running_calculator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronek22/runningCalculator/dfd5172f4baac4244b24c83e4a999273f47374d3/running_calculator/__init__.py -------------------------------------------------------------------------------- /running_calculator/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronek22/runningCalculator/dfd5172f4baac4244b24c83e4a999273f47374d3/running_calculator/core/__init__.py -------------------------------------------------------------------------------- /running_calculator/core/calculator.py: -------------------------------------------------------------------------------- 1 | """Main script for basic calculation functions for program""" 2 | from .utils import time_to_number, time_to_string, pace_to_number 3 | 4 | 5 | def long_run(mileage, pace): 6 | """Calculate long run distance by Jack Daniels formula""" 7 | calculated_pace = pace_to_number(pace) 8 | calculated_distance = (mileage / 4) / (0.75) 9 | 10 | time = int(calculated_distance * calculated_pace) 11 | 12 | if time > 120: 13 | return "Run in comfortable way for 2 hours." 14 | return """ 15 | Your long run in this week should be: {0:.2f} km 16 | Probably your run will takes: {1} minutes 17 | """.format(calculated_distance, time) 18 | 19 | 20 | def calc_pace(time, distance): 21 | """Calculate pace from given time and distance""" 22 | pace = time_to_number(time) / distance 23 | minutes = int(pace) 24 | seconds = int((pace - minutes) * 60) 25 | 26 | if seconds > 9: 27 | return "If you ran {0:.2f} km in {1}, your pace will be {2}:{3} min/km"\ 28 | .format(distance, time, minutes, seconds) 29 | return "If you ran {0:.2f} km in {1}, your pace will be {2}:0{3} min/km"\ 30 | .format(distance, time, minutes, seconds) 31 | 32 | 33 | def calc_time(pace, distance): 34 | """Calculate pace from given pace and distance""" 35 | return time_to_string(pace_to_number(pace) * distance) 36 | -------------------------------------------------------------------------------- /running_calculator/core/constants.py: -------------------------------------------------------------------------------- 1 | """Script for storing constants variables""" 2 | MENU_TEXT = """\nRunning Calculator 3 | {0} 4 | 1. Long run distance calculation. 5 | 2. Distance & Time -> Pace. 6 | 3. Pace & Distance -> Time. 7 | 4. VDOT calculations & Provide paces. 8 | 5. Exit 9 | {0}""".format("=" * 50) 10 | 11 | LONG_RUN_TEXT = """ 12 | Long run is run in comfortable pace, 13 | It maintain your endurance, 14 | According to Jack Daniels program, 15 | run shouldn't last longer than 120 minutes 16 | or shouldn't have more than 25% of weekly training volume. 17 | """ 18 | 19 | DISTANCES = """ 20 | 1. 1500m 21 | 2. 1600m 22 | 3. 3km 23 | 4. 3200m 24 | 5. 5km 25 | 6. 10km 26 | 7. 15km 27 | 8. Half Marathon 28 | 9. Marathon 29 | """ 30 | 31 | DIST_DIC = { 32 | 1: '1500m', 33 | 2: '1600m', 34 | 3: '3km', 35 | 4: '3200m', 36 | 5: '5km', 37 | 6: '10km', 38 | 7: '15km', 39 | 8: 'HM', 40 | 9: 'M' 41 | } 42 | -------------------------------------------------------------------------------- /running_calculator/core/control.py: -------------------------------------------------------------------------------- 1 | """Script to control user interaction like input and output""" 2 | from .calculator import long_run, calc_pace, calc_time 3 | from .constants import LONG_RUN_TEXT, MENU_TEXT 4 | from .utils import cls, pause_end, get_distance, get_time 5 | 6 | 7 | def enter_long_run_data(): 8 | """Interact with user to provide long run instructions""" 9 | print(LONG_RUN_TEXT) 10 | 11 | distance = float(input("How many kilometres do you run in this week?\n>> ")) 12 | pace = input("What is your long run pace?\n>> ") 13 | 14 | print(long_run(distance, pace)) 15 | 16 | 17 | def enter_data_for_pace_calc(): 18 | """Interact with user to calculate pace""" 19 | print("Distance & Time -> Pace") 20 | print("=" * 50) 21 | 22 | distance = float(input("Distance[km]: ")) 23 | time = input("Time[hh:mm:ss]: ") 24 | 25 | print(calc_pace(time, distance)) 26 | 27 | 28 | def enter_data_for_time_calc(): 29 | """Interact with user to calculate time""" 30 | print("Pace & Distance -> Time") 31 | print("=" * 50) 32 | 33 | pace = input("Pace[min/km]: ") 34 | distance = float(input("Distance[km]: ")) 35 | 36 | calc_time(pace, distance) 37 | 38 | 39 | def enter_data_to_running_index(): 40 | """Provide data to running index calculate function""" 41 | print('{:~^20}'.format('VDOT Table')) 42 | return get_distance(), get_time() 43 | 44 | 45 | def enter_into_running_index_mode(): 46 | """Interact with user to provide running index data""" 47 | from .running_index import RunningIndex 48 | analysis = RunningIndex() 49 | distance, time = enter_data_to_running_index() 50 | analysis.calculate(distance, time) 51 | 52 | 53 | def close_program(): 54 | """Print exit message and close""" 55 | print("End with Calculations\nSee u later :).") 56 | exit(0) 57 | 58 | 59 | def menu(): 60 | """ Main function of program, at startup 61 | it displays menu, waits for input and controls flow of application""" 62 | menu_options = [enter_long_run_data, enter_data_for_pace_calc, enter_data_for_time_calc, 63 | enter_into_running_index_mode, close_program] 64 | while True: 65 | cls() 66 | print(MENU_TEXT) 67 | choose = int(input("What do you want to calculate?\n>> ")) 68 | cls() 69 | 70 | if 1 <= choose <= 5: 71 | menu_options[choose-1]() 72 | else: 73 | print("That option doesnt exist. Try again.") 74 | pause_end() 75 | -------------------------------------------------------------------------------- /running_calculator/core/running_index.py: -------------------------------------------------------------------------------- 1 | """Implement VDOT calculation and print training&racing paces 2 | Required files: daniels_table_races.csv & paces.csv""" 3 | 4 | import os 5 | import pandas as pd 6 | pd.set_option('display.expand_frame_repr', False) 7 | 8 | 9 | class RunningIndex: 10 | """Something like singleton class for calculate 11 | running index in efficient way""" 12 | def __init__(self): 13 | self.data_folder = os.path.abspath(os.path.dirname(__file__)) 14 | self.vdot = self.__prepare_table() 15 | self.paces_tab = pd.read_csv(os.path.join(self.data_folder, '..//data//paces.csv'), 16 | delimiter=',', index_col='Params') 17 | 18 | def __prepare_table(self): 19 | """Read the csv table and convert all time columns to timedelta""" 20 | timetable = pd.read_csv(os.path.join(self.data_folder, '..//data//daniels_table_races.csv'), 21 | delimiter=',', index_col='Params') 22 | for column in list(timetable): 23 | timetable[column] = timetable[column].apply(pd.Timedelta) 24 | return timetable 25 | 26 | def nearest(self, target, distance): 27 | """Return index(VDOT coefficient) of closest time in given distance""" 28 | row = min(self.vdot[distance], key=lambda x: abs(x - target)) 29 | return self.vdot.loc[self.vdot[distance] == row].index.item() 30 | 31 | def __print_results(self, ind): 32 | print("YOUR VDOT IS: ", ind, "\n") 33 | print('{:~^20}'.format('PACES')) 34 | print(self.paces_tab.ix[ind].to_frame()) 35 | print("") 36 | print('{:~^20}'.format('RACING TIMES')) 37 | print(self.vdot.ix[ind].to_frame().T) 38 | print("") 39 | 40 | def calculate(self, distance, time): 41 | """Main method for running_index class 42 | that print results from calculations""" 43 | coefficient = self.nearest(time, distance) 44 | self.__print_results(coefficient) 45 | -------------------------------------------------------------------------------- /running_calculator/core/utils.py: -------------------------------------------------------------------------------- 1 | """This script contains utility functions used in other scripts""" 2 | import os 3 | from datetime import timedelta 4 | from .constants import DIST_DIC, DISTANCES 5 | 6 | 7 | def pace_to_number(pace): 8 | """return from pace in string to pace in minutes""" 9 | minutes, seconds = pace.split(':') 10 | return int(minutes) + float(seconds) / 60 11 | 12 | 13 | def time_to_number(time): 14 | """return time from string to minutes(float)""" 15 | hours, minutes, seconds = time.split(':') 16 | hours, minutes, seconds = int(hours) * 60, int(minutes), float(seconds) / 60 17 | return hours + minutes + seconds 18 | 19 | 20 | def time_to_string(time): 21 | """convert from time in minutes to hh:mm:ss and print it out""" 22 | hours = int(time / 60) 23 | minutes = int(time % 60) 24 | seconds = (time - int(time)) * 60 25 | print("%02d:%02d:%02d" % (hours, minutes, seconds)) 26 | 27 | 28 | def duration(time): 29 | """Convert time from string to timedelta""" 30 | hour, minute, second = list(map(int, time.split(":"))) 31 | return timedelta(hours=hour, minutes=minute, seconds=second) 32 | 33 | 34 | def get_distance(): 35 | """Function for handle distance input by user 36 | and return mapped data from DIST_DIC dictionary""" 37 | distance = int(input("Choose distance:" + DISTANCES + "\n>> ")) 38 | return DIST_DIC[distance] 39 | 40 | 41 | def get_time(): 42 | """Function for handle time input by user 43 | and return it in timedelta format""" 44 | time = input("Provide your time: ") 45 | return duration(time) 46 | 47 | 48 | def pause_end(): 49 | """control app: wait for input""" 50 | input("Press any key to back to menu.") 51 | 52 | 53 | def cls(): 54 | """Clear screen depending of os""" 55 | os.system('cls' if os.name == 'nt' else 'clear') 56 | -------------------------------------------------------------------------------- /running_calculator/data/daniels_table_races.csv: -------------------------------------------------------------------------------- 1 | Params,1500m,1600m,3km,3200m,5km,10km,15km,HM,M 2 | 30,00:08:30,00:09:11,00:17:56,00:19:19,00:30:40,01:03:46,01:38:14,02:21:04,04:49:17 3 | 31,00:08:15,00:08:55,00:17:27,00:18:48,00:29:51,01:02:03,01:35:36,02:17:21,04:41:57 4 | 32,00:08:02,00:08:41,00:16:59,00:18:18,00:29:05,01:00:26,01:33:07,02:13:49,04:34:59 5 | 33,00:07:49,00:08:27,00:16:33,00:17:50,00:28:21,00:58:54,01:30:45,02:10:27,04:28:22 6 | 34,00:07:37,00:08:14,00:16:09,00:17:24,00:27:39,00:57:26,01:28:30,02:07:16,04:22:03 7 | 35,00:07:25,00:08:01,00:15:45,00:16:58,00:27:00,00:56:03,01:26:22,02:04:13,04:16:03 8 | 36,00:07:14,00:07:49,00:15:23,00:16:34,00:26:22,00:54:44,01:24:20,02:01:19,04:10:19 9 | 37,00:07:04,00:07:38,00:15:01,00:16:11,00:25:46,00:53:29,01:22:24,01:58:34,04:04:50 10 | 38,00:06:54,00:07:27,00:14:41,00:15:49,00:25:12,00:52:17,01:20:33,01:55:55,03:59:35 11 | 39,00:06:44,00:07:17,00:14:21,00:15:29,00:24:39,00:51:09,01:18:47,01:53:24,03:54:34 12 | 40,00:06:35,00:07:07,00:14:03,00:15:08,00:24:08,00:50:03,01:17:06,01:50:59,03:49:45 13 | 41,00:06:27,00:06:58,00:13:45,00:14:49,00:23:38,00:49:01,01:15:29,01:48:40,03:45:09 14 | 42,00:06:19,00:06:49,00:13:28,00:14:31,00:23:09,00:48:01,01:13:56,01:46:27,03:40:43 15 | 43,00:06:11,00:06:41,00:13:11,00:14:13,00:22:41,00:47:04,01:12:27,01:44:20,03:36:28 16 | 44,00:06:03,00:06:32,00:12:55,00:13:56,00:22:15,00:46:09,01:11:02,01:42:17,03:32:23 17 | 45,00:05:56,00:06:25,00:12:40,00:13:40,00:21:50,00:45:16,01:09:40,01:40:20,03:28:26 18 | 46,00:05:49,00:06:17,00:12:26,00:13:25,00:21:25,00:44:25,01:08:22,01:38:27,03:24:39 19 | 47,00:05:42,00:06:10,00:12:12,00:13:10,00:21:02,00:43:36,01:07:06,01:36:38,03:21:00 20 | 48,00:05:36,00:06:03,00:11:58,00:12:55,00:20:39,00:42:50,01:05:53,01:34:53,03:17:29 21 | 49,00:05:30,00:05:56,00:11:45,00:12:41,00:20:18,00:42:04,01:04:44,01:33:12,03:14:06 22 | 50,00:05:24,00:05:50,00:11:33,00:12:28,00:19:57,00:41:21,01:03:36,01:31:35,03:10:49 23 | 51,00:05:18,00:05:44,00:11:21,00:12:15,00:19:36,00:40:39,01:02:31,01:30:02,03:07:39 24 | 52,00:05:13,00:05:38,00:11:09,00:12:02,00:19:17,00:39:59,01:01:29,01:28:31,03:04:36 25 | 53,00:05:07,00:05:32,00:10:58,00:11:50,00:18:58,00:39:20,01:00:28,01:27:04,03:01:39 26 | 54,00:05:02,00:05:27,00:10:47,00:11:39,00:18:40,00:38:42,00:59:30,01:25:40,02:58:47 27 | 55,00:04:57,00:05:21,00:10:37,00:11:28,00:18:22,00:38:06,00:58:33,01:24:18,02:56:01 28 | 56,00:04:53,00:05:16,00:10:27,00:11:17,00:18:05,00:37:31,00:57:39,01:23:00,02:53:20 29 | 57,00:04:48,00:05:11,00:10:17,00:11:06,00:17:49,00:36:57,00:56:46,01:21:43,02:50:45 30 | 58,00:04:44,00:05:06,00:10:08,00:10:56,00:17:33,00:36:24,00:55:55,01:20:30,02:48:14 31 | 59,00:04:39,00:05:02,00:09:58,00:10:46,00:17:17,00:35:52,00:55:06,01:19:18,02:45:47 32 | 60,00:04:35,00:04:57,00:09:50,00:10:37,00:17:03,00:35:22,00:54:18,01:18:09,02:43:25 33 | 61,00:04:31,00:04:53,00:09:41,00:10:27,00:16:48,00:34:52,00:53:32,01:17:02,02:41:08 34 | 62,00:04:27,00:04:49,00:09:33,00:10:18,00:16:34,00:34:23,00:52:47,01:15:57,02:38:54 35 | 63,00:04:24,00:04:45,00:09:25,00:10:10,00:16:20,00:33:55,00:52:03,01:14:54,02:36:44 36 | 64,00:04:20,00:04:41,00:09:17,00:10:01,00:16:07,00:33:28,00:51:21,01:13:53,02:34:38 37 | 65,00:04:16,00:04:37,00:09:09,00:09:53,00:15:54,00:33:01,00:50:40,01:12:53,02:32:35 38 | 66,00:04:13,00:04:33,00:09:02,00:09:45,00:15:42,00:32:35,00:50:00,01:11:56,02:30:36 39 | 67,00:04:10,00:04:30,00:08:55,00:09:37,00:15:29,00:32:11,00:49:22,01:11:00,02:28:40 40 | 68,00:04:06,00:04:26,00:08:48,00:09:30,00:15:18,00:31:46,00:48:44,01:10:05,02:26:47 41 | 69,00:04:03,00:04:23,00:08:41,00:09:23,00:15:06,00:31:23,00:48:08,01:09:12,02:24:57 42 | 70,00:04:00,00:04:19,00:08:34,00:09:16,00:14:55,00:31:00,00:47:32,01:08:21,02:23:10 43 | 71,00:03:57,00:04:16,00:08:28,00:09:09,00:14:44,00:30:38,00:46:58,01:07:31,02:21:26 44 | 72,00:03:54,00:04:13,00:08:22,00:09:02,00:14:33,00:30:16,00:46:24,01:06:42,02:19:44 45 | 73,00:03:52,00:04:10,00:08:16,00:08:55,00:14:23,00:29:55,00:45:51,01:05:54,02:18:05 46 | 74,00:03:49,00:04:07,00:08:10,00:08:49,00:14:13,00:29:34,00:45:19,01:05:08,02:16:29 47 | 75,00:03:46,00:04:04,00:08:04,00:08:43,00:14:03,00:29:14,00:44:48,01:04:23,02:14:55 48 | 76,00:03:44,00:04:02,00:07:58,00:08:37,00:13:54,00:28:55,00:44:18,01:03:39,02:13:23 49 | 77,00:03:41,00:03:58,00:07:53,00:08:31,00:13:44,00:28:36,00:43:49,01:02:56,02:11:54 50 | 78,00:03:38,00:03:56,00:07:48,00:08:25,00:13:35,00:28:17,00:43:20,01:02:15,02:10:27 51 | 79,00:03:36,00:03:53,00:07:43,00:08:20,00:13:26,00:27:59,00:42:52,01:01:34,02:09:02 52 | 80,00:03:34,00:03:51,00:07:37,00:08:14,00:13:17,00:27:41,00:42:25,01:00:54,02:07:38 53 | 81,00:03:31,00:03:48,00:07:32,00:08:08,00:13:09,00:27:24,00:41:58,01:00:15,02:06:17 54 | 82,00:03:29,00:03:46,00:07:27,00:08:03,00:13:01,00:27:07,00:41:32,00:59:38,02:04:57 55 | 83,00:03:27,00:03:44,00:07:23,00:07:58,00:12:53,00:26:51,00:41:06,00:59:01,02:03:40 56 | 84,00:03:25,00:03:41,00:07:18,00:07:53,00:12:45,00:26:34,00:40:42,00:58:25,02:02:24 57 | 85,00:03:23,00:03:39,00:07:14,00:07:48,00:12:37,00:26:19,00:40:17,00:57:50,02:01:10 -------------------------------------------------------------------------------- /running_calculator/data/paces.csv: -------------------------------------------------------------------------------- 1 | Params,Easy Km,M Km,T 400m,T Km,I 400m,I Km,I 1200m,I Mile,R 200m,R 300m,R 400m,R 600m,R 800m 2 | 30,7:27-8:14,7:03,2:33,6:24,2:22,—,—,—,67,1:41,—,—,— 3 | 31,7:16-8:02,6:52,2:30,6:14,2:18,—,—,—,65,98,—,—,— 4 | 32,7:05-7:52,6:40,2:26,6:05,2:14,—,—,—,63,95,—,—,— 5 | 33,6:55-7:41,6:30,2:23,5:56,2:11,—,—,—,61,92,—,—,— 6 | 34,6:45-7:31,6:20,2:19,5:48,2:08,—,—,—,60,90,2:00,—,— 7 | 35,6:36-7:21,6:10,2:16,5:40,2:05,—,—,—,58,87,1:57,—,— 8 | 36,6:27-7:11,6:01,2:13,5:33,2:02,—,—,—,57,85,1:54,—,— 9 | 37,6:19-7:02,5:53,2:10,5:26,1:59,5:00,—,—,55,83,1:51,—,— 10 | 38,6:11-6:54,5:45,2:07,5:19,1:56,4:54,—,—,54,81,1:48,—,— 11 | 39,6:03-6:46,5:37,2:05,5:12,1:54,4:48,—,—,53,80,1:46,—,— 12 | 40,5:56-6:38,5:29,2:02,5:06,1:52,4:42,—,—,52,78,1:44,—,— 13 | 41,5:49-6:31,5:22,2:00,5:00,1:50,4:36,—,—,51,77,1:42,—,— 14 | 42,5:42-6:23,5:16,1:57,4:54,1:48,4:31,—,—,50,75,1:40,—,— 15 | 43,5:35-6:16,5:09,1:55,4:49,1:46,4:26,—,—,49,74,98,—,— 16 | 44,5:29-6:10,5:03,1:53,4:43,1:44,4:21,—,—,48,72,96,—,— 17 | 45,5:23-6:03,4:57,1:51,4:38,1:42,4:16,—,—,47,71,94,—,— 18 | 46,5:17-5:57,4:51,1:49,4:33,1:40,4:12,5:00,—,46,69,92,—,— 19 | 47,5:12-5:51,4:46,1:47,4:29,98,4:07,4:54,—,45,68,90,—,— 20 | 48,5:07-5:45,4:41,1:45,4:24,96,4:03,4:49,—,44,67,89,—,— 21 | 49,5:01-5:40,4:36,1:43,4:20,95,3:59,4:45,—,44,66,88,—,— 22 | 50,4:56-5:34,4:31,1:41,4:15,93,3:55,4:40,—,43,65,87,—,— 23 | 51,4:52-5:29,4:27,1:40,4:11,92,3:51,4:36,—,43,64,86,—,— 24 | 52,4:47-5:24,4:22,98,4:07,91,3:48,4:32,—,42,64,85,—,— 25 | 53,4:43-5:19,4:18,97,4:04,90,3:44,4:29,—,42,63,84,—,— 26 | 54,4:38-5:14,4:14,95,4:00,88,3:41,4:25,—,41,62,82,—,— 27 | 55,4:34-5:10,4:10,94,3:56,87,3:37,4:21,—,40,61,81,—,— 28 | 56,4:30-5:05,4:06,93,3:53,86,3:34,4:18,—,40,60,80,2:00,— 29 | 57,4:26-5:01,4:03,91,3:50,85,3:31,4:14,—,39,59,79,1:57,— 30 | 58,4:22-4:57,3:59,90,3:46,83,3:28,4:10,—,38,58,77,1:55,— 31 | 59,4:19-4:53,3:56,89,3:43,82,3:25,4:07,—,38,57,76,1:54,— 32 | 60,4:15-4:49,3:52,88,3:40,81,3:23,4:03,—,37,56,75,1:52,— 33 | 61,4:11-4:45,3:49,86,3:37,80,3:20,4:00,—,37,55,74,1:51,— 34 | 62,4:08-4:41,3:46,85,3:34,79,3:17,3:57,—,36,54,73,1:49,— 35 | 63,4:05-4:38,3:43,84,3:32,78,3:15,3:54,—,36,53,72,1:48,— 36 | 64,4:02-4:34,3:40,83,3:29,77,3:12,3:51,—,35,52,71,1:46,— 37 | 65,3:59-4:31,3:37,82,3:26,76,3:10,3:48,—,35,52,70,1:45,— 38 | 66,3:56-4:28,3:34,81,3:24,75,3:08,3:45,5:00,34,51,69,1:43,— 39 | 67,3:53-4:24,3:31,80,3:21,74,3:05,3:42,4:57,34,51,68,1:42,— 40 | 68,3:50-4:21,3:29,79,3:19,73,3:03,3:39,4:53,33,50,67,1:40,— 41 | 69,3:47-4:18,3:26,78,3:16,72,3:01,3:36,4:50,33,49,66,99,— 42 | 70,3:44-4:15,3:24,77,3:14,71,2:59,3:34,4:46,32,48,65,97,— 43 | 71,3:42-4:12,3:21,76,3:12,70,2:57,3:31,4:43,32,48,64,96,— 44 | 72,3:40-4:00,3:19,76,3:10,69,2:55,3:29,4:40,31,47,63,94,— 45 | 73,3:37-4:07,3:16,75,3:08,69,2:53,3:27,4:37,31,47,63,93,— 46 | 74,3:34-4:04,3:14,74,3:06,68,2:51,3:25,4:34,31,46,62,92,— 47 | 75,3:32-4:01,3:12,74,3:04,67,2:49,3:22,4:31,30,46,61,91,— 48 | 76,3:30-3:58,3:10,73,3:02,66,2:48,3:20,4:28,30,45,60,90,— 49 | 77,3:28-3:56,3:08,72,3:00,65,2:46,3:18,4:25,29,45,59,89,2:00 50 | 78,3:25-3:53,3:06,71,2:58,65,2:44,3:16,4:23,29,44,59,88,1:59 51 | 79,3:23-3:51,3:03,70,2:56,64,2:42,3:14,4:20,29,44,58,87,1:58 52 | 80,3:21-3:49,3:01,70,2:54,64,2:41,3:12,4:17,29,43,58,87,1:56 53 | 81,3:19-3:46,3:00,69,2:53,63,2:39,3:10,4:15,28,43,57,86,1:55 54 | 82,3:17-3:44,2:58,68,2:51,62,2:38,3:08,4:12,28,42,56,85,1:54 55 | 83,3:15-3:42,2:56,68,2:49,62,2:36,3:07,4:10,28,42,56,84,1:53 56 | 84,3:13-3:40,2:54,67,2:48,61,2:35,3:05,4:08,27,41,55,83,1:52 57 | 85,3:11-3:38,2:52,66,2:46,61,2:33,3:03,4:05,27,41,55,82,1:51 58 | -------------------------------------------------------------------------------- /running_calculator/running_calculator.py: -------------------------------------------------------------------------------- 1 | """Running calculator, that calculates (pace, time,long run, vdot, paces for vdot""" 2 | from core.control import menu 3 | 4 | if __name__ == '__main__': 5 | menu() 6 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # must be unique in a given SonarQube instance 2 | sonar.projectKey=ronek22_runningCalculator 3 | sonar.organization=ronek22-github 4 | sonar.sources=. 5 | sonar.language=py 6 | 7 | sonar.python.pylint=/usr/local/bin/pylint 8 | 9 | 10 | sonar.sourceEncoding=UTF-8 11 | sonar.host.url=https://sonarcloud.io 12 | sonar.python.coverage.reportPath=coverage.xml 13 | sonar.exclusions=tests/**,running_calculator/core/control.py 14 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronek22/runningCalculator/dfd5172f4baac4244b24c83e4a999273f47374d3/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_calculator.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from running_calculator.core.calculator import long_run, calc_pace, calc_time 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "mileage, pace, out", [ 8 | (50, "05:00", """ 9 | Your long run in this week should be: 16.67 km 10 | Probably your run will takes: 83 minutes 11 | """), 12 | (45, "04:30", """ 13 | Your long run in this week should be: 15.00 km 14 | Probably your run will takes: 67 minutes 15 | """), 16 | (70, "03:50", """ 17 | Your long run in this week should be: 23.33 km 18 | Probably your run will takes: 89 minutes 19 | """), 20 | ] 21 | ) 22 | def test_long_run_under120(mileage, pace, out): 23 | assert long_run(mileage, pace) == out 24 | 25 | 26 | def test_long_run_above120(): 27 | assert long_run(120, "5:00") == "Run in comfortable way for 2 hours." 28 | 29 | 30 | def test_calc_pace(): 31 | assert calc_pace("00:20:00", 5) == "If you ran 5.00 km in 00:20:00, your pace will be 4:00 min/km" 32 | 33 | 34 | def test_calc_pace_with_added_zero(): 35 | assert calc_pace("00:35:00", 10) == "If you ran 10.00 km in 00:35:00, your pace will be 3:30 min/km" 36 | 37 | 38 | def test_calc_time(capfd): 39 | calc_time("03:30", 10) 40 | out, _ = capfd.readouterr() 41 | assert out == "00:35:00\n" 42 | -------------------------------------------------------------------------------- /tests/test_running_index.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from running_calculator.core.running_index import RunningIndex 4 | 5 | 6 | class TestRunningIndex: 7 | analyzer = RunningIndex() 8 | 9 | @pytest.mark.parametrize( 10 | "target, distance, index", [ 11 | ("00:17:30", "5km", 58), 12 | ("00:45:44", "10km", 44) 13 | ] 14 | ) 15 | def test_nearest(self, target, distance, index): 16 | assert self.analyzer.nearest(target, distance) == index 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest import approx 3 | from datetime import timedelta 4 | 5 | from running_calculator.core.utils import pace_to_number, time_to_number, time_to_string, duration 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'pace_str, pace_fl', [ 10 | ("3:30", 3.50), 11 | ("5:00", 5.00), 12 | ("5:25", 5.41), 13 | ("6:17", 6.28) 14 | ] 15 | ) 16 | def test_pace_to_number(pace_str, pace_fl): 17 | assert pace_to_number(pace_str) == approx(pace_fl, rel=1e-2) 18 | 19 | 20 | @pytest.mark.parametrize( 21 | 'time_str, time_fl', [ 22 | ("00:20:00", 20.00), 23 | ("00:15:30", 15.50), 24 | ("01:35:45", 95.75), 25 | ("00:24:15", 24.25) 26 | ] 27 | ) 28 | def test_time_to_number(time_str, time_fl): 29 | assert time_to_number(time_str) == approx(time_fl, rel=1e-2) 30 | 31 | 32 | @pytest.mark.parametrize( 33 | 'badtime_str', [ 34 | "20:00", "30", "00:01:35:20", "24:10" 35 | ] 36 | ) 37 | def test_time_to_number_with_corrupted_input(badtime_str): 38 | with pytest.raises(ValueError) as error_info: 39 | time_to_number(badtime_str) 40 | assert 'to unpack' in str(error_info) 41 | 42 | 43 | @pytest.mark.parametrize( 44 | 'time_str, time_fl', [ 45 | ("00:20:00", 20.00), 46 | ("00:15:30", 15.50), 47 | ("01:35:45", 95.75), 48 | ("00:24:15", 24.25) 49 | ] 50 | ) 51 | def test_time_to_string(time_str, time_fl, capfd): 52 | minutes = time_fl 53 | time_to_string(minutes) 54 | out, _ = capfd.readouterr() 55 | assert out == time_str + "\n" 56 | 57 | 58 | def test_duration(): 59 | assert duration("01:15:25") == timedelta(hours=1, minutes=15, seconds=25) 60 | 61 | # modules with input require some complex mocking, so i do it later 62 | 63 | 64 | 65 | 66 | 67 | --------------------------------------------------------------------------------