Inject exercise mapper to parser
This commit is contained in:
parent
740244a04d
commit
4b93c5a9a0
6 changed files with 88 additions and 56 deletions
|
|
@ -11,7 +11,7 @@ class AppleNotesParser(BaseNotesParser):
|
||||||
"""Parser for Apple Notes format training data."""
|
"""Parser for Apple Notes format training data."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("apple.md")
|
super().__init__("apple.md", unique_apple_exercises_mapper)
|
||||||
|
|
||||||
def filter_training_data(self, training_data: str) -> str:
|
def filter_training_data(self, training_data: str) -> str:
|
||||||
"""Filter Apple-specific training data format."""
|
"""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]:
|
def remap_unique_exercises(apple_trainings: List[Training]) -> List[Training]:
|
||||||
"""Remap exercise names using Apple-specific mapping."""
|
"""Remap exercise names using Apple-specific mapping (deprecated - use parser.parse_and_map_training_data())."""
|
||||||
for apple_training in apple_trainings:
|
parser = AppleNotesParser()
|
||||||
if not apple_training or not apple_training.exercises:
|
return parser.apply_exercise_mapping(apple_trainings)
|
||||||
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
|
|
||||||
13
main.py
13
main.py
|
|
@ -1,4 +1,17 @@
|
||||||
from parsers.text_data_parser import parse_old_data
|
from parsers.text_data_parser import parse_old_data
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
text_trainings = parse_old_data()
|
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))
|
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}")
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ list_of_unique_names = [
|
||||||
"Жим от груди сидя в тренажере (рычаги)",
|
"Жим от груди сидя в тренажере (рычаги)",
|
||||||
"Жим от плеч вверх в тренажере (рычаги)",
|
"Жим от плеч вверх в тренажере (рычаги)",
|
||||||
"Икроножные сидя",
|
"Икроножные сидя",
|
||||||
|
"Пресс на наклонной скамье"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -152,5 +153,6 @@ obsidian_unique_exercies_mapping = {
|
||||||
"Тяга горизонтального блока": "Тяга горизонтального блока",
|
"Тяга горизонтального блока": "Тяга горизонтального блока",
|
||||||
"Тяга горизонтального блока 1 рукой поочерёдно": "Тяга горизонтального блока (по очереди)",
|
"Тяга горизонтального блока 1 рукой поочерёдно": "Тяга горизонтального блока (по очереди)",
|
||||||
"Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)",
|
"Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)",
|
||||||
|
"Пресс на скамье": "Пресс на наклонной скамье"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class ObsidianNotesParser(BaseNotesParser):
|
||||||
"""Parser for Obsidian Notes format training data."""
|
"""Parser for Obsidian Notes format training data."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("obsidian.md")
|
super().__init__("obsidian.md", obsidian_unique_exercies_mapping)
|
||||||
|
|
||||||
def filter_training_data(self, training_data: str) -> str:
|
def filter_training_data(self, training_data: str) -> str:
|
||||||
"""Filter Obsidian-specific training data format."""
|
"""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]:
|
def remap_unique_exercises(obsidian_trainings: List[Training]) -> List[Training]:
|
||||||
"""Remap exercise names using Obsidian-specific mapping."""
|
"""Remap exercise names using Obsidian-specific mapping (deprecated - use parser.parse_and_map_training_data())."""
|
||||||
for obsidian_training in obsidian_trainings:
|
parser = ObsidianNotesParser()
|
||||||
if not obsidian_training or not obsidian_training.exercises:
|
return parser.apply_exercise_mapping(obsidian_trainings)
|
||||||
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
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import re
|
from typing import List, Tuple, Dict, Optional
|
||||||
from typing import List, Tuple
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from obsidian.py_models import Approach, Exercise, Training
|
from obsidian.py_models import Approach, Exercise, Training
|
||||||
from utils.date_refactor import parse_training_date
|
from utils.date_refactor import parse_training_date
|
||||||
|
|
@ -9,38 +7,41 @@ from utils.date_refactor import parse_training_date
|
||||||
|
|
||||||
class BaseNotesParser:
|
class BaseNotesParser:
|
||||||
"""Base class for parsing training data from different note formats."""
|
"""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.data_file_name = data_file_name
|
||||||
self.project_root = os.getcwd()
|
self.project_root = os.getcwd()
|
||||||
|
self.exercise_mapper = exercise_mapper or {}
|
||||||
|
|
||||||
def get_data_path(self) -> str:
|
def get_data_path(self) -> str:
|
||||||
return os.path.join(self.project_root, "data")
|
return os.path.join(self.project_root, "data")
|
||||||
|
|
||||||
def get_data_file_path(self, file_name: str) -> str:
|
def get_data_file_path(self, file_name: str) -> str:
|
||||||
return os.path.join(self.get_data_path(), file_name)
|
return os.path.join(self.get_data_path(), file_name)
|
||||||
|
|
||||||
def read_data_file(self, file_name: str) -> str:
|
def read_data_file(self, file_name: str) -> str:
|
||||||
path_to_file = self.get_data_file_path(file_name)
|
path_to_file = self.get_data_file_path(file_name)
|
||||||
with open(path_to_file, "r") as f:
|
with open(path_to_file, "r") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def serialize_exercise(self, reps: str, weight: str, name: str) -> Exercise:
|
def serialize_exercise(self, reps: str, weight: str, name: str) -> Exercise:
|
||||||
"""Convert raw exercise data into Exercise object with approaches."""
|
"""Convert raw exercise data into Exercise object with approaches."""
|
||||||
reps_list: List[int] = [int(rep) for rep in reps.split("-")]
|
reps_list: List[int] = [int(rep) for rep in reps.split("-")]
|
||||||
weight_splitted: bool = False
|
weight_splitted: bool = False
|
||||||
weight_list: List[float] = []
|
weight_list: List[float] = []
|
||||||
|
|
||||||
if weight:
|
if weight:
|
||||||
weight_str_list: List[str] = [weight for weight in weight.split("-")]
|
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", "х"]):
|
if any(split_anchor in weight_str_list[0] for split_anchor in ["x", "х"]):
|
||||||
weight_splitted = True
|
weight_splitted = True
|
||||||
splitter = "x" if "x" in weight_str_list[0] else "х"
|
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:
|
else:
|
||||||
weight_list = [float(w) for w in weight_str_list]
|
weight_list = [float(w) for w in weight_str_list]
|
||||||
|
|
||||||
approaches = []
|
approaches = []
|
||||||
if not weight:
|
if not weight:
|
||||||
for rep_index in range(0, len(reps_list)):
|
for rep_index in range(0, len(reps_list)):
|
||||||
|
|
@ -49,19 +50,23 @@ class BaseNotesParser:
|
||||||
else:
|
else:
|
||||||
weight_pointer = 0
|
weight_pointer = 0
|
||||||
for rep_index in range(0, len(reps_list)):
|
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:
|
if rep_index < len(weight_list) - 1:
|
||||||
weight_pointer += 1
|
weight_pointer += 1
|
||||||
approaches.append(approach)
|
approaches.append(approach)
|
||||||
|
|
||||||
exercise = Exercise(
|
exercise = Exercise(
|
||||||
name=name, approaches=approaches, splitted_weight=weight_splitted
|
name=name, approaches=approaches, splitted_weight=weight_splitted
|
||||||
)
|
)
|
||||||
return exercise
|
return exercise
|
||||||
|
|
||||||
def parse_training_exercises(self, exercise_line: str) -> Exercise:
|
def parse_training_exercises(self, exercise_line: str) -> Exercise:
|
||||||
"""Parse exercise data from a table row."""
|
"""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:
|
for entry in stripped:
|
||||||
if entry in ["Упражнение", "Вес", "Подходы"]:
|
if entry in ["Упражнение", "Вес", "Подходы"]:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
@ -74,28 +79,32 @@ class BaseNotesParser:
|
||||||
name=stripped[0], weight=stripped[1], reps=stripped[2]
|
name=stripped[0], weight=stripped[1], reps=stripped[2]
|
||||||
)
|
)
|
||||||
raise ValueError("No valid exercise data found")
|
raise ValueError("No valid exercise data found")
|
||||||
|
|
||||||
def filter_training_data(self, training_data: str) -> str:
|
def filter_training_data(self, training_data: str) -> str:
|
||||||
"""Filter and clean training data. Override in subclasses for specific formats."""
|
"""Filter and clean training data. Override in subclasses for specific formats."""
|
||||||
return training_data
|
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."""
|
"""Parse training header. Override in subclasses for specific formats."""
|
||||||
raise NotImplementedError("Subclasses must implement parse_training_header")
|
raise NotImplementedError("Subclasses must implement parse_training_header")
|
||||||
|
|
||||||
def create_training_from_date(self, date_str: str) -> Training:
|
def create_training_from_date(self, date_str: str) -> Training:
|
||||||
"""Create Training object from date string using utility function."""
|
"""Create Training object from date string using utility function."""
|
||||||
return Training(date=parse_training_date(date_str), exercises=[])
|
return Training(date=parse_training_date(date_str), exercises=[])
|
||||||
|
|
||||||
def parse_training_data(self) -> List[Training]:
|
def parse_training_data(self) -> List[Training]:
|
||||||
"""Main parsing method. Override for specific parsing logic."""
|
"""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()
|
lines = training_data.splitlines()
|
||||||
current_training = None
|
current_training = None
|
||||||
trains = []
|
trains = []
|
||||||
|
|
||||||
for index, line in enumerate(lines):
|
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:
|
if index == len(lines) - 1:
|
||||||
trains.append(current_training)
|
trains.append(current_training)
|
||||||
if header_parsed:
|
if header_parsed:
|
||||||
|
|
@ -108,5 +117,24 @@ class BaseNotesParser:
|
||||||
current_training.exercises.append(exr)
|
current_training.exercises.append(exr)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return [train for train in trains if train is not None]
|
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from apple.notes_parser import parse_training_data as aptd
|
from apple.notes_parser import AppleNotesParser
|
||||||
from obsidian.notes_parser import parse_training_data as optd
|
from obsidian.notes_parser import ObsidianNotesParser
|
||||||
from obsidian.py_models import Training
|
from obsidian.py_models import Training
|
||||||
|
|
||||||
def parse_old_data() -> List[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:
|
Returns:
|
||||||
List of trainings
|
List of trainings with standardized exercise names
|
||||||
"""
|
"""
|
||||||
o_dates = [train for train in optd()]
|
obsidian_parser = ObsidianNotesParser()
|
||||||
a_dates = [train for train in aptd()]
|
apple_parser = AppleNotesParser()
|
||||||
return o_dates + a_dates
|
|
||||||
|
o_trainings = obsidian_parser.parse_and_map_training_data()
|
||||||
|
a_trainings = apple_parser.parse_and_map_training_data()
|
||||||
|
|
||||||
|
return o_trainings + a_trainings
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue