diff --git a/apple/notes_parser.py b/apple/notes_parser.py index 2811fc8..035cb1b 100644 --- a/apple/notes_parser.py +++ b/apple/notes_parser.py @@ -11,7 +11,7 @@ class AppleNotesParser(BaseNotesParser): """Parser for Apple Notes format training data.""" def __init__(self): - super().__init__("apple.md") + super().__init__("apple.md", unique_apple_exercises_mapper) def filter_training_data(self, training_data: str) -> str: """Filter Apple-specific training data format.""" @@ -66,15 +66,6 @@ def parse_training_data() -> List[Training]: def remap_unique_exercises(apple_trainings: List[Training]) -> List[Training]: - """Remap exercise names using Apple-specific mapping.""" - for apple_training in apple_trainings: - if not apple_training or not apple_training.exercises: - continue - for apple_exercise in apple_training.exercises: - if not apple_exercise: - continue - print(f"{apple_training.date} : {apple_exercise}") - mapped_name = unique_apple_exercises_mapper.get(apple_exercise.name) - if mapped_name is not None: - apple_exercise.name = mapped_name - return apple_trainings \ No newline at end of file + """Remap exercise names using Apple-specific mapping (deprecated - use parser.parse_and_map_training_data()).""" + parser = AppleNotesParser() + return parser.apply_exercise_mapping(apple_trainings) \ No newline at end of file diff --git a/main.py b/main.py index de00af2..4558e63 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,17 @@ from parsers.text_data_parser import parse_old_data +from collections import Counter text_trainings = parse_old_data() +for training in text_trainings: + print(f"{training.date} :: {[ex.name for ex in training.exercises]}") print(len(text_trainings)) + +exercise_names = [] +for training in text_trainings: + for exercise in training.exercises: + exercise_names.append(exercise.name) + +exercise_counts = Counter(exercise_names) +print("\nExercise counts:") +for name, count in sorted(exercise_counts.items()): + print(f"{name}: {count}") diff --git a/obsidian/mapper.py b/obsidian/mapper.py index dfc7ad4..ed05866 100644 --- a/obsidian/mapper.py +++ b/obsidian/mapper.py @@ -40,6 +40,7 @@ list_of_unique_names = [ "Жим от груди сидя в тренажере (рычаги)", "Жим от плеч вверх в тренажере (рычаги)", "Икроножные сидя", + "Пресс на наклонной скамье" ] @@ -152,5 +153,6 @@ obsidian_unique_exercies_mapping = { "Тяга горизонтального блока": "Тяга горизонтального блока", "Тяга горизонтального блока 1 рукой поочерёдно": "Тяга горизонтального блока (по очереди)", "Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)", + "Пресс на скамье": "Пресс на наклонной скамье" } diff --git a/obsidian/notes_parser.py b/obsidian/notes_parser.py index 21ab823..a349a87 100644 --- a/obsidian/notes_parser.py +++ b/obsidian/notes_parser.py @@ -10,7 +10,7 @@ class ObsidianNotesParser(BaseNotesParser): """Parser for Obsidian Notes format training data.""" def __init__(self): - super().__init__("obsidian.md") + super().__init__("obsidian.md", obsidian_unique_exercies_mapping) def filter_training_data(self, training_data: str) -> str: """Filter Obsidian-specific training data format.""" @@ -36,12 +36,6 @@ def parse_training_data() -> List[Training]: def remap_unique_exercises(obsidian_trainings: List[Training]) -> List[Training]: - """Remap exercise names using Obsidian-specific mapping.""" - for obsidian_training in obsidian_trainings: - if not obsidian_training or not obsidian_training.exercises: - continue - for obsidian_exercise in obsidian_training.exercises: - mapped_name = obsidian_unique_exercies_mapping.get(obsidian_exercise.name) - if mapped_name is not None: - obsidian_exercise.name = mapped_name - return obsidian_trainings \ No newline at end of file + """Remap exercise names using Obsidian-specific mapping (deprecated - use parser.parse_and_map_training_data()).""" + parser = ObsidianNotesParser() + return parser.apply_exercise_mapping(obsidian_trainings) \ No newline at end of file diff --git a/parsers/base_parser.py b/parsers/base_parser.py index 0e78332..770fa03 100644 --- a/parsers/base_parser.py +++ b/parsers/base_parser.py @@ -1,7 +1,5 @@ import os -import re -from typing import List, Tuple -from datetime import datetime +from typing import List, Tuple, Dict, Optional from obsidian.py_models import Approach, Exercise, Training from utils.date_refactor import parse_training_date @@ -9,38 +7,41 @@ from utils.date_refactor import parse_training_date class BaseNotesParser: """Base class for parsing training data from different note formats.""" - - def __init__(self, data_file_name: str): + + def __init__(self, data_file_name: str, exercise_mapper: Optional[Dict[str, str]] = None): self.data_file_name = data_file_name self.project_root = os.getcwd() - + self.exercise_mapper = exercise_mapper or {} + def get_data_path(self) -> str: return os.path.join(self.project_root, "data") - + def get_data_file_path(self, file_name: str) -> str: return os.path.join(self.get_data_path(), file_name) - + def read_data_file(self, file_name: str) -> str: path_to_file = self.get_data_file_path(file_name) with open(path_to_file, "r") as f: content = f.read() return content - + def serialize_exercise(self, reps: str, weight: str, name: str) -> Exercise: """Convert raw exercise data into Exercise object with approaches.""" reps_list: List[int] = [int(rep) for rep in reps.split("-")] weight_splitted: bool = False weight_list: List[float] = [] - + if weight: weight_str_list: List[str] = [weight for weight in weight.split("-")] if any(split_anchor in weight_str_list[0] for split_anchor in ["x", "х"]): weight_splitted = True splitter = "x" if "x" in weight_str_list[0] else "х" - weight_list = [float(xweight.split(splitter)[0]) for xweight in weight_str_list] + weight_list = [ + float(xweight.split(splitter)[0]) for xweight in weight_str_list + ] else: weight_list = [float(w) for w in weight_str_list] - + approaches = [] if not weight: for rep_index in range(0, len(reps_list)): @@ -49,19 +50,23 @@ class BaseNotesParser: else: weight_pointer = 0 for rep_index in range(0, len(reps_list)): - approach = Approach(weight=weight_list[weight_pointer], reps=reps_list[rep_index]) + approach = Approach( + weight=weight_list[weight_pointer], reps=reps_list[rep_index] + ) if rep_index < len(weight_list) - 1: weight_pointer += 1 approaches.append(approach) - + exercise = Exercise( name=name, approaches=approaches, splitted_weight=weight_splitted ) return exercise - + def parse_training_exercises(self, exercise_line: str) -> Exercise: """Parse exercise data from a table row.""" - stripped: List[str] = [entry.strip() for entry in exercise_line.split("|")][1:-1] + stripped: List[str] = [entry.strip() for entry in exercise_line.split("|")][ + 1:-1 + ] for entry in stripped: if entry in ["Упражнение", "Вес", "Подходы"]: raise ValueError @@ -74,28 +79,32 @@ class BaseNotesParser: name=stripped[0], weight=stripped[1], reps=stripped[2] ) raise ValueError("No valid exercise data found") - + def filter_training_data(self, training_data: str) -> str: """Filter and clean training data. Override in subclasses for specific formats.""" return training_data - - def parse_training_header(self, training_data_line: str) -> Tuple[bool, str, str, str]: + + def parse_training_header( + self, training_data_line: str + ) -> Tuple[bool, str, str, str]: """Parse training header. Override in subclasses for specific formats.""" raise NotImplementedError("Subclasses must implement parse_training_header") - + def create_training_from_date(self, date_str: str) -> Training: """Create Training object from date string using utility function.""" return Training(date=parse_training_date(date_str), exercises=[]) - + def parse_training_data(self) -> List[Training]: """Main parsing method. Override for specific parsing logic.""" - training_data = self.filter_training_data(self.read_data_file(self.data_file_name)) + training_data = self.filter_training_data( + self.read_data_file(self.data_file_name) + ) lines = training_data.splitlines() current_training = None trains = [] - + for index, line in enumerate(lines): - header_parsed, date, trainer, year_count = self.parse_training_header(line) + header_parsed, date, _, _ = self.parse_training_header(line) if index == len(lines) - 1: trains.append(current_training) if header_parsed: @@ -108,5 +117,24 @@ class BaseNotesParser: current_training.exercises.append(exr) except ValueError: pass - - return [train for train in trains if train is not None] \ No newline at end of file + + return [train for train in trains if train is not None] + + def apply_exercise_mapping(self, trainings: List[Training]) -> List[Training]: + """Apply exercise name mapping to all trainings.""" + for training in trainings: + if not training or not training.exercises: + continue + for exercise in training.exercises: + if not exercise: + continue + mapped_name = self.exercise_mapper.get(exercise.name) + if mapped_name is not None: + exercise.name = mapped_name + return trainings + + def parse_and_map_training_data(self) -> List[Training]: + """Parse training data and apply exercise mapping.""" + trainings = self.parse_training_data() + return self.apply_exercise_mapping(trainings) + diff --git a/parsers/text_data_parser.py b/parsers/text_data_parser.py index 4a4dd35..693ddbe 100644 --- a/parsers/text_data_parser.py +++ b/parsers/text_data_parser.py @@ -1,15 +1,19 @@ from typing import List -from apple.notes_parser import parse_training_data as aptd -from obsidian.notes_parser import parse_training_data as optd +from apple.notes_parser import AppleNotesParser +from obsidian.notes_parser import ObsidianNotesParser from obsidian.py_models import Training def parse_old_data() -> List[Training]: - """Method for parsing all old data from apple and obsidian notes + """Method for parsing all old data from apple and obsidian notes with exercise mapping applied Returns: - List of trainings + List of trainings with standardized exercise names """ - o_dates = [train for train in optd()] - a_dates = [train for train in aptd()] - return o_dates + a_dates + obsidian_parser = ObsidianNotesParser() + apple_parser = AppleNotesParser() + + o_trainings = obsidian_parser.parse_and_map_training_data() + a_trainings = apple_parser.parse_and_map_training_data() + + return o_trainings + a_trainings