реализованы все функции и основной сценарий
This commit is contained in:
commit
3bf10a7461
9
data/workouts.json
Normal file
9
data/workouts.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"date": "2026-04-28",
|
||||||
|
"type": "swimming",
|
||||||
|
"duration_min": 1200,
|
||||||
|
"distance_km": 2.0,
|
||||||
|
"calories": 120
|
||||||
|
}
|
||||||
|
]
|
||||||
154
fitness_tracker.py
Normal file
154
fitness_tracker.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def load_workouts(file_path: str) -> list:
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
workouts = json.load(file)
|
||||||
|
return workouts if isinstance(workouts, list) else []
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError, IOError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def filter_by_type(workouts: list, workout_type: str) -> list:
|
||||||
|
return [w for w in workouts if w.get('type') == workout_type]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_by_date_range(workouts: list, start_date: str, end_date: str) -> list:
|
||||||
|
return [w for w in workouts if start_date <= w.get('date', '') <= end_date]
|
||||||
|
|
||||||
|
|
||||||
|
def total_calories(workouts: list) -> int:
|
||||||
|
return sum(w.get('calories', 0) for w in workouts)
|
||||||
|
|
||||||
|
|
||||||
|
def average_duration(workouts: list) -> float:
|
||||||
|
if not workouts:
|
||||||
|
return 0.0
|
||||||
|
total_duration = sum(w.get('duration_min', 0) for w in workouts)
|
||||||
|
return total_duration / len(workouts)
|
||||||
|
|
||||||
|
|
||||||
|
def best_distance(workouts: list) -> Optional[dict]:
|
||||||
|
distance_workouts = [w for w in workouts if w.get('distance_km', 0) > 0]
|
||||||
|
|
||||||
|
if not distance_workouts:
|
||||||
|
return None
|
||||||
|
|
||||||
|
best = max(distance_workouts, key=lambda x: x.get('distance_km', 0))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'date': best.get('date'),
|
||||||
|
'type': best.get('type'),
|
||||||
|
'distance_km': best.get('distance_km')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def calories_per_minute(workout: dict) -> float:
|
||||||
|
duration = workout.get('duration_min', 0)
|
||||||
|
calories = workout.get('calories', 0)
|
||||||
|
|
||||||
|
if duration == 0:
|
||||||
|
return 0.0
|
||||||
|
return calories / duration
|
||||||
|
|
||||||
|
|
||||||
|
def add_workout(workouts: list, date: str, workout_type: str, duration_min: int,
|
||||||
|
distance_km: float, calories: int, avg_heart_rate: int = None) -> list:
|
||||||
|
valid_types = ['running', 'swimming', 'cycling', 'strength']
|
||||||
|
|
||||||
|
if workout_type not in valid_types:
|
||||||
|
raise ValueError(f"Неверный тип тренировки. Допустимые: {valid_types}")
|
||||||
|
|
||||||
|
if duration_min <= 0:
|
||||||
|
raise ValueError("Длительность должна быть больше 0")
|
||||||
|
|
||||||
|
if calories <= 0:
|
||||||
|
raise ValueError("Калории должны быть больше 0")
|
||||||
|
|
||||||
|
if distance_km < 0:
|
||||||
|
raise ValueError("Дистанция не может быть отрицательной")
|
||||||
|
|
||||||
|
workout = {
|
||||||
|
'date': date,
|
||||||
|
'type': workout_type,
|
||||||
|
'duration_min': duration_min,
|
||||||
|
'distance_km': distance_km,
|
||||||
|
'calories': calories
|
||||||
|
}
|
||||||
|
|
||||||
|
if avg_heart_rate is not None:
|
||||||
|
workout['avg_heart_rate'] = avg_heart_rate
|
||||||
|
|
||||||
|
workouts.append(workout)
|
||||||
|
return workouts
|
||||||
|
|
||||||
|
|
||||||
|
def get_heart_rate_zones(workouts: list) -> dict:
|
||||||
|
zones = {
|
||||||
|
'low': 0,
|
||||||
|
'moderate': 0,
|
||||||
|
'high': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for workout in workouts:
|
||||||
|
heart_rate = workout.get('avg_heart_rate')
|
||||||
|
if heart_rate is not None:
|
||||||
|
if heart_rate < 120:
|
||||||
|
zones['low'] += 1
|
||||||
|
elif heart_rate <= 150:
|
||||||
|
zones['moderate'] += 1
|
||||||
|
else:
|
||||||
|
zones['high'] += 1
|
||||||
|
|
||||||
|
return zones
|
||||||
|
|
||||||
|
|
||||||
|
def generate_monthly_report(workouts: list, year: int, month: int) -> dict:
|
||||||
|
month_workouts = []
|
||||||
|
for w in workouts:
|
||||||
|
date = w.get('date', '')
|
||||||
|
if date and date.startswith(f"{year}-{month:02d}"):
|
||||||
|
month_workouts.append(w)
|
||||||
|
|
||||||
|
if not month_workouts:
|
||||||
|
return {
|
||||||
|
'total_workouts': 0,
|
||||||
|
'total_calories': 0,
|
||||||
|
'total_distance_km': 0.0,
|
||||||
|
'avg_duration_min': 0.0,
|
||||||
|
'workouts_by_type': {},
|
||||||
|
'most_frequent_type': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
total_workouts = len(month_workouts)
|
||||||
|
total_calories = sum(w.get('calories', 0) for w in month_workouts)
|
||||||
|
|
||||||
|
distance_types = ['running', 'swimming', 'cycling']
|
||||||
|
total_distance = sum(w.get('distance_km', 0) for w in month_workouts
|
||||||
|
if w.get('type') in distance_types)
|
||||||
|
|
||||||
|
avg_duration = sum(w.get('duration_min', 0) for w in month_workouts) / total_workouts
|
||||||
|
|
||||||
|
workouts_by_type = {}
|
||||||
|
for w in month_workouts:
|
||||||
|
w_type = w.get('type')
|
||||||
|
workouts_by_type[w_type] = workouts_by_type.get(w_type, 0) + 1
|
||||||
|
|
||||||
|
if workouts_by_type:
|
||||||
|
max_count = max(workouts_by_type.values())
|
||||||
|
most_frequent = [t for t, c in workouts_by_type.items() if c == max_count]
|
||||||
|
most_frequent_type = sorted(most_frequent)[0]
|
||||||
|
else:
|
||||||
|
most_frequent_type = ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_workouts': total_workouts,
|
||||||
|
'total_calories': total_calories,
|
||||||
|
'total_distance_km': round(total_distance, 2),
|
||||||
|
'avg_duration_min': round(avg_duration, 1),
|
||||||
|
'workouts_by_type': workouts_by_type,
|
||||||
|
'most_frequent_type': most_frequent_type
|
||||||
|
}
|
||||||
175
main.py
Normal file
175
main.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
from fitness_tracker import *
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def save_workouts(file_path: str, workouts: list) -> None:
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||||
|
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as file:
|
||||||
|
json.dump(workouts, file, indent=2, ensure_ascii=False)
|
||||||
|
print(f"\nДанные сохранены в {file_path}")
|
||||||
|
except IOError as e:
|
||||||
|
print(f"\nОшибка при сохранении: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("ФИТНЕС ТРЕКЕР - АНАЛИЗ ТРЕНИРОВОК")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
FILE_PATH = "data/workouts.json"
|
||||||
|
|
||||||
|
workouts = load_workouts(FILE_PATH)
|
||||||
|
print(f"\nЗагружено {len(workouts)} тренировок")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("ОБЩАЯ СТАТИСТИКА")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print(f"\nВсего тренировок: {len(workouts)}")
|
||||||
|
|
||||||
|
total_cals = total_calories(workouts)
|
||||||
|
print(f"Всего калорий: {total_cals}")
|
||||||
|
|
||||||
|
avg_dur = average_duration(workouts)
|
||||||
|
print(f"Средняя длительность: {avg_dur:.1f} минут")
|
||||||
|
|
||||||
|
best = best_distance(workouts)
|
||||||
|
if best:
|
||||||
|
print(f"Лучшая дистанция: {best['distance_km']} км ({best['type']}, {best['date']})")
|
||||||
|
else:
|
||||||
|
print("Лучшая дистанция: нет тренировок с дистанцией")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("ФИЛЬТРАЦИЯ ПО ТИПУ ТРЕНИРОВКИ")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
valid_types = ['running', 'swimming', 'cycling', 'strength']
|
||||||
|
while True:
|
||||||
|
workout_type = input("\nВведите тип тренировки (running/swimming/cycling/strength): ").lower()
|
||||||
|
if workout_type in valid_types:
|
||||||
|
break
|
||||||
|
print(f"Неверный тип. Допустимые: {valid_types}")
|
||||||
|
|
||||||
|
filtered = filter_by_type(workouts, workout_type)
|
||||||
|
print(f"\nТренировок типа '{workout_type}': {len(filtered)}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("МЕСЯЧНЫЙ ОТЧЁТ")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
year = int(input("\nВведите год (например, 2024): "))
|
||||||
|
month = int(input("Введите месяц (1-12): "))
|
||||||
|
if 1 <= month <= 12:
|
||||||
|
break
|
||||||
|
print("Месяц должен быть от 1 до 12")
|
||||||
|
except ValueError:
|
||||||
|
print("Введите целое число")
|
||||||
|
|
||||||
|
report = generate_monthly_report(workouts, year, month)
|
||||||
|
|
||||||
|
print(f"\nОТЧЁТ ЗА {year}-{month:02d}")
|
||||||
|
print("-" * 40)
|
||||||
|
print(f"Всего тренировок: {report['total_workouts']}")
|
||||||
|
print(f"Всего калорий: {report['total_calories']}")
|
||||||
|
print(f"Общая дистанция: {report['total_distance_km']} км")
|
||||||
|
print(f"Средняя длительность: {report['avg_duration_min']} мин")
|
||||||
|
print("\nРаспределение по типам:")
|
||||||
|
for w_type, count in report['workouts_by_type'].items():
|
||||||
|
print(f" {w_type}: {count}")
|
||||||
|
print(f"\nСамый частый тип: {report['most_frequent_type'] if report['most_frequent_type'] else 'нет данных'}")
|
||||||
|
|
||||||
|
month_workouts = [w for w in workouts if w.get('date', '').startswith(f"{year}-{month:02d}")]
|
||||||
|
hr_zones = get_heart_rate_zones(month_workouts)
|
||||||
|
|
||||||
|
print("\nПУЛЬСОВЫЕ ЗОНЫ (за месяц)")
|
||||||
|
print("-" * 40)
|
||||||
|
print(f"Низкая (< 120 bpm): {hr_zones['low']} тренировок")
|
||||||
|
print(f"Средняя (120-150 bpm): {hr_zones['moderate']} тренировок")
|
||||||
|
print(f"Высокая (> 150 bpm): {hr_zones['high']} тренировок")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("ДОБАВЛЕНИЕ НОВОЙ ТРЕНИРОВКИ")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
add_new = input("\nДобавить новую тренировку? (да/нет): ").lower()
|
||||||
|
|
||||||
|
if add_new in ['да', 'yes', 'y', 'д']:
|
||||||
|
print("\nВведите данные новой тренировки:")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
date = input("Дата (ГГГГ-ММ-ДД): ")
|
||||||
|
if len(date) == 10 and date[4] == '-' and date[7] == '-':
|
||||||
|
break
|
||||||
|
print("Неверный формат даты. Используйте ГГГГ-ММ-ДД")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
w_type = input("Тип (running/swimming/cycling/strength): ").lower()
|
||||||
|
if w_type in valid_types:
|
||||||
|
break
|
||||||
|
print(f"Неверный тип. Допустимые: {valid_types}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
duration = int(input("Длительность (минуты): "))
|
||||||
|
if duration > 0:
|
||||||
|
break
|
||||||
|
print("Длительность должна быть больше 0")
|
||||||
|
except ValueError:
|
||||||
|
print("Введите целое число")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
distance = float(input("Дистанция (км): "))
|
||||||
|
if distance >= 0:
|
||||||
|
break
|
||||||
|
print("Дистанция не может быть отрицательной")
|
||||||
|
except ValueError:
|
||||||
|
print("Введите число")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
calories = int(input("Калории: "))
|
||||||
|
if calories > 0:
|
||||||
|
break
|
||||||
|
print("Калории должны быть больше 0")
|
||||||
|
except ValueError:
|
||||||
|
print("Введите целое число")
|
||||||
|
|
||||||
|
hr_input = input("Средний пульс (опционально, Enter чтобы пропустить): ")
|
||||||
|
avg_heart_rate = int(hr_input) if hr_input.strip() else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
workouts = add_workout(workouts, date, w_type, duration, distance, calories, avg_heart_rate)
|
||||||
|
print(f"\nТренировка успешно добавлена")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"\nОшибка: {e}")
|
||||||
|
|
||||||
|
save_workouts(FILE_PATH, workouts)
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("ДЕМОНСТРАЦИЯ ФУНКЦИИ calories_per_minute")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if workouts:
|
||||||
|
first_workout = workouts[0]
|
||||||
|
intensity = calories_per_minute(first_workout)
|
||||||
|
print(f"\nПервая тренировка в списке:")
|
||||||
|
print(f"Дата: {first_workout.get('date')}")
|
||||||
|
print(f"Тип: {first_workout.get('type')}")
|
||||||
|
print(f"Длительность: {first_workout.get('duration_min')} мин")
|
||||||
|
print(f"Калории: {first_workout.get('calories')}")
|
||||||
|
print(f"\nИнтенсивность: {intensity:.2f} калорий в минуту")
|
||||||
|
else:
|
||||||
|
print("\nНет тренировок для демонстрации")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("ПРОГРАММА ЗАВЕРШЕНА")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user