diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/PycharmProject.iml b/.idea/PycharmProject.iml
new file mode 100644
index 0000000..267c9c8
--- /dev/null
+++ b/.idea/PycharmProject.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..ac21435
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..31080cc
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..463ca10
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..830a55c
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/data.txt b/data/data.txt
new file mode 100644
index 0000000..e69de29
diff --git a/file.txt b/file.txt
index 3f53d3b..7ba8a0c 100644
Binary files a/file.txt and b/file.txt differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e69de29
diff --git a/src/fitness_tracker.py b/src/fitness_tracker.py
new file mode 100644
index 0000000..3599dd1
--- /dev/null
+++ b/src/fitness_tracker.py
@@ -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
+ }
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..d99c3b0
--- /dev/null
+++ b/src/main.py
@@ -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()
\ No newline at end of file