Compare commits

...

13 commits

37 changed files with 1450 additions and 533 deletions

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
# PostgreSQL Database Configuration
POSTGRES_DATABASE_DB=your_database_name
POSTGRES_DATABASE_USER=your_username
POSTGRES_DATABASE_PASSWORD=your_secure_password
POSTGRES_DATABASE_HOST=postgres
POSTGRES_DATABASE_PORT=5432
# FastAPI Configuration
PYTHONPATH=/app
# Development settings
DEBUG=true

9
.gitignore vendored
View file

@ -123,11 +123,14 @@ celerybeat.pid
*.sage.py *.sage.py
# Environments # Environments
.env
.venv .venv
env/ env/
venv/ venv/
ENV/ ENV/
.env
.env.local
.env.production
env.bak/ env.bak/
venv.bak/ venv.bak/
@ -163,3 +166,7 @@ cython_debug/
#.idea/ #.idea/
pyrightconfig.json pyrightconfig.json
# Database
*.db
*.sqlite3

86
CLAUDE.md Normal file
View file

@ -0,0 +1,86 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
F1tness Parser is a FastAPI-based web application that parses fitness training data from two note formats:
- **Obsidian Notes**: Markdown tables with `# DD.MM.YYYY (trainer-session)` headers
- **Apple Notes**: Bold markdown tables with `**DD.MM.YYYY (trainer-session)**` headers
The application uses a layered architecture with parsers, models, services, and API endpoints.
## Development Commands
### Local Development
```bash
# Start development server
python run.py
# Start with Docker Compose (includes PostgreSQL)
docker compose up --build
# Run migrations manually
python -c "from migrations.runner import MigrationRunner; from app.config import settings; import asyncio; asyncio.run(MigrationRunner(settings.get_postgres_database_url()).run_migrations())"
```
### Testing
```bash
# Run tests
pytest
# Run specific test file
pytest tests/test_core/test_obsidian_parser.py
# Run tests with coverage
pytest --cov=app
```
### Type Checking
```bash
# Run type checking
mypy app/
```
## Architecture
### Core Components
**Parser Architecture**: The system uses a base parser class (`app/core/parsers/base.py`) with format-specific implementations:
- `ObsidianNotesParser` - handles Obsidian markdown format
- `AppleNotesParser` - handles Apple notes format
- Each parser includes exercise name mapping for normalization
**Data Models**: Located in `app/core/dto/training.py`:
- `Training` - represents a workout session
- `Exercise` - individual exercise with multiple approaches
- `Approach` - single set with weight and reps
**API Structure**:
- `/api/v1/` - REST API endpoints
- `/app/` - Web interface endpoints
- Static files served from `app/static/`
- Templates in `app/templates/`
### Database
The application supports both SQLite (default) and PostgreSQL. Database migrations are handled by a custom migration runner in `migrations/runner.py` that reads SQL files from `migrations/sql/`.
Configuration is managed through `app/config.py` using Pydantic settings with `.env` file support.
### Key Files
- `app/main.py` - FastAPI application setup with lifespan management
- `app/config.py` - Application configuration and database settings
- `run.py` - Development server entry point
- `migrations/runner.py` - Custom database migration system
- `compose.yaml` - Docker Compose setup with PostgreSQL
## Environment Setup
Copy `.env.example` to `.env` and configure:
- PostgreSQL connection settings
- Debug mode
- PYTHONPATH (for Docker)
The application will run database migrations automatically on startup through the FastAPI lifespan event.

View file

@ -19,6 +19,7 @@ $table("EXERCISE", "exercise"){
$column("NAME") VARCHAR $column("NAME") VARCHAR
} }
$table("APPROACH", "approach"){ $table("APPROACH", "approach"){
$pk("ID") INTEGER NOT NULL $pk("ID") INTEGER NOT NULL
$fk("EXERCISE") INTEGER NOT NULL $fk("EXERCISE") INTEGER NOT NULL

25
DOCS/deployment.puml Normal file
View file

@ -0,0 +1,25 @@
@startuml
actor client
agent "web interface" as WEB_UI
agent "ios app" as IOS_APP
agent "telegram app" as TG_APP
frame docker:f1tness_db {
database "PostgreSQL" as db
}
frame docker:fastAPI {
node backend_API
node frontend_renderer
}
client <--> WEB_UI
client <--> IOS_APP
client <--> TG_APP
WEB_UI <--> frontend_renderer
IOS_APP <--> backend_API
TG_APP <--> backend_API
backend_API --> db
@enduml

View file

@ -1,11 +1,62 @@
from datetime import datetime
import logging
from fastapi import APIRouter from fastapi import APIRouter
from app.core.database.connection import SQLiteExecutor
from app.core.database.models.approach import ApproachOps
from app.core.database.models.training import TrainingOps
from app.core.database.models.exercise import ExerciseOps
from app.core.dto.training import TrainingDTO
from app.core.parsers.obsidian import parse_training_data from app.core.parsers.obsidian import parse_training_data
sqllite_executor = SQLiteExecutor()
router = APIRouter() router = APIRouter()
@router.get("/obsidian/") @router.get("/obsidian/")
async def obsidian_trainings_list(): async def obsidian_trainings_list():
return { return {"data": parse_training_data()}
"data": parse_training_data()
}
@router.get("/test/create/")
async def create_sample_training():
test_training = TrainingDTO(date=datetime.now().date(), trainer="Stepka")
training_executor = TrainingOps(sqllite_executor)
result_id = await training_executor.create(test_training)
return {"status": "ok", "create_id": result_id}
@router.get("/test/get/list/")
async def get_trainings_list():
training_executor = TrainingOps(sqllite_executor)
results = await training_executor.list()
return {"status": "ok", "create_id": results}
@router.delete("/test/delete/")
async def delete_by_ids():
training_executor = TrainingOps(sqllite_executor)
await training_executor.delete(list_of_ids=[4,5,6])
return {"status": "ok"}
@router.post("/test/create/")
async def create_full_training():
training_executor = TrainingOps(sqllite_executor)
exercise_executor = ExerciseOps(sqllite_executor)
approach_executor = ApproachOps(sqllite_executor)
sample_training_data: TrainingDTO = parse_training_data()[-1]
logging.info(sample_training_data)
training_id: int = await training_executor.create(sample_training_data)
if sample_training_data.exercises:
for exercise in sample_training_data.exercises:
exercise.training_id = training_id
exercise_id: int = await exercise_executor.create(exercise)
if exercise.approaches:
for approach in exercise.approaches:
approach.exercise_id = exercise_id
await approach_executor.create(approach)
return {"status": "ok"}

View file

@ -1,14 +1,38 @@
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional from typing import Optional
class Settings(BaseSettings): class Settings(BaseSettings):
app_name: str = "Fitness Parser API" # Base app settings
version: str = "0.1.0" model_config = SettingsConfigDict(env_file=".env")
app_name: str = "F1tness Parser API"
version: str = "0.0.1"
debug: bool = False debug: bool = False
class Config: # SQLite database settings
env_file = ".env" SQLITE_DATABASE_PATH: str = "fitness.db"
# Postgres database settings
POSTGRES_DATABASE_URL: Optional[str] = None
POSTGRES_DATABASE_HOST: str = "localhost"
POSTGRES_DATABASE_PORT: int = 5432
POSTGRES_DATABASE_DB: str = "f1tness_db"
POSTGRES_DATABASE_USER: str = "postgres"
POSTGRES_DATABASE_PASSWORD: str = "password"
def get_postgres_database_url(self, async_driver: bool = True) -> str:
"""Method for receiving relational database URL"""
# If POSTGRES_DATABASE_URL is somehow set in env file - return it
if self.POSTGRES_DATABASE_URL:
return self.POSTGRES_DATABASE_URL
driver = "postgresql+asyncpg" if async_driver else "postgresql"
return (
f"{driver}://{self.POSTGRES_DATABASE_USER}:{self.POSTGRES_DATABASE_PASSWORD}"
f"@{self.POSTGRES_DATABASE_HOST}"
f":{self.POSTGRES_DATABASE_PORT}/{self.POSTGRES_DATABASE_DB}"
)
settings = Settings() settings = Settings()

View file

@ -0,0 +1,36 @@
from typing import Dict, List, Any
import aiosqlite
from app.config import settings
class SQLiteExecutor:
"""Executor для SQL запросов в БД SQLlite"""
def __init__(self) -> None:
self._db_path = settings.SQLITE_DATABASE_PATH
async def execute_query(
self, query: str, params: tuple = ()
) -> List[Dict[str, Any]]:
"""Выполнить запрос, который не должен вносить изменения в БД.
:param query: SQL запрос
:param params: Параметры, которые нужно передать вместе с запросом
:return: Список, выдаваемый aiosqlite
"""
async with aiosqlite.connect(self._db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute(query, params)
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def execute_mod_query(self, query: str, params: tuple = ()) -> int:
"""Выполнить запрос, который что-то изменяет в БД.
:param query: SQL запрос
:param params: Параметры, которые нужно передать вместе с запросом
:return: Список, выдаваемый aiosqlite
"""
async with aiosqlite.connect(self._db_path) as db:
cursor = await db.execute(query, params)
await db.commit()
return cursor.lastrowid if cursor.lastrowid else cursor.rowcount

View file

View file

@ -0,0 +1,26 @@
from typing import List, Dict, Any
from app.core.database.connection import SQLiteExecutor
from app.core.dto.training import ApproachDTO
class ApproachOps:
TABLE_NAME: str = "approaches"
def __init__(self, executor: SQLiteExecutor) -> None:
self._executor = executor
async def create(self, approach: ApproachDTO) -> int:
query = f"INSERT INTO {self.TABLE_NAME} (exercise_id, weight, reps) VALUES (?, ?, ?)"
return await self._executor.execute_mod_query(
query, (approach.exercise_id, approach.weight, approach.reps)
)
async def list(self) -> List[Dict[str, Any]]:
query = f"SELECT * FROM {self.TABLE_NAME}"
return await self._executor.execute_query(query)
async def delete(self, list_of_ids: List[int]) -> None:
placeholders: str = ",".join("?" * len(list_of_ids))
query = f"DELETE FROM {self.TABLE_NAME} WHERE id IN ({placeholders})"
await self._executor.execute_mod_query(query, tuple(list_of_ids))
return None

View file

@ -0,0 +1,26 @@
from typing import List, Dict, Any
from app.core.database.connection import SQLiteExecutor
from app.core.dto.training import ExerciseDTO
class ExerciseOps:
TABLE_NAME: str = "exercises"
def __init__(self, executor: SQLiteExecutor) -> None:
self._executor = executor
async def create(self, exercise: ExerciseDTO) -> int:
query = f"INSERT INTO {self.TABLE_NAME} (training_id, name, splitted_weight) VALUES (?, ?, ?)"
return await self._executor.execute_mod_query(
query, (exercise.training_id, exercise.name, exercise.splitted_weight)
)
async def list(self) -> List[Dict[str, Any]]:
query = f"SELECT * FROM {self.TABLE_NAME}"
return await self._executor.execute_query(query)
async def delete(self, list_of_ids: List[int]) -> None:
placeholders: str = ",".join("?" * len(list_of_ids))
query = f"DELETE FROM {self.TABLE_NAME} WHERE id IN ({placeholders})"
await self._executor.execute_mod_query(query, tuple(list_of_ids))
return None

View file

@ -0,0 +1,26 @@
from typing import List, Dict, Any
from app.core.database.connection import SQLiteExecutor
from app.core.dto.training import TrainingDTO
class TrainingOps:
TABLE_NAME: str = "trainings"
def __init__(self, executor: SQLiteExecutor) -> None:
self._executor = executor
async def create(self, training: TrainingDTO) -> int:
query = f"INSERT INTO {self.TABLE_NAME} (date, trainer) VALUES (?, ?)"
return await self._executor.execute_mod_query(
query, (training.date, training.trainer)
)
async def list(self) -> List[Dict[str, Any]]:
query = f"SELECT * FROM {self.TABLE_NAME}"
return await self._executor.execute_query(query)
async def delete(self, list_of_ids: List[int]) -> None:
placeholders: str = ",".join("?" * len(list_of_ids))
query = f"DELETE FROM {self.TABLE_NAME} WHERE id IN ({placeholders})"
await self._executor.execute_mod_query(query, tuple(list_of_ids))
return None

0
app/core/dto/__init__.py Normal file
View file

25
app/core/dto/training.py Normal file
View file

@ -0,0 +1,25 @@
from typing import List, Optional
from pydantic import BaseModel
from datetime import date
class ApproachDTO(BaseModel):
id: Optional[int] = None
exercise_id: Optional[int] = None
weight: float
reps: int
class ExerciseDTO(BaseModel):
id: Optional[int] = None
training_id: Optional[int] = None
name: str
splitted_weight: bool = False
approaches: Optional[List[ApproachDTO]] = None
class TrainingDTO(BaseModel):
id: Optional[int] = None
date: date
trainer: Optional[str] = None
exercises: Optional[List[ExerciseDTO]] = None

View file

@ -1,23 +0,0 @@
from typing import List, Optional
from pydantic import BaseModel
from datetime import date
class Approach(BaseModel):
weight: float
reps: int
class Exercise(BaseModel):
name: str
splitted_weight: bool = False
approaches: List[Approach]
class Training(BaseModel):
date: date
exercises: Optional[List[Exercise]]
class Coach(BaseModel):
name: str

View file

@ -2,7 +2,7 @@ import re
from typing import List, Tuple from typing import List, Tuple
from datetime import datetime from datetime import datetime
from app.core.models.training import Training from app.core.dto.training import TrainingDTO
from app.core.parsers.apple_mapper import unique_apple_exercises_mapper from app.core.parsers.apple_mapper import unique_apple_exercises_mapper
from app.core.parsers.base import BaseNotesParser from app.core.parsers.base import BaseNotesParser
@ -47,18 +47,18 @@ class AppleNotesParser(BaseNotesParser):
return True, date, trainer, year_count return True, date, trainer, year_count
return False, "", "", "" return False, "", "", ""
def create_training_from_date(self, date_str: str) -> Training: def create_training_from_date(self, date_str: str) -> TrainingDTO:
"""Create Training object from date string with fallback parsing.""" """Create Training object from date string with fallback parsing."""
try: try:
return Training( return TrainingDTO(
date=datetime.strptime(date_str, "%d.%m.%Y").date(), exercises=[] date=datetime.strptime(date_str, "%d.%m.%Y").date(), exercises=[]
) )
except ValueError: except ValueError:
return Training( return TrainingDTO(
date=datetime.strptime(date_str, "%d.%m.%y").date(), exercises=[] date=datetime.strptime(date_str, "%d.%m.%y").date(), exercises=[]
) )
def parse(self, data: str) -> List[Training]: def parse(self, data: str) -> List[TrainingDTO]:
"""Parse Apple Notes training data from string input.""" """Parse Apple Notes training data from string input."""
# Override the data file reading with direct string input # Override the data file reading with direct string input
original_method = self.read_data_file original_method = self.read_data_file
@ -72,13 +72,13 @@ class AppleNotesParser(BaseNotesParser):
self.read_data_file = original_method self.read_data_file = original_method
def parse_training_data() -> List[Training]: def parse_training_data() -> List[TrainingDTO]:
"""Parse Apple Notes training data.""" """Parse Apple Notes training data."""
parser = AppleNotesParser() parser = AppleNotesParser()
return parser.parse_training_data() return parser.parse_training_data()
def remap_unique_exercises(apple_trainings: List[Training]) -> List[Training]: def remap_unique_exercises(apple_trainings: List[TrainingDTO]) -> List[TrainingDTO]:
"""Remap exercise names using Apple-specific mapping (deprecated - use parser.parse_and_map_training_data()).""" """Remap exercise names using Apple-specific mapping (deprecated - use parser.parse_and_map_training_data())."""
parser = AppleNotesParser() parser = AppleNotesParser()
return parser.apply_exercise_mapping(apple_trainings) return parser.apply_exercise_mapping(apple_trainings)

View file

@ -1,9 +1,79 @@
list_of_unique_apple_names = [
"Тяга горизонтального блока",
"Тяга горизонтального блока (по очереди)",
"Тяга гантелей в наклоне (по очереди)",
"Тяга гантелей на скамье",
"Тяга вертикального блока",
"Тяга вертикального блока (обратный хват)",
"Разгибание рук сидя (трицепс машина)",
"Подъем штанги на бицепс стоя",
"Подъем гантелей на бицепс стоя",
"Сгибание ног сидя",
"Сгибание ног лежа",
"Сведение рук в пек-дек",
"Румынская тяга",
"Разгибание ног сидя",
"Махи гантелей стоя",
"Разведение рук в стороны в наклоне (гантели)",
"Приседания",
"Подъем ног на турнике",
"Подтягивания",
"Подтягивания обратным хватом",
"Отжимания",
"Отведение рук в кроссовере (по очереди)",
"Разведение рук в пек-дек (по очереди)",
"Разведение рук в пек-дек",
"Отжимание на трицепс в кроссовере",
"Баттерфляй",
"Болгарские сплит-приседания",
"Брусья",
"Вертикальная тяга одной рукой",
"Выпады",
"Гиперэкстензия",
"Гравитрон",
"Гребная тяга с упором в грудь (по очереди)",
"Разведение рук в стороны (дельт-машина)",
"Жим гантелей лежа",
"Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей сидя",
"Жим ногами",
"Жим от груди сидя в тренажере (рычаги)",
"Жим от плеч вверх в тренажере (рычаги)",
"Икроножные сидя",
"Пресс на наклонной скамье",
"Бицепс машина",
"Pull down",
"Тяга Т-грифа",
"Французский жим гантели сидя",
"Молоты стоя (наискосок)",
"Отведение руки из-за спины в кроссовере",
"Гребная тяга с упором в грудь",
"Тяга штанги в наклоне",
"Молоты стоя",
"Жим штанги лежа",
"Тяга штанги в наклоне (обратным хватом)",
"Жим штанги сидя (45 градусов)",
"Наклоны с прямой спиной",
"Разгибание рук в наклоне (кик-бек)",
"Разгибание спины",
"Скручивания",
"Подъем гантелей на бицепс сидя"
]
unique_apple_exercises_mapper = { unique_apple_exercises_mapper = {
# Баттерфляй
"Батерфляй": "Баттерфляй", "Батерфляй": "Баттерфляй",
"Баттерфляй": "Баттерфляй", "Баттерфляй": "Баттерфляй",
"Баттерфляй дю": "Баттерфляй", "Баттерфляй дю": "Баттерфляй",
# Подъем гантелей на бицепс сидя
"Бицепс на лавке 45⁰": "Подъем гантелей на бицепс сидя", "Бицепс на лавке 45⁰": "Подъем гантелей на бицепс сидя",
"Болгарский присед": "Болгарские сплит-приседения",
# Болгарские сплит-приседания
"Болгарский присед": "Болгарские сплит-приседания",
# Брусья
"Брусья": "Брусья", "Брусья": "Брусья",
"Брусья (гравитрон)": "Брусья", "Брусья (гравитрон)": "Брусья",
"Брусья в тренажере": "Брусья", "Брусья в тренажере": "Брусья",
@ -12,42 +82,48 @@ unique_apple_exercises_mapper = {
"Брусья узким хватом за": "Брусья", "Брусья узким хватом за": "Брусья",
"Брусья широким хватом": "Брусья", "Брусья широким хватом": "Брусья",
"Брусья-прямые ноги": "Брусья", "Брусья-прямые ноги": "Брусья",
"ГАК машина": "Жим ногами (гакк-машина)", "Отжимания на брусьях": "Брусья",
"Гакк машина": "Жим ногами (гакк-машина)", "Отжимания на брусьях узким хватом": "Брусья",
"Гакк присед": "Жим ногами (гакк-машина)", "Отжимания на брусьях широким хватом": "Брусья",
"Гантели": "Подъем гантелей на бицепс стоя",
"Гантели к поясу лежа на скамье": "Тяга гантелей на скамье", # Выпады
"Гантели молот": "Гантели молот", "Присд на одну ногу (по очереди)": "Выпады",
"Гантели на лавках": "Жим гантелей сидя", "Присед на одну ногу": "Выпады",
"Гантели-молот": "Гантели молот", "Присед на одну ногу (каждая)": "Выпады",
"Приседания на одной ноге": "Выпады",
# Молоты стоя
"Гантели молот": "Молоты стоя",
"Гантели-молот": "Молоты стоя",
"Молот назад двумя руками": "Молоты стоя",
"Молот трицепс стоя": "Молоты стоя",
"Молоты": "Молоты стоя",
"Сгибание гантелей в стиле молот одновременно": "Молоты стоя",
# Гиперэкстензия
"Гипекстензия": "Гиперэкстензия", "Гипекстензия": "Гиперэкстензия",
"Гиперэкстензия": "Гиперэкстензия", "Гиперэкстензия": "Гиперэкстензия",
"Горизонтальна тяга на себя": "Тяга горизонтального блока",
"Горизонтальная тяга блока": "Тяга горизонтального блока", # Гравитрон
"Гравитрон": "Гравитрон",
"Гравитрор": "Гравитрон", "Гравитрор": "Гравитрон",
# Гребная тяга с упором в грудь
"Гребная тяга": "Гребная тяга с упором в грудь", "Гребная тяга": "Гребная тяга с упором в грудь",
"Гребная тяга с упором в грудь": "Гребная тяга с упором в грудь", "Гребная тяга с упором в грудь": "Гребная тяга с упором в грудь",
"Гребная тяга с упором в грудь как": "Гребная тяга с упором в грудь", "Гребная тяга с упором в грудь как": "Гребная тяга с упором в грудь",
"Гребная тяга с упором на грудь": "Гребная тяга с упором в грудь", "Гребная тяга с упором на грудь": "Гребная тяга с упором в грудь",
"Гребная тяга сидя (одна рука)": "Гребная тяга с упором в грудь (по очереди)",
"Гребная тяга сидя одной рукой": "Гребная тяга с упором в грудь (по очереди)",
"Гребная тяга сидя с упором в грудт": "Гребная тяга с упором в грудь", "Гребная тяга сидя с упором в грудт": "Гребная тяга с упором в грудь",
"Гребная тяга сидя с упором в грудь": "Гребная тяга с упором в грудь", "Гребная тяга сидя с упором в грудь": "Гребная тяга с упором в грудь",
"Гребная тяга сидя с упором в грудь (одной рукой рычаг)": "Гребная тяга с упором в грудь (по очереди)",
"Гребная тяга\xa0 упором в грудьс": "Гребная тяга с упором в грудь", "Гребная тяга\xa0 упором в грудьс": "Гребная тяга с упором в грудь",
"Жим в тренажере вверх": "Жим от плеч вверх в тренажере (рычаги)", "Тяга на себя с упором в грудь": "Гребная тяга с упором в грудь",
"Жим в тренажере на плечи блочный (3х12)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим в тренажёре на плечи (силовой тренажёр: жим сидя)": "Жим от плеч вверх в тренажере (рычаги)", # Гребная тяга с упором в грудь (по очереди)
"Жим вверх 80⁰": "Жим гантелей сидя", "Гребная тяга сидя (одна рука)": "Гребная тяга с упором в грудь (по очереди)",
"Гребная тяга сидя одной рукой": "Гребная тяга с упором в грудь (по очереди)",
"Гребная тяга сидя с упором в грудь (одной рукой рычаг)": "Гребная тяга с упором в грудь (по очереди)",
# Жим гантелей лежа
"Жим гантелей": "Жим гантелей лежа", "Жим гантелей": "Жим гантелей лежа",
"Жим гантелей 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей 60⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей 80⁰": "Жим гантелей сидя",
"Жим гантелей 90⁰": "Жим гантелей сидя",
"Жим гантелей вверх 80⁰": "Жим гантелей сидя",
"Жим гантелей вверх 90⁰": "Жим гантелей сидя",
"Жим гантелей вверх сидя 90⁰": "Жим гантелей сидя",
"Жим гантелей лежа": "Жим гантелей лежа", "Жим гантелей лежа": "Жим гантелей лежа",
"Жим гантелей лежа 15⁰": "Жим гантелей лежа", "Жим гантелей лежа 15⁰": "Жим гантелей лежа",
"Жим гантелей лежа 30⁰": "Жим гантелей лежа", "Жим гантелей лежа 30⁰": "Жим гантелей лежа",
@ -55,37 +131,64 @@ unique_apple_exercises_mapper = {
"Жим гантелей лёжа": "Жим гантелей лежа", "Жим гантелей лёжа": "Жим гантелей лежа",
"Жим гантелей лёжа на скамье": "Жим гантелей лежа", "Жим гантелей лёжа на скамье": "Жим гантелей лежа",
"Жим гантелей на скамье": "Жим гантелей лежа", "Жим гантелей на скамье": "Жим гантелей лежа",
"Жис гантелей лежа": "Жим гантелей лежа",
# Жим гантелей лежа (Под углом 45 градусов)
"Жим гантелей 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей 60⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей на скамье 45⁰": "Жим гантелей лежа (Под углом 45 градусов)", "Жим гантелей на скамье 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим сидя 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
# Жим гантелей сидя
"Жим гантелей 80⁰": "Жим гантелей сидя",
"Жим гантелей 90⁰": "Жим гантелей сидя",
"Жим гантелей вверх 80⁰": "Жим гантелей сидя",
"Жим гантелей вверх 90⁰": "Жим гантелей сидя",
"Жим гантелей вверх сидя 90⁰": "Жим гантелей сидя",
"Жим гантелей сидя": "Жим гантелей сидя", "Жим гантелей сидя": "Жим гантелей сидя",
"Жим гантелей сидя 80⁰": "Жим гантелей сидя", "Жим гантелей сидя 80⁰": "Жим гантелей сидя",
"Жим гантелей сидя 90⁰": "Жим гантелей сидя", "Жим гантелей сидя 90⁰": "Жим гантелей сидя",
"Жим гантелей сидя на скамейке": "Жим гантелей сидя", "Жим гантелей сидя на скамейке": "Жим гантелей сидя",
"Жим гантелей стоя": "Подъем гантелей на бицепс стоя", "Жим вверх 80⁰": "Жим гантелей сидя",
"Жим л груди в тренажёре": "Жим от груди сидя в тренажере (рычаги)", "Жим от плеч вверх сидя 80⁰": "Жим гантелей сидя",
"Жим лежа": "Жим штанги лежа", "Гантели на лавках": "Жим гантелей сидя",
# ГАК машина
"ГАК машина": "ГАК машина",
"Гакк машина": "ГАК машина",
"Гакк присед": "ГАК машина",
# Жим ногами
"Жим ногами": "Жим ногами", "Жим ногами": "Жим ногами",
"Жим ногами (3х15)": "Жим ногами", "Жим ногами (3х15)": "Жим ногами",
"Жим ногами (медленно вниз)": "Жим ногами", "Жим ногами (медленно вниз)": "Жим ногами",
"Жим ногами лежа": "Жим ногами", "Жим ногами лежа": "Жим ногами",
"Жим ногами медленно": "Жим ногами", "Жим ногами медленно": "Жим ногами",
"Жим от груди": "Жим штанги лежа",
"Жим от груди (сидя, тренажёр)": "Жим от груди сидя в тренажере (блоки)", # Жим от груди сидя в тренажере (рычаги)
"Жим от груди в тренажере сидя": "Жим от груди сидя в тренажере (блоки)", "Жим л груди в тренажёре": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди в тренажёре (кирпичики)": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя (блинчики)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя (блинчики)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя в тренажере (около трицепс машины)": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя в тренажере (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажере (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере рычаги": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажере рычаги": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя в тренажёре (блинчики)": "Жим от груди сидя в тренажере (блоки)",
"Жим от груди сидя в тренажёре (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажёре (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жис сидя в тренажёре": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди (сидя, тренажёр)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди в тренажере сидя": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере (около трицепс машины)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре (блинчики)": "Жим от груди сидя в тренажере (рычаги)",
"Силовой тренажер": "Жим от груди сидя в тренажере (рычаги)",
# Жим от плеч вверх в тренажере (рычаги)
"Жим в тренажере вверх": "Жим от плеч вверх в тренажере (рычаги)",
"Жим в тренажере на плечи блочный (3х12)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим в тренажёре на плечи (силовой тренажёр: жим сидя)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч (рычаги)": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч (рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх в тренажёре": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх в тренажёре": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх сидя": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх сидя": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх сидя 80⁰": "Жим гантелей сидя",
"Жим от плеч вверх сидя в тренажере": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх сидя в тренажере": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч сидя": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч сидя": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч сидя (рычаги)": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч сидя (рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
@ -93,32 +196,31 @@ unique_apple_exercises_mapper = {
"Жим от плеч сидя хз": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч сидя хз": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плечей вверх": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плечей вверх": "Жим от плеч вверх в тренажере (рычаги)",
"Жим плечами вверх сидя": "Жим от плеч вверх в тренажере (рычаги)", "Жим плечами вверх сидя": "Жим от плеч вверх в тренажере (рычаги)",
"Жим сидя 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим штанги 45⁰": "Жим штанги сидя (45 градусов)", # Жим штанги лежа
"Жим штанги в наклоне": "Тяга штанги в наклоне", "Жим лежа": "Жим штанги лежа",
"Жим от груди": "Жим штанги лежа",
"Жим штанги лежа": "Жим штанги лежа", "Жим штанги лежа": "Жим штанги лежа",
"Штанга": "Жим штанги лежа",
"Штанга лежа": "Жим штанги лежа",
# Жим штанги сидя (45 градусов)
"Жим штанги 45⁰": "Жим штанги сидя (45 градусов)",
"Жим штанги сидя 45⁰": "Жим штанги сидя (45 градусов)", "Жим штанги сидя 45⁰": "Жим штанги сидя (45 градусов)",
"Жим штанги стля": "Подъем штанги на бицепс стоя",
"Жим штанги стоя": "Подъем штанги на бицепс стоя", # Махи гантелей стоя
"Жис гантелей лежа": "Жим гантелей лежа", "Отведение рук в стороны с гантелями стоя": "Махи гантелей стоя",
"Жис сидя в тренажёре": "Жим от груди сидя в тренажере (рычаги)", "Отведение рук в стороны стоя с гантелями": "Махи гантелей стоя",
"Махи в наклоне с упором": "Разведение рук в стороны в наклоне (гантели)", "Отведение рук с гантелями стоя": "Махи гантелей стоя",
"Молот назад двумя руками": "Гантели молот", "Отведение рук стоя с гантелями": "Махи гантелей стоя",
"Молот трицепс стоя": "Гантели молот", "Разведение рук в стороны с гантелям": "Махи гантелей стоя",
"Молоты": "Гантели молот", "Разведение рук в стороны стоя": "Махи гантелей стоя",
"Наклонная тяга (скамья)": "Жим штанги лежа", "Разведение рук в стороны стоя с гантелями": "Махи гантелей стоя",
"Наклоны с прямой спиной": "Наклоны с прямой спиной", "Разведение рук стоя с гантелями": "Махи гантелей стоя",
"Разведение рук в стрроны": "Махи гантелей стоя",
# Отведение рук в кроссовере (по очереди)
"Отведение в кроссовере одной рукой": "Отведение рук в кроссовере (по очереди)", "Отведение в кроссовере одной рукой": "Отведение рук в кроссовере (по очереди)",
"Отведение в рук в наклоне": "Разведение рук в стороны в наклоне (гантели)",
"Отведение назад в пек дек": "Разведение рук в пек-дек",
"Отведение назад в тренажере пек дек": "Разведение рук в пек-дек",
"Отведение назад в тренажере пэк дэк (3х12)": "Разведение рук в пек-дек",
"Отведение рук в кроссовере": "Отведение рук в кроссовере (по очереди)",
"Отведение рук в стороны с гантелями стоя": "Разведение рук в стороны стоя (гантели)",
"Отведение рук в стороны стоя с гантелями": "Разведение рук в стороны стоя (гантели)",
"Отведение рук назад в пек дек": "Разведение рук в пек-дек",
"Отведение рук с гантелями стоя": "Разведение рук в стороны стоя (гантели)",
"Отведение рук стоя с гантелями": "Разведение рук в стороны стоя (гантели)",
"Отведение руки в кроссовере": "Отведение рук в кроссовере (по очереди)", "Отведение руки в кроссовере": "Отведение рук в кроссовере (по очереди)",
"Отведение руки в кроссовере (одной рукой)": "Отведение рук в кроссовере (по очереди)", "Отведение руки в кроссовере (одной рукой)": "Отведение рук в кроссовере (по очереди)",
"Отведение руки в кроссовере (по одной руке)": "Отведение рук в кроссовере (по очереди)", "Отведение руки в кроссовере (по одной руке)": "Отведение рук в кроссовере (по очереди)",
@ -128,75 +230,107 @@ unique_apple_exercises_mapper = {
"Отведение руки в сторону в кроссовере (каждая рука)": "Отведение рук в кроссовере (по очереди)", "Отведение руки в сторону в кроссовере (каждая рука)": "Отведение рук в кроссовере (по очереди)",
"Отведение руки стоя": "Отведение рук в кроссовере (по очереди)", "Отведение руки стоя": "Отведение рук в кроссовере (по очереди)",
"Отведение руки стоя снизу вверх": "Отведение рук в кроссовере (по очереди)", "Отведение руки стоя снизу вверх": "Отведение рук в кроссовере (по очереди)",
"Отжимания": "Отжимания",
"Отжимания на брусьях": "Брусья", # Отжимание на трицепс в кроссовере
"Отжимания на брусьях узким хватом": "Брусья", "Разведение рук в кроссовере на трицепс": "Отжимание на трицепс в кроссовере",
"Отжимания на брусьях широким хватом": "Брусья", "Разгибание рук в кроссовере": "Отжимание на трицепс в кроссовере",
"Пек дек (отведение назад)": "Разведение рук в пек-дек", "Разгибание рук в тренажёре для трицепса": "Отжимание на трицепс в кроссовере",
"Пек дек разведение рук в стороны": "Разведение рук в пек-дек", "Разгибание рук в трицепс машине (3х10)": "Отжимание на трицепс в кроссовере",
"Разгибание рук стоя с канатом": "Отжимание на трицепс в кроссовере",
"Трацепс": "Отжимание на трицепс в кроссовере",
"Трицепс вниз у стойки": "Отжимание на трицепс в кроссовере",
# Подтягивания
"Подтягивание на заднюю дельту в петлях": "Подтягивания", "Подтягивание на заднюю дельту в петлях": "Подтягивания",
"Подтягивание ног на ручнике": "Подъем ног на турнике",
"Подтягивание ног на турнике": "Подъем ног на турнике",
"Подтягивание ног прямых вися на турнике": "Подъем ног на турнике",
"Подтягивания": "Подтягивания", "Подтягивания": "Подтягивания",
"Подтягивания в гравитроне": "Подтягивания", "Подтягивания в гравитроне": "Подтягивания",
"Подтягивания на резинке": "Подтягивания", "Подтягивания на резинке": "Подтягивания",
"Подтягивания обратным хватом": "Подтягивания обратным хватом",
"Подтяотвания": "Подтягивания", "Подтяотвания": "Подтягивания",
"Подъем коленей на турнике": "Подъем ног на турнике",
"Подъем ног на турнике": "Подъем ног на турнике", # Подтягивания обратным хватом
"Подъем штанги стоя": "Подъем штанги на бицепс стоя", "Подтягивания обратным хватом": "Подтягивания обратным хватом",
"Подъёмы ног на турнике": "Подъем ног на турнике",
"Подьем штанги стоя": "Подъем штанги на бицепс стоя", # Подъем гантелей на бицепс стоя
"Присд на одну ногу (по очереди)": "Выпады", "Гантели": "Подъем гантелей на бицепс стоя",
"Присед на одну ногу": "Выпады", "Жим гантелей стоя": "Подъем гантелей на бицепс стоя",
"Присед на одну ногу (каждая)": "Выпады", "Сгибание рук с гантелями стоя": "Подъем гантелей на бицепс стоя",
"Приседания": "Приседания", "Сгибание рук с гантелями стоя ям": "Подъем гантелей на бицепс стоя",
"Приседания на одной ноге": "Выпады",
"Разведение рук в кроссовере на трицепс": "Отжимание на трицепс в кроссовере", # Подъём ног в висе
"Разведение рук в наклоне": "Разведение рук в стороны в наклоне (гантели)", "Подтягивание ног на ручнике": "Подъём ног в висе",
"Разведение рук в налоне": "Разведение рук в стороны в наклоне (гантели)", "Подтягивание ног на турнике": "Подъём ног в висе",
"Подтягивание ног прямых вися на турнике": "Подъём ног в висе",
"Подъем коленей на турнике": "Подъём ног в висе",
"Подъем ног на турнике": "Подъём ног в висе",
"Подъёмы ног на турнике": "Подъём ног в висе",
# Подъем штанги стоя
"Жим штанги стля": "Подъем штанги стоя",
"Жим штанги стоя": "Подъем штанги стоя",
"Подьем штанги стоя": "Подъем штанги стоя",
"Сгибание со штангой стоя (3х12)": "Подъем штанги стоя",
"Сгибания рук со штангой стоя": "Подъем штанги стоя",
"Тяга штанги стоя": "Подъем штанги стоя",
"Штанга стоя": "Подъем штанги стоя",
# Разведение рук в пек-дек
"Отведение назад в пек дек": "Разведение рук в пек-дек",
"Отведение назад в тренажере пек дек": "Разведение рук в пек-дек",
"Отведение назад в тренажере пэк дэк (3х12)": "Разведение рук в пек-дек",
"Отведение рук назад в пек дек": "Разведение рук в пек-дек",
"Пек дек (отведение назад)": "Разведение рук в пек-дек",
"Пек дек разведение рук в стороны": "Разведение рук в пек-дек",
"Разведение рук в пек дек": "Разведение рук в пек-дек", "Разведение рук в пек дек": "Разведение рук в пек-дек",
"Разведение рук в стороны": "Разведение рук в пек-дек", "Разведение рук в стороны": "Разведение рук в пек-дек",
"Разведение рук в стороны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны (дельта-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны (пек дек)": "Разведение рук в пек-дек", "Разведение рук в стороны (пек дек)": "Разведение рук в пек-дек",
"Разведение рук в стороны в наклоне 90⁰": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в наклоне с гантелями": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в пекдек": "Разведение рук в пек-дек", "Разведение рук в стороны в пекдек": "Разведение рук в пек-дек",
"Разведение рук в стороны в тренажёре": "Разведение рук в пек-дек", "Разведение рук в стороны в тренажёре": "Разведение рук в пек-дек",
"Разведение рук в стороны в тренажёре Дж": "Разведение рук в пек-дек", "Разведение рук в стороны в тренажёре Дж": "Разведение рук в пек-дек",
"Разведение рук в стороны пек дек": "Разведение рук в пек-дек", "Разведение рук в стороны пек дек": "Разведение рук в пек-дек",
"Разведение рук в стороны с гантелям": "Разведение рук в стороны стоя (гантели)",
"Разведение рук в стороны стоя": "Разведение рук в стороны стоя (гантели)",
"Разведение рук в стороны стоя с гантелями": "Разведение рук в стороны стоя (гантели)",
"Разведение рук в стрроны": "Разведение рук в стороны стоя (гантели)",
"Разведение рук назад в пек дек": "Разведение рук в пек-дек", "Разведение рук назад в пек дек": "Разведение рук в пек-дек",
"Разведение рук назад пек дек": "Разведение рук в пек-дек", "Разведение рук назад пек дек": "Разведение рук в пек-дек",
"Разведение рук пек дек": "Разведение рук в пек-дек", "Разведение рук пек дек": "Разведение рук в пек-дек",
"Разведение рук сидя": "Разведение рук в пек-дек", "Разведение рук сидя": "Разведение рук в пек-дек",
"Разведение рук сидя с прямыми руками в тренажере": "Разведение рук в пек-дек", "Разведение рук сидя с прямыми руками в тренажере": "Разведение рук в пек-дек",
"Разведение рук стоя с гантелями": "Разведение рук в стороны стоя (гантели)",
"Разгибание из за головы": "Гантели молот", # Разведение рук в стороны (дельт-машина)
"Разгибание кик-бек": "Разгибание рук в наклоне (кик-бек)", "Разведение рук в стороны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны (дельта-машина)": "Разведение рук в стороны (дельт-машина)",
"Разгибание прямых рук сидя": "Разведение рук в стороны (дельт-машина)",
# Разведение рук в стороны в наклоне (гантели)
"Махи в наклоне с упором": "Разведение рук в стороны в наклоне (гантели)",
"Отведение в рук в наклоне": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в наклоне": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в налоне": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в наклоне 90⁰": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в наклоне с гантелями": "Разведение рук в стороны в наклоне (гантели)",
"Разгибание локтей в наклоне": "Разведение рук в стороны в наклоне (гантели)", "Разгибание локтей в наклоне": "Разведение рук в стороны в наклоне (гантели)",
"Сведение на скамейке": "Разведение рук в стороны в наклоне (гантели)",
# Разгибание ног сидя
"Разгибание ног": "Разгибание ног сидя", "Разгибание ног": "Разгибание ног сидя",
"Разгибание ног сидя": "Разгибание ног сидя", "Разгибание ног сидя": "Разгибание ног сидя",
"Разгибание ног сидя (4х10)": "Разгибание ног сидя", "Разгибание ног сидя (4х10)": "Разгибание ног сидя",
"Разгибание прямых рук сидя": "Разведение рук в стороны (дельт-машина)",
"Разгибание рук в кроссовере": "Отжимание на трицепс в кроссовере", # Разгибание рук в наклоне (кик-бек)
"Разгибание рук в тренажёре для трицепса": "Отжимание на трицепс в кроссовере", "Разгибание кик-бек": "Разгибание рук в наклоне (кик-бек)",
"Разгибание рук в трицепс машине (3х10)": "Отжимание на трицепс в кроссовере",
# Разгибание рук сидя (трицепс машина)
"Разгибание рук сидя": "Разгибание рук сидя (трицепс машина)", "Разгибание рук сидя": "Разгибание рук сидя (трицепс машина)",
"Разгибание рук сидя (локти в стороны)": "Разгибание рук сидя (трицепс машина)", "Разгибание рук сидя (локти в стороны)": "Разгибание рук сидя (трицепс машина)",
"Разгибание рук сидя (трицепс машина)": "Разгибание рук сидя (трицепс машина)", "Разгибание рук сидя (трицепс машина)": "Разгибание рук сидя (трицепс машина)",
"Разгибание рук стоя с канатом": "Отжимание на трицепс в кроссовере", "Трицепс машина": "Разгибание рук сидя (трицепс машина)",
"Разгибание спины": "Разгибание спины", "Трицепс сидя": "Разгибание рук сидя (трицепс машина)",
"Трицепс-машина (разгибание рук сидя)": "Разгибание рук сидя (трицепс машина)",
# Румынская тяга
"Румынская тяга": "Румынская тяга", "Румынская тяга": "Румынская тяга",
"Румынская тяга с гантелями": "Румынская тяга", "Румынская тяга с гантелями": "Румынская тяга",
"Румынская тяна": "Румынская тяга", "Румынская тяна": "Румынская тяга",
"Румынская тяоа": "Румынская тяга", "Румынская тяоа": "Румынская тяга",
"Сведение на скамейке":"Разведение рук в стороны в наклоне (гантели)",
# Сведение рук в пек-дек
"Сведение рук в пек дек": "Сведение рук в пек-дек", "Сведение рук в пек дек": "Сведение рук в пек-дек",
"Сведение рук в пек дек зэ": "Сведение рук в пек-дек", "Сведение рук в пек дек зэ": "Сведение рук в пек-дек",
"Сведение рук в тренажере": "Сведение рук в пек-дек", "Сведение рук в тренажере": "Сведение рук в пек-дек",
@ -206,37 +340,39 @@ unique_apple_exercises_mapper = {
"Сведение рук сидя": "Сведение рук в пек-дек", "Сведение рук сидя": "Сведение рук в пек-дек",
"Сведение рук сидя пек дек": "Сведение рук в пек-дек", "Сведение рук сидя пек дек": "Сведение рук в пек-дек",
"Сведения рук пек дек": "Сведение рук в пек-дек", "Сведения рук пек дек": "Сведение рук в пек-дек",
"Сгибание гантелей в стиле молот одновременно": "Гантели молот",
"Сгибание ног": "Сгибание ног сидя", # Сгибание ног лежа
"Сгибание ног лежа": "Сгибание ног лежа", "Сгибание ног лежа": "Сгибание ног лежа",
"Сгибание ног лежа (медленно разгибать)": "Сгибание ног лежа", "Сгибание ног лежа (медленно разгибать)": "Сгибание ног лежа",
"Сгибание ног лёжа": "Сгибание ног лежа", "Сгибание ног лёжа": "Сгибание ног лежа",
"Сгибания ног лежа": "Сгибание ног лежа",
# Сгибание ног сидя
"Сгибание ног": "Сгибание ног сидя",
"Сгибание ног сидя": "Сгибание ног сидя", "Сгибание ног сидя": "Сгибание ног сидя",
"Сгибание ног сидя (3х12)": "Сгибание ног сидя", "Сгибание ног сидя (3х12)": "Сгибание ног сидя",
"Сгибание рук с гантелями стоя": "Подъем гантелей на бицепс стоя",
"Сгибание рук с гантелями стоя ям": "Подъем гантелей на бицепс стоя", # Скручивания
"Сгибание со штангой стоя (3х12)": "Подъем штанги на бицепс стоя",
"Сгибания ног лежа": "Сгибание ног лежа",
"Сгибания рук со штангой стоя": "Подъем штанги на бицепс стоя",
"Силовой тренажер": "Жим от груди сидя в тренажере (блоки)",
"Скручивания на мяче": "Скручивания", "Скручивания на мяче": "Скручивания",
"Трацепс": "Отжимание на трицепс в кроссовере",
"Трицепс вниз у стойки": "Отжимание на трицепс в кроссовере", # Тяга Т-грифа
"Трицепс машина": "Разгибание рук сидя (трицепс машина)",
"Трицепс сидя": "Разгибание рук сидя (трицепс машина)",
"Трицепс-машина (разгибание рук сидя)": "Разгибание рук сидя (трицепс машина)",
"Тяга Т гриф": "Тяга Т-грифа", "Тяга Т гриф": "Тяга Т-грифа",
"Тяга в наклоне": "Тяга штанги в наклоне", "Тяга т грифа": "Тяга Т-грифа",
# Тяга вертикального блока
"Тяга вертикального блока": "Тяга вертикального блока", "Тяга вертикального блока": "Тяга вертикального блока",
"Тяга вертикального блока обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга верхнего блока": "Тяга вертикального блока", "Тяга верхнего блока": "Тяга вертикального блока",
"Тяга вниз сидя": "Тяга вертикального блока",
# Тяга вертикального блока (обратный хват)
"Тяга вертикального блока обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга верхнего блока обратным хватом": "Тяга вертикального блока (обратный хват)", "Тяга верхнего блока обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга вниз обратным хватом сидя (фитнес станция)": "Тяга вертикального блока (обратный хват)", "Тяга вниз обратным хватом сидя (фитнес станция)": "Тяга вертикального блока (обратный хват)",
"Тяга вниз сидя": "Тяга вертикального блока", "Тяга сидя вниз обратным хватом": "Тяга вертикального блока (обратный хват)",
# Тяга гантелей в наклоне (по очереди)
"Тяга гантелей в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантелей в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантелей к поясу лёжа на скамейке 40⁰": "Тяга гантелей на скамье",
"Тяга гантелей к поясу стоя в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантелей к поясу стоя в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантелей лежа на животе на скамье 80°": "Тяга гантелей на скамье",
"Тяга гантели в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне (гантеля поочередно)": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне (гантеля поочередно)": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне (к поясу одной рукой)": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне (к поясу одной рукой)": "Тяга гантелей в наклоне (по очереди)",
@ -249,324 +385,43 @@ unique_apple_exercises_mapper = {
"Тяга гантели к поясу одной рукой у стойки": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели к поясу одной рукой у стойки": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели одной рукой в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели одной рукой в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели одной рукой в стойке": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели одной рукой в стойке": "Тяга гантелей в наклоне (по очереди)",
# Тяга гантелей на скамье
"Гантели к поясу лежа на скамье": "Тяга гантелей на скамье",
"Тяга гантелей к поясу лёжа на скамейке 40⁰": "Тяга гантелей на скамье",
"Тяга гантелей лежа на животе на скамье 80°": "Тяга гантелей на скамье",
# Тяга горизонтального блока
"Горизонтальна тяга на себя": "Тяга горизонтального блока",
"Горизонтальная тяга блока": "Тяга горизонтального блока",
"Тяга горизонтально блока": "Тяга горизонтального блока", "Тяга горизонтально блока": "Тяга горизонтального блока",
"Тяга горизонтального блок": "Тяга горизонтального блока", "Тяга горизонтального блок": "Тяга горизонтального блока",
"Тяга горизонтального блока": "Тяга горизонтального блока", "Тяга горизонтального блока": "Тяга горизонтального блока",
"Тяга горизонтального блока (1 рука)": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока дю": "Тяга горизонтального блока", "Тяга горизонтального блока дю": "Тяга горизонтального блока",
"Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока одной рукой (поочерёдно)": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блокай": "Тяга горизонтального блока", "Тяга горизонтального блокай": "Тяга горизонтального блока",
"Тяга на себя": "Тяга горизонтального блока", "Тяга на себя": "Тяга горизонтального блока",
"Тяга на себя с упором в грудь": "Гребная тяга с упором в грудь",
"Тяга на себя сидя": "Тяга горизонтального блока", "Тяга на себя сидя": "Тяга горизонтального блока",
"Тяга ногами": "Жим ногами", "тяга горизонтального блока": "Тяга горизонтального блока",
"Тяга ногами вниз": "Жим ногами",
"Тяга обратным хватом штанги к поясу": "Тяга штанги в наклоне", # Тяга горизонтального блока (по очереди)
"Тяга горизонтального блока (1 рука)": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока одной рукой (поочерёдно)": "Тяга горизонтального блока (по очереди)",
"Тяга одной рукой в кроссовере": "Тяга горизонтального блока (по очереди)", "Тяга одной рукой в кроссовере": "Тяга горизонтального блока (по очереди)",
"Тяга одной рукой в кроссовере на колене": "Тяга горизонтального блока (по очереди)", "Тяга одной рукой в кроссовере на колене": "Тяга горизонтального блока (по очереди)",
"Тяга руки в кроссовере до": "Тяга горизонтального блока (по очереди)", "Тяга руки в кроссовере до": "Тяга горизонтального блока (по очереди)",
"Тяга рычага к груди": "Тяга горизонтального блока (по очереди)", "Тяга рычага к груди": "Тяга горизонтального блока (по очереди)",
"Тяга сидя вниз обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга т грифа": "Тяга Т-грифа", # Тяга штанги в наклоне
"Тяга в наклоне": "Тяга штанги в наклоне",
"Тяга штанги в наклоне": "Тяга штанги в наклоне", "Тяга штанги в наклоне": "Тяга штанги в наклоне",
"Тяга штанги в наклоне к груди": "Тяга штанги в наклоне", "Тяга штанги в наклоне к груди": "Тяга штанги в наклоне",
"Тяга штанги в наклоне к поясу": "Тяга штанги в наклоне", "Тяга штанги в наклоне к поясу": "Тяга штанги в наклоне",
"Штанга в наклоне": "Тяга штанги в наклоне",
# Тяга штанги в наклоне (обратным хватом)
"Тяга обратным хватом штанги к поясу": "Тяга штанги в наклоне (обратным хватом)",
"Тяга штанги в наклоне к поясу обратным хватом": "Тяга штанги в наклоне (обратным хватом)", "Тяга штанги в наклоне к поясу обратным хватом": "Тяга штанги в наклоне (обратным хватом)",
"Тяга штанги в наклоне обратным хватом": "Тяга штанги в наклоне (обратным хватом)", "Тяга штанги в наклоне обратным хватом": "Тяга штанги в наклоне (обратным хватом)",
"Тяга штанги к поясу в наклоне": "Тяга штанги в наклоне (обратным хватом)", "Тяга штанги к поясу в наклоне": "Тяга штанги в наклоне (обратным хватом)",
"Тяга штанги к поясу стоя": "Подъем штанги на бицепс стоя",
"Тяга штанги стоя": "Подъем штанги на бицепс стоя",
"Штанга": "Жим штанги лежа",
"Штанга в наклоне": "Тяга штанги в наклоне",
"Штанга лежа": "Жим штанги лежа",
"Штанга стоя": "Подъем штанги на бицепс стоя",
"тяга горизонтального блока": "Тяга горизонтального блока",
}
backup = {
"Батерфляй": 1,
"Баттерфляй": 16,
"Баттерфляй дю": 1,
"Бицепс на лавке 45⁰": 1,
"Болгарский присед": 1,
"Брусья": 1,
"Брусья (гравитрон)": 1,
"Брусья в тренажере": 1,
"Брусья прямые ноги": 1,
"Брусья узким хватом": 1,
"Брусья узким хватом за": 1,
"Брусья широким хватом": 3,
"Брусья-прямые ноги": 1,
"ГАК машина": 1,
"Гакк машина": 3,
"Гакк присед": 2,
"Гантели": 1,
"Гантели к поясу лежа на скамье": 1,
"Гантели молот": 1,
"Гантели на лавках": 1,
"Гантели-молот": 1,
"Гипекстензия": 1,
"Гиперэкстензия": 13,
"Горизонтальна тяга на себя": 1,
"Горизонтальная тяга блока": 1,
"Гравитрон": 30,
"Гравитрор": 1,
"Гребная тяга": 2,
"Гребная тяга с упором в грудь": 7,
"Гребная тяга с упором в грудь как": 1,
"Гребная тяга с упором на грудь": 5,
"Гребная тяга сидя (одна рука)": 1,
"Гребная тяга сидя одной рукой": 1,
"Гребная тяга сидя с упором в грудт": 1,
"Гребная тяга сидя с упором в грудь": 1,
"Гребная тяга сидя с упором в грудь (одной рукой рычаг)": 1,
"Гребная тяга\xa0 упором в грудьс": 1,
"Жим в тренажере вверх": 1,
"Жим в тренажере на плечи блочный (3х12)": 1,
"Жим в тренажёре на плечи (силовой тренажёр: жим сидя)": 1,
"Жим вверх 80⁰": 1,
"Жим гантелей": 3,
"Жим гантелей 45⁰": 3,
"Жим гантелей 60⁰": 1,
"Жим гантелей 80⁰": 3,
"Жим гантелей 90⁰": 3,
"Жим гантелей вверх 80⁰": 1,
"Жим гантелей вверх 90⁰": 1,
"Жим гантелей вверх сидя 90⁰": 1,
"Жим гантелей лежа": 10,
"Жим гантелей лежа 15⁰": 1,
"Жим гантелей лежа 30⁰": 1,
"Жим гантелей лежа на скамье": 1,
"Жим гантелей лёжа": 3,
"Жим гантелей лёжа на скамье": 2,
"Жим гантелей на скамье": 1,
"Жим гантелей на скамье 45⁰": 1,
"Жим гантелей сидя": 3,
"Жим гантелей сидя 80⁰": 2,
"Жим гантелей сидя 90⁰": 2,
"Жим гантелей сидя на скамейке": 1,
"Жим гантелей стоя": 2,
"Жим л груди в тренажёре": 1,
"Жим лежа": 6,
"Жим ногами": 15,
"Жим ногами (3х15)": 1,
"Жим ногами (медленно вниз)": 1,
"Жим ногами лежа": 1,
"Жим ногами медленно": 1,
"Жим от груди": 2,
"Жим от груди (сидя, тренажёр)": 1,
"Жим от груди в тренажере сидя": 1,
"Жим от груди в тренажёре (кирпичики)": 1,
"Жим от груди сидя": 5,
"Жим от груди сидя (блинчики)": 2,
"Жим от груди сидя в тренажере": 3,
"Жим от груди сидя в тренажере (около трицепс машины)": 1,
"Жим от груди сидя в тренажере (рычаги)": 1,
"Жим от груди сидя в тренажере рычаги": 1,
"Жим от груди сидя в тренажёре": 3,
"Жим от груди сидя в тренажёре (блинчики)": 1,
"Жим от груди сидя в тренажёре (рычаги)": 1,
"Жим от плеч (рычаги)": 1,
"Жим от плеч вверх": 12,
"Жим от плеч вверх в тренажёре": 1,
"Жим от плеч вверх сидя": 1,
"Жим от плеч вверх сидя 80⁰": 1,
"Жим от плеч вверх сидя в тренажере": 1,
"Жим от плеч сидя": 2,
"Жим от плеч сидя (рычаги)": 3,
"Жим от плеч сидя зэ": 1,
"Жим от плеч сидя хз": 1,
"Жим от плечей вверх": 1,
"Жим плечами вверх сидя": 1,
"Жим сидя 45⁰": 1,
"Жим штанги 45⁰": 2,
"Жим штанги в наклоне": 1,
"Жим штанги лежа": 1,
"Жим штанги сидя 45⁰": 1,
"Жим штанги стля": 1,
"Жим штанги стоя": 1,
"Жис гантелей лежа": 1,
"Жис сидя в тренажёре": 1,
"Махи в наклоне с упором": 1,
"Молот назад двумя руками": 1,
"Молот трицепс стоя": 1,
"Молоты": 1,
"Наклонная тяга (скамья)": 1,
"Наклоны с прямой спиной": 1,
"Отведение в кроссовере одной рукой": 1,
"Отведение в рук в наклоне": 1,
"Отведение назад в пек дек": 1,
"Отведение назад в тренажере пек дек": 1,
"Отведение назад в тренажере пэк дэк (3х12)": 1,
"Отведение рук в кроссовере": 1,
"Отведение рук в стороны с гантелями стоя": 1,
"Отведение рук в стороны стоя с гантелями": 1,
"Отведение рук назад в пек дек": 1,
"Отведение рук с гантелями стоя": 2,
"Отведение рук стоя с гантелями": 1,
"Отведение руки в кроссовере": 2,
"Отведение руки в кроссовере (одной рукой)": 1,
"Отведение руки в кроссовере (по одной руке)": 2,
"Отведение руки в кроссовере на каждую руку": 1,
"Отведение руки в кроссовкре": 1,
"Отведение руки в сторону": 1,
"Отведение руки в сторону в кроссовере (каждая рука)": 1,
"Отведение руки стоя": 2,
"Отведение руки стоя снизу вверх": 1,
"Отжимания": 16,
"Отжимания на брусьях": 7,
"Отжимания на брусьях узким хватом": 1,
"Отжимания на брусьях широким хватом": 3,
"Пек дек (отведение назад)": 1,
"Пек дек разведение рук в стороны": 1,
"Подтягивание на заднюю дельту в петлях": 1,
"Подтягивание ног на ручнике": 1,
"Подтягивание ног на турнике": 2,
"Подтягивание ног прямых вися на турнике": 1,
"Подтягивания": 8,
"Подтягивания в гравитроне": 1,
"Подтягивания на резинке": 1,
"Подтягивания обратным хватом": 8,
"Подтяотвания": 1,
"Подъем коленей на турнике": 1,
"Подъем ног на турнике": 4,
"Подъем штанги стоя": 2,
"Подъёмы ног на турнике": 1,
"Подьем штанги стоя": 1,
"Присд на одну ногу (по очереди)": 1,
"Присед на одну ногу": 1,
"Присед на одну ногу (каждая)": 1,
"Приседания": 16,
"Приседания на одной ноге": 1,
"Разведение рук в кроссовере на трицепс": 1,
"Разведение рук в наклоне": 2,
"Разведение рук в налоне": 1,
"Разведение рук в пек дек": 6,
"Разведение рук в стороны": 14,
"Разведение рук в стороны (дельт-машина)": 3,
"Разведение рук в стороны (дельта-машина)": 1,
"Разведение рук в стороны (пек дек)": 1,
"Разведение рук в стороны в наклоне 90⁰": 2,
"Разведение рук в стороны в наклоне с гантелями": 1,
"Разведение рук в стороны в пекдек": 1,
"Разведение рук в стороны в тренажёре": 1,
"Разведение рук в стороны в тренажёре Дж": 1,
"Разведение рук в стороны пек дек": 2,
"Разведение рук в стороны с гантелям": 1,
"Разведение рук в стороны стоя": 2,
"Разведение рук в стороны стоя с гантелями": 2,
"Разведение рук в стрроны": 1,
"Разведение рук назад в пек дек": 1,
"Разведение рук назад пек дек": 1,
"Разведение рук пек дек": 4,
"Разведение рук сидя": 3,
"Разведение рук сидя с прямыми руками в тренажере": 1,
"Разведение рук стоя с гантелями": 1,
"Разгибание из за головы": 1,
"Разгибание кик-бек": 1,
"Разгибание локтей в наклоне": 1,
"Разгибание ног": 5,
"Разгибание ног сидя": 22,
"Разгибание ног сидя (4х10)": 1,
"Разгибание прямых рук сидя": 1,
"Разгибание рук в кроссовере": 1,
"Разгибание рук в тренажёре для трицепса": 1,
"Разгибание рук в трицепс машине (3х10)": 1,
"Разгибание рук сидя": 5,
"Разгибание рук сидя (локти в стороны)": 1,
"Разгибание рук сидя (трицепс машина)": 1,
"Разгибание рук стоя с канатом": 1,
"Разгибание спины": 1,
"Румынская тяга": 5,
"Румынская тяга с гантелями": 1,
"Румынская тяна": 2,
"Румынская тяоа": 1,
"Сведение на скамейке": 1,
"Сведение рук в пек дек": 8,
"Сведение рук в пек дек зэ": 1,
"Сведение рук в тренажере": 1,
"Сведение рук в тренажёре пёк дек": 1,
"Сведение рук пек дек": 7,
"Сведение рук пек дек (бабочка)": 1,
"Сведение рук сидя": 3,
"Сведение рук сидя пек дек": 1,
"Сведения рук пек дек": 1,
"Сгибание гантелей в стиле молот одновременно": 1,
"Сгибание ног": 2,
"Сгибание ног лежа": 14,
"Сгибание ног лежа (медленно разгибать)": 1,
"Сгибание ног лёжа": 2,
"Сгибание ног сидя": 8,
"Сгибание ног сидя (3х12)": 1,
"Сгибание рук с гантелями стоя": 1,
"Сгибание рук с гантелями стоя ям": 1,
"Сгибание со штангой стоя (3х12)": 1,
"Сгибания ног лежа": 1,
"Сгибания рук со штангой стоя": 1,
"Силовой тренажер": 1,
"Скручивания на мяче": 2,
"Трацепс": 1,
"Трицепс вниз у стойки": 1,
"Трицепс машина": 4,
"Трицепс сидя": 1,
"Трицепс-машина (разгибание рук сидя)": 1,
"Тяга Т гриф": 1,
"Тяга в наклоне": 1,
"Тяга вертикального блока": 15,
"Тяга вертикального блока обратным хватом": 5,
"Тяга верхнего блока": 3,
"Тяга верхнего блока обратным хватом": 1,
"Тяга вниз обратным хватом сидя (фитнес станция)": 1,
"Тяга вниз сидя": 2,
"Тяга гантелей в наклоне": 3,
"Тяга гантелей к поясу лёжа на скамейке 40⁰": 1,
"Тяга гантелей к поясу стоя в наклоне": 1,
"Тяга гантелей лежа на животе на скамье 80°": 1,
"Тяга гантели в наклоне": 4,
"Тяга гантели в наклоне (гантеля поочередно)": 1,
"Тяга гантели в наклоне (к поясу одной рукой)": 1,
"Тяга гантели в наклоне одной рукой": 1,
"Тяга гантели к корпусу в наклоне же": 1,
"Тяга гантели к поясу": 1,
"Тяга гантели к поясу в наклоне": 1,
"Тяга гантели к поясу в наклоне одной рукой": 1,
"Тяга гантели к поясу в стойке": 1,
"Тяга гантели к поясу одной рукой у стойки": 1,
"Тяга гантели одной рукой в наклоне": 1,
"Тяга гантели одной рукой в стойке": 1,
"Тяга горизонтально блока": 1,
"Тяга горизонтального блок": 1,
"Тяга горизонтального блока": 14,
"Тяга горизонтального блока (1 рука)": 1,
"Тяга горизонтального блока дю": 1,
"Тяга горизонтального блока одной рукой": 4,
"Тяга горизонтального блока одной рукой (поочерёдно)": 1,
"Тяга горизонтального блокай": 1,
"Тяга на себя": 3,
"Тяга на себя с упором в грудь": 1,
"Тяга на себя сидя": 3,
"Тяга ногами": 1,
"Тяга ногами вниз": 1,
"Тяга обратным хватом штанги к поясу": 1,
"Тяга одной рукой в кроссовере": 1,
"Тяга одной рукой в кроссовере на колене": 1,
"Тяга руки в кроссовере до": 1,
"Тяга рычага к груди": 1,
"Тяга сидя вниз обратным хватом": 1,
"Тяга т грифа": 1,
"Тяга штанги в наклоне": 7,
"Тяга штанги в наклоне к груди": 1,
"Тяга штанги в наклоне к поясу": 1,
"Тяга штанги в наклоне к поясу обратным хватом": 1,
"Тяга штанги в наклоне обратным хватом": 1,
"Тяга штанги к поясу в наклоне": 1,
"Тяга штанги к поясу стоя": 1,
"Тяга штанги стоя": 1,
"Штанга": 2,
"Штанга в наклоне": 1,
"Штанга лежа": 1,
"Штанга стоя": 1,
"тяга горизонтального блока": 1,
} }

View file

@ -1,7 +1,7 @@
import os import os
from typing import List, Tuple, Dict, Optional from typing import List, Tuple, Dict, Optional
from app.core.models.training import Approach, Exercise, Training from app.core.dto.training import ApproachDTO, ExerciseDTO, TrainingDTO
from app.core.utils.date_refactor import parse_training_date from app.core.utils.date_refactor import parse_training_date
@ -25,7 +25,7 @@ class BaseNotesParser:
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) -> ExerciseDTO:
"""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
@ -45,24 +45,24 @@ class BaseNotesParser:
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)):
approach = Approach(weight=0.0, reps=reps_list[rep_index]) approach = ApproachDTO(weight=0.0, reps=reps_list[rep_index])
approaches.append(approach) approaches.append(approach)
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( approach = ApproachDTO(
weight=weight_list[weight_pointer], reps=reps_list[rep_index] 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 = ExerciseDTO(
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) -> ExerciseDTO:
"""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("|")][ stripped: List[str] = [entry.strip() for entry in exercise_line.split("|")][
1:-1 1:-1
@ -90,11 +90,11 @@ class BaseNotesParser:
"""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) -> TrainingDTO:
"""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 TrainingDTO(date=parse_training_date(date_str), exercises=[])
def parse_training_data(self) -> List[Training]: def parse_training_data(self) -> List[TrainingDTO]:
"""Main parsing method. Override for specific parsing logic.""" """Main parsing method. Override for specific parsing logic."""
training_data = self.filter_training_data( training_data = self.filter_training_data(
self.read_data_file(self.data_file_name) self.read_data_file(self.data_file_name)
@ -120,7 +120,7 @@ class BaseNotesParser:
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]: def apply_exercise_mapping(self, trainings: List[TrainingDTO]) -> List[TrainingDTO]:
"""Apply exercise name mapping to all trainings.""" """Apply exercise name mapping to all trainings."""
for training in trainings: for training in trainings:
if not training or not training.exercises: if not training or not training.exercises:
@ -133,7 +133,7 @@ class BaseNotesParser:
exercise.name = mapped_name exercise.name = mapped_name
return trainings return trainings
def parse_and_map_training_data(self) -> List[Training]: def parse_and_map_training_data(self) -> List[TrainingDTO]:
"""Parse training data and apply exercise mapping.""" """Parse training data and apply exercise mapping."""
trainings = self.parse_training_data() trainings = self.parse_training_data()
return self.apply_exercise_mapping(trainings) return self.apply_exercise_mapping(trainings)

View file

@ -1,7 +1,7 @@
import re import re
from typing import List, Tuple from typing import List, Tuple
from app.core.models.training import Training from app.core.dto.training import TrainingDTO
from app.core.parsers.obsidian_mapper import obsidian_unique_exercies_mapping from app.core.parsers.obsidian_mapper import obsidian_unique_exercies_mapping
from app.core.parsers.base import BaseNotesParser from app.core.parsers.base import BaseNotesParser
@ -35,7 +35,7 @@ class ObsidianNotesParser(BaseNotesParser):
return True, date, trainer, year_count return True, date, trainer, year_count
return False, "", "", "" return False, "", "", ""
def parse(self, data: str) -> List[Training]: def parse(self, data: str) -> List[TrainingDTO]:
"""Parse Obsidian training data from string input.""" """Parse Obsidian training data from string input."""
# Override the data file reading with direct string input # Override the data file reading with direct string input
original_method = self.read_data_file original_method = self.read_data_file
@ -49,13 +49,13 @@ class ObsidianNotesParser(BaseNotesParser):
self.read_data_file = original_method self.read_data_file = original_method
def parse_training_data() -> List[Training]: def parse_training_data() -> List[TrainingDTO]:
"""Parse Obsidian Notes training data.""" """Parse Obsidian Notes training data."""
parser = ObsidianNotesParser() parser = ObsidianNotesParser()
return parser.parse_training_data() return parser.parse_training_data()
def remap_unique_exercises(obsidian_trainings: List[Training]) -> List[Training]: def remap_unique_exercises(obsidian_trainings: List[TrainingDTO]) -> List[TrainingDTO]:
"""Remap exercise names using Obsidian-specific mapping (deprecated - use parser.parse_and_map_training_data()).""" """Remap exercise names using Obsidian-specific mapping (deprecated - use parser.parse_and_map_training_data())."""
parser = ObsidianNotesParser() parser = ObsidianNotesParser()
return parser.apply_exercise_mapping(obsidian_trainings) return parser.apply_exercise_mapping(obsidian_trainings)

View file

@ -40,124 +40,335 @@ list_of_unique_names = [
"Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажере (рычаги)",
"Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх в тренажере (рычаги)",
"Икроножные сидя", "Икроножные сидя",
"Пресс на наклонной скамье" "Пресс на наклонной скамье",
"Бицепс машина",
"Pull down (блины)",
"Pull down",
"Тяга Т-грифа",
"Французский жим гантели сидя",
"Молоты стоя (наискосок)",
"Отведение руки из-за спины в кроссовере",
"Гребная тяга с упором в грудь",
"Тяга штанги в наклоне",
"Молоты с гантелями лёжа",
"Махи гантелей стоя",
"Жим штанги лежа",
"Подъем штанги стоя",
"Подъем гантелей на предплечья обратным хватом",
"Жим от плеч вверх в тренажёре (блины)",
"Жим от плеч вверх в тренажёре (кирпичики)"
] ]
obsidian_unique_exercies_mapping = { obsidian_unique_exercies_mapping = {
# Подъем ног в висе
"Подьем ног в висе": "Подъем ног в висе",
"Подъем ног в висе": "Подъем ног в висе",
"Подъём ног в висе": "Подъем ног в висе",
# Подъем штанги стоя
"Подъем штанги на бицепс стоя": "Подъем штанги стоя",
"Подьем штанги на бицепс": "Подъем штанги стоя",
"Подъём штанги стоя": "Подъем штанги стоя",
"Подьем штанги стоя": "Подъем штанги стоя",
"Разведение рук в стороны с гантелями стоя": "Подъем штанги стоя",
"Сгибание штанги стоя на бицепс": "Подъем штанги стоя",
# Подъем гантелей на предплечья обратным хватом
'Подъем гантелей на предплечье обратным хватом': 'Подъем гантелей на предплечья обратным хватом',
'Подъем гартелей на предплечье обратным хватом': 'Подъем гантелей на предплечья обратным хватом',
# Баттерфляй
"Баттерфляй": "Баттерфляй", "Баттерфляй": "Баттерфляй",
# Бицепс машина
"Бицепс-машина": "Бицепс машина",
"Бицепс в тренажёре": "Бицепс машина",
"Бицепс в тренажёре (arm curl)": "Бицепс машина",
# Болгарские сплит-приседания
"Болгарские приседания на одну ногу": "Болгарские сплит-приседения", "Болгарские приседания на одну ногу": "Болгарские сплит-приседения",
# Брусья
"Брусья": "Брусья", "Брусья": "Брусья",
"Брусья узким хватом": "Брусья", "Брусья узким хватом": "Брусья",
# Вертикальная тяга одной рукой
"Вертикальная тяга одной рукой 45⁰": "Вертикальная тяга одной рукой", "Вертикальная тяга одной рукой 45⁰": "Вертикальная тяга одной рукой",
# Выпады
"Выпады вперед по очереди каждой ногой": "Выпады", "Выпады вперед по очереди каждой ногой": "Выпады",
"Выпады с гантелями на одну ногу вперед": "Выпады", "Выпады с гантелями на одну ногу вперед": "Выпады",
"Выпады вперед с гантелями на одну ногу": "Выпады",
# Гиперэкстензия
"Гиперэкстензия": "Гиперэкстензия", "Гиперэкстензия": "Гиперэкстензия",
# Гравитрон
"Гравитрон": "Гравитрон", "Гравитрон": "Гравитрон",
"Гравитрон (медленно вниз)": "Гравитрон", "Гравитрон (медленно вниз)": "Гравитрон",
# Гребная тяга с упором в грудь
"Гребная тяга с упорм в грудь": "Гребная тяга с упором в грудь", "Гребная тяга с упорм в грудь": "Гребная тяга с упором в грудь",
"Гребная тяга с упором в грудь": "Гребная тяга с упором в грудь", "Гребная тяга с упором в грудь": "Гребная тяга с упором в грудь",
# Гребная тяга с упором в грудь (по очереди)
"Гребная тяга с упором в грудь (одна рука)": "Гребная тяга с упором в грудь (по очереди)", "Гребная тяга с упором в грудь (одна рука)": "Гребная тяга с упором в грудь (по очереди)",
"Дельт машина": "Разведение рук в стороны (дельт-машина)",
"Дельт машина (разведение рук в стороны)": "Разведение рук в стороны (дельт-машина)", # Жим гантелей лежа
"Жим гантелей лежа": "Жим гантелей лежа", "Жим гантелей лежа": "Жим гантелей лежа",
"Жим гантелей лежа на скамье": "Жим гантелей лежа", "Жим гантелей лежа на скамье": "Жим гантелей лежа",
"Жим гантелей лежа на скамье 30⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей лёжа": "Жим гантелей лежа", "Жим гантелей лёжа": "Жим гантелей лежа",
"Жим гантелей от груди лежа": "Жим гантелей лежа",
"Жим от груди лежа с гантелями": "Жим гантелей лежа",
# Жим гантелей лежа (Под углом 45 градусов)
"Жим гантелей лежа на скамье 30⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей лёжа на скамейке 45⁰": "Жим гантелей лежа (Под углом 45 градусов)", "Жим гантелей лёжа на скамейке 45⁰": "Жим гантелей лежа (Под углом 45 градусов)",
"Жим гантелей на скамье 65⁰": "Жим гантелей лежа (Под углом 45 градусов)", "Жим гантелей на скамье 65⁰": "Жим гантелей лежа (Под углом 45 градусов)",
# Жим гантелей сидя
"Жим гантелей сидя": "Жим гантелей сидя", "Жим гантелей сидя": "Жим гантелей сидя",
"Жим гантелей сидя 80⁰": "Жим гантелей сидя", "Жим гантелей сидя 80⁰": "Жим гантелей сидя",
"Жим гантелей сидя 85⁰": "Жим гантелей сидя", "Жим гантелей сидя 85⁰": "Жим гантелей сидя",
"Жим гантелей сидя 8⁰": "Жим гантелей сидя", "Жим гантелей сидя 8⁰": "Жим гантелей сидя",
"Жим гантелей сидя 90⁰": "Жим гантелей сидя", "Жим гантелей сидя 90⁰": "Жим гантелей сидя",
"Жим гантелей сидя на скамье": "Жим гантелей сидя", "Жим гантелей сидя на скамье": "Жим гантелей сидя",
"Жим гантелей силя с гантелями": "Жим гантелей сидя",
"Жим от плеч вверх с гантелями": "Жим гантелей сидя",
"Жим от плеч вверх сидя": "Жим гантелей сидя",
"Жим от плеч вверх сидя с гантелями": "Жим гантелей сидя",
"Жим от плеч сидя с гантелями": "Жим гантелей сидя",
"Жим от плеч сидя": "Жим гантелей сидя",
# Жим ногами
"Жим ногами": "Жим ногами", "Жим ногами": "Жим ногами",
"Жим ногами ш-ш-ш": "Жим ногами", "Жим ногами ш-ш-ш": "Жим ногами",
"Жим ногамиш": "Жим ногами",
# Жим от груди сидя в тренажере (рычаги)
"Жим от груди (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди рычаги": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди рычаги": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажере (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре (рычаги)": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя в тренажёре (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя рычаги": "Жим от груди сидя в тренажере (рычаги)", "Жим от груди сидя рычаги": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди в тренажёре сидя (рычаги)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажере (блинчики)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре (блинчики)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре (блины)": "Жим от груди сидя в тренажере (рычаги)",
"Жим от груди сидя в тренажёре (рычаги, блинчики)": "Жим от груди сидя в тренажере (рычаги)",
# Жим от плеч вверх в тренажере (рычаги)
"Жим от плеч вверх (тренажёр, рычаги)": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх (тренажёр, рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх в тренажёре": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх в тренажёре": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх рычаги": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч вверх рычаги": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч сидя (рычаги наверху)": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч сидя (рычаги наверху)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч сидя в тренажере (рычаги)": "Жим от плеч вверх в тренажере (рычаги)", "Жим от плеч сидя в тренажере (рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх (рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
"Жим от плеч вверх в тренажёре (рычаги)": "Жим от плеч вверх в тренажере (рычаги)",
# Жим от плеч вверх в тренажере (блины)
"Жим от плеч вверх сидя в тренажёре (блинчики)":"Жим от плеч вверх в тренажёре (блины)",
"Жим от плеч сидя в тренажёре (блинчики)":"Жим от плеч вверх в тренажёре (блины)",
# Жим от плеч вверх в тренажере (кирпичики)
"Жим от плеч вверх сидя в тренажёре (кирпичики)":"Жим от плеч вверх в тренажёре (кирпичики)",
# Жим штанги лежа
"Жим штанги лёжа": "Жим штанги лежа",
# Икроножные сидя
"Икроножные сидя": "Икроножные сидя", "Икроножные сидя": "Икроножные сидя",
"Икроножные сидя за": "Икроножные сидя", "Икроножные сидя за": "Икроножные сидя",
"Кроссовер - отжимание на трицепс": "Отжимание на трицепс в кроссовере",
# Махи гантелей стоя
"Разведение рук в стороны стоя (гантели)": "Махи гантелей стоя",
"Разведение рук в стороны стоя с гантелями": "Махи гантелей стоя",
"Разведение рук стоя": "Махи гантелей стоя",
"Разведение рук с гантелями стоя": "Махи гантелей стоя",
"Разведение рук в стороны стоя": "Махи гантелей стоя",
"Разведение рук в стороны с гантелями": "Махи гантелей стоя",
# Молоты с гантелями лёжа
"Молоты лежа": "Молоты с гантелями лёжа",
"Молоты лёжа": "Молоты с гантелями лёжа",
"Молоты с гантелями лежа": "Молоты с гантелями лёжа",
# Молоты стоя (наискосок)
'Молоты стоя наискосок': 'Молоты стоя (наискосок)',
# Отведение рук в кроссовере (по очереди)
"Мах руки в кроссовере": "Отведение рук в кроссовере (по очереди)", "Мах руки в кроссовере": "Отведение рук в кроссовере (по очереди)",
"Отведение плеча в сторону в кроссовере": "Отведение рук в кроссовере (по очереди)", "Отведение плеча в сторону в кроссовере": "Отведение рук в кроссовере (по очереди)",
"Отведение руки в кроссовере из за спины": "Отведение рук в кроссовере (по очереди)", "Отведение руки в кроссовере из за спины": "Отведение рук в кроссовере (по очереди)",
"Отведение руки в кроссовере по очереди каждую руку": "Отведение рук в кроссовере (по очереди)", "Отведение руки в кроссовере по очереди каждую руку": "Отведение рук в кроссовере (по очереди)",
"Отведение руки в пек дек (по очереди каждой рукой)": "Разведение рук в пек-дек (по очереди)",
"Отведение руки в пек дек (по очереди)": "Разведение рук в пек-дек (по очереди)",
"Отведение руки в сторону (дефоли) в кроссовере": "Отведение рук в кроссовере (по очереди)", "Отведение руки в сторону (дефоли) в кроссовере": "Отведение рук в кроссовере (по очереди)",
"Отведение руки назад в кроссовере (аналог пек дек одной рукой)": "Разведение рук в пек-дек (по очереди)", "Отведение руки в кроссовере": "Отведение рук в кроссовере (по очереди)",
# Отведение руки из-за спины в кроссовере
'Отведение руки из-за спины': 'Отведение руки из-за спины в кроссовере',
# Отжимание на трицепс в кроссовере
"Кроссовер - отжимание на трицепс": "Отжимание на трицепс в кроссовере",
"Отдикна трицепс в кроссовере": "Отжимание на трицепс в кроссовере",
"Отжимания на брусьях": "Отжимание на трицепс в кроссовере",
"Отжимания на трицепс": "Отжимание на трицепс в кроссовере",
"Кроссовер: отжимание на трицепс": "Отжимание на трицепс в кроссовере",
"Кроссовер: отжимания на трицепс": "Отжимание на трицепс в кроссовере",
"Отжимания на трицепс в кроссовере": "Отжимание на трицепс в кроссовере",
"Отличается на трицепс в кроссовере": "Отжимание на трицепс в кроссовере",
"Трицепс в кроссовере": "Отжимание на трицепс в кроссовере",
# Отжимания
"Отжимания": "Отжимания", "Отжимания": "Отжимания",
# Подтягивания
"Подтягивания": "Подтягивания", "Подтягивания": "Подтягивания",
"Подтягивания обратным хватом": "Подтягивания обратным хватом",
"Подтянивания": "Подтягивания", "Подтянивания": "Подтягивания",
"Подъём ног в висе": "Подъём ног в висе",
# Подтягивания обратным хватом
"Подтягивание обратным хватом": "Подтягивания обратным хватом",
# Подъем гантелей на бицепс стоя
"Сгибание рук с гантелями стоя (супинирование?)": "Подъем гантелей на бицепс стоя",
"Подъем гантелей на бицепс": "Подъем гантелей на бицепс стоя",
"Подъем гантелей стоя": "Подъем гантелей на бицепс стоя",
"Подъём гантелей на бицепс": "Подъем гантелей на бицепс стоя",
"Подъём гантелей стоя": "Подъем гантелей на бицепс стоя",
"Подьем гантелей на бицепс стоя": "Подъем гантелей на бицепс стоя",
"Подьем гантелей стоя": "Подъем гантелей на бицепс стоя",
"Сгибание на бицепс с гантелями": "Подъем гантелей на бицепс стоя",
# Пресс на наклонной скамье
"Пресс на скамье": "Пресс на наклонной скамье",
"Пресс на скамейке": "Пресс на наклонной скамье",
# Приседания
"Приседания": "Приседания", "Приседания": "Приседания",
"Разведение рук а стороны сидя": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в наклоне": "Разведение рук в стороны в наклоне (гантели)", # Pull down
"Разведение рук в наклоне сидя": "Разведение рук в стороны в наклоне (гантели)", "Pull down (кирпичики)": "Pull down",
"Разведение рук в стороны": "Разведение рук в стороны (дельт-машина)", "Pull down в тренажёре (кирпичи)": "Pull down",
"Разведение рук в стороны (дельт машина)": "Разведение рук в стороны (дельт-машина)", "Тяга вертикального блока (кирпичики)": "Pull down",
"Разведение рук в стороны (дельт-машина)": "Разведение рук в стороны (дельт-машина)", "Тяга вертикального блока в тренажёре (кирпичики)": "Pull down",
"Разведение рук в стороны в наклоне с гантелями": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в тренажаре": "Разведение рук в стороны (дельт-машина)", # Pull down (блины)
"Разведение рук в стороны с гантелями стоя": "Разведение рук в стороны стоя (гантели)", "Pull down (блины на рычагах)": "Pull down (блины)",
"Разведение рук в стороны сидя": "Разведение рук в стороны (дельт-машина)", "Вертикальная тяга - рычаги (pull down)": "Pull down (блины)",
"Разведение рук в стороны сидя спиной к тренажеру": "Разведение рук в стороны (дельт-машина)", "Вертикальная тяга - рычаги с блинами (pull down)": "Pull down (блины)",
"Разведение рук в стороны стоя": "Разведение рук в стороны стоя (гантели)", "Вертикальная тяга - рычги (pull down)": "Pull down (блины)",
"Разведение рук в строоны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стророны (дельт-машина)": "Разведение рук в стороны (дельт-машина)", # Разведение рук в пек-дек
"Разведение рук назад в пек дек": "Разведение рук в пек-дек", "Разведение рук назад в пек дек": "Разведение рук в пек-дек",
"Разведение рук назад пёк дек": "Разведение рук в пек-дек", "Разведение рук назад пёк дек": "Разведение рук в пек-дек",
"Разведение рук пек дек": "Разведение рук в пек-дек", "Разведение рук пек дек": "Разведение рук в пек-дек",
"Разведение рук пёк дек": "Разведение рук в пек-дек", "Разведение рук пёк дек": "Разведение рук в пек-дек",
"Разведение рук пек-дек": "Разведение рук в пек-дек",
"Разведение рук назад пек дек": "Разведение рук в пек-дек",
# Разведение рук в пек-дек (по очереди)
"Отведение руки в пек дек (по очереди каждой рукой)": "Разведение рук в пек-дек (по очереди)",
"Отведение руки в пек дек (по очереди)": "Разведение рук в пек-дек (по очереди)",
"Отведение руки назад в кроссовере (аналог пек дек одной рукой)": "Разведение рук в пек-дек (по очереди)",
# Разведение рук в стороны (дельт-машина)
"Дельт машина": "Разведение рук в стороны (дельт-машина)",
"Дельт машина (разведение рук в стороны)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук а стороны сидя": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны (дельт машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны в тренажаре": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны сидя": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стороны сидя спиной к тренажеру": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в строоны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук в стророны (дельт-машина)": "Разведение рук в стороны (дельт-машина)",
"Разведение рук а стороны (Дельт машина)": "Разведение рук в стороны (дельт-машина)",
# Разведение рук в стороны в наклоне (гантели)
"Разведение рук в наклоне": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в наклоне сидя": "Разведение рук в стороны в наклоне (гантели)",
"Разведение рук в стороны в наклоне с гантелями": "Разведение рук в стороны в наклоне (гантели)",
# Разгибание ног сидя
"Разгибание ног сидя": "Разгибание ног сидя", "Разгибание ног сидя": "Разгибание ног сидя",
# Разгибание рук сидя (трицепс машина)
"Разгибание рук сидя (трицепс машина)": "Разгибание рук сидя (трицепс машина)", "Разгибание рук сидя (трицепс машина)": "Разгибание рук сидя (трицепс машина)",
"Трицепс машина": "Разгибание рук сидя (трицепс машина)",
# Румынская тяга
"Румынская тяга": "Румынская тяга", "Румынская тяга": "Румынская тяга",
# Сведение рук в пек-дек
"Сведение пек дек": "Сведение рук в пек-дек", "Сведение пек дек": "Сведение рук в пек-дек",
"Сведение рук в пек дек": "Сведение рук в пек-дек", "Сведение рук в пек дек": "Сведение рук в пек-дек",
"Сведение рук в пек декх": "Сведение рук в пек-дек", "Сведение рук в пек декх": "Сведение рук в пек-дек",
"Сведение рук пек дек": "Сведение рук в пек-дек", "Сведение рук пек дек": "Сведение рук в пек-дек",
"Сведение рук пёк дек": "Сведение рук в пек-дек", "Сведение рук пёк дек": "Сведение рук в пек-дек",
"Сведение рук в пёк дек": "Сведение рук в пек-дек",
"Сведение рук пек-дек": "Сведение рук в пек-дек",
# Сгибание ног лежа
"Сгибание ног лежа": "Сгибание ног лежа", "Сгибание ног лежа": "Сгибание ног лежа",
"Сгибание ног лёжа": "Сгибание ног лежа", "Сгибание ног лёжа": "Сгибание ног лежа",
# Сгибание ног сидя
"Сгибание ног сидя": "Сгибание ног сидя", "Сгибание ног сидя": "Сгибание ног сидя",
"Сгибание рук с гантелями стоя (супинирование?)": "Подъем гантелей на бицепс стоя",
"Сгибание штанги стоя на бицепс": "Подъем штанги на бицепс стоя", # Тяга Т-грифа
"Трицепс машина": "Разгибание рук сидя (трицепс машина)", 'Тяга Т грифа': 'Тяга Т-грифа',
'Тяга т-грифа': 'Тяга Т-грифа',
# Тяга вертикального блока
"Тяга вертикального блока": "Тяга вертикального блока", "Тяга вертикального блока": "Тяга вертикального блока",
"Тяга вертикального блока обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга вертикального блока прямым хватом": "Тяга вертикального блока", "Тяга вертикального блока прямым хватом": "Тяга вертикального блока",
"Тяга верхнего блока": "Тяга вертикального блока", "Тяга верхнего блока": "Тяга вертикального блока",
"Тяга верхнего блока за": "Тяга вертикального блока", "Тяга верхнего блока за": "Тяга вертикального блока",
"Тяга вертикалього блока": "Тяга вертикального блока",
# Тяга вертикального блока (обратный хват)
"Тяга вертикального блока обратным хватом": "Тяга вертикального блока (обратный хват)",
"Тяга вертикалього блока обратным хватом": "Тяга вертикального блока (обратный хват)",
# Тяга вертикального блока (блины)
"Тяга вертикального блока в тренажёре (рычаги)": "Тяга вертикального блока (блины)",
"Тяга вертикально блока (блинчики)": "Тяга вертикального блока (блины)",
# Тяга гантелей в наклоне (по очереди)
"Тяга гантелей в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантелей в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантелей к поясу лёжа на животе на скамье": "Тяга гантелей на скамье",
"Тяга гантелей лежа на животе на скамье 80°": "Тяга гантелей на скамье",
"Тяга гантелей на скамье": "Тяга гантелей на скамье",
"Тяга гантели в наклоне": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне 1 рукой": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне 1 рукой": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне к поясу к стойки": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне к поясу к стойки": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне одной рукой": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне одной рукой": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели в наклоне одной рукой (каждой)": "Тяга гантелей в наклоне (по очереди)", "Тяга гантели в наклоне одной рукой (каждой)": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантелей в наклоне одной рукой": "Тяга гантелей в наклоне (по очереди)",
"Тяга гантели к поясу в наклоне одной рукой": "Тяга гантелей в наклоне (по очереди)",
# Тяга гантелей на скамье
"Тяга гантелей к поясу лёжа на животе на скамье": "Тяга гантелей на скамье",
"Тяга гантелей лежа на животе на скамье 80°": "Тяга гантелей на скамье",
"Тяга гантелей на скамье": "Тяга гантелей на скамье",
"Тяга гантели лежа в наклоне на скамье": "Тяга гантелей на скамье", "Тяга гантели лежа в наклоне на скамье": "Тяга гантелей на скамье",
"Тяга горизонтально блока одной рукой": "Тяга горизонтального блока (по очереди)", "Тяга гантелей к поясу лежа на скамье": "Тяга гантелей на скамье",
# Тяга горизонтального блока
"Тяга горизонтального блока": "Тяга горизонтального блока", "Тяга горизонтального блока": "Тяга горизонтального блока",
# Тяга горизонтального блока (по очереди)
"Тяга горизонтально блока одной рукой": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока 1 рукой поочерёдно": "Тяга горизонтального блока (по очереди)", "Тяга горизонтального блока 1 рукой поочерёдно": "Тяга горизонтального блока (по очереди)",
"Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)", "Тяга горизонтального блока одной рукой": "Тяга горизонтального блока (по очереди)",
"Пресс на скамье": "Пресс на наклонной скамье",
"Пресс на скамейке": "Пресс на наклонной скамье",
"Отжимания на трицепс":"Отжимания на трицепс в кроссовере",
"Молоты лежа":"Молоты с гантелями лёжа",
"Молоты лёжа":"Молоты с гантелями лёжа",
"Молоты с гантелями лежа":"Молоты с гантелями лёжа",
}
# Тяга штанги в наклоне
"Тяга штанги к поясу в наклоне": "Тяга штанги в наклоне",
# Французский жим гантели сидя
'Французский жим с гантелей': 'Французский жим гантели сидя',
'Французский жим сидя': 'Французский жим гантели сидя',
}

View file

@ -1,9 +1,9 @@
from typing import List from typing import List
from app.core.parsers.apple import AppleNotesParser from app.core.parsers.apple import AppleNotesParser
from app.core.parsers.obsidian import ObsidianNotesParser from app.core.parsers.obsidian import ObsidianNotesParser
from app.core.models.training import Training from app.core.dto.training import TrainingDTO
def parse_old_data() -> List[Training]: def parse_old_data() -> List[TrainingDTO]:
"""Method for parsing all old data from apple and obsidian notes with exercise mapping applied """Method for parsing all old data from apple and obsidian notes with exercise mapping applied
Returns: Returns:

View file

@ -1,13 +1,35 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from app.api.v1.api import api_router from app.api.v1.api import api_router
from app.api.v1.web import web_router from app.api.v1.web import web_router
from app.config import settings
from migrations.runner import MigrationRunner
# TODO: Replace level with settings value
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
@asynccontextmanager
async def lifespan(app: FastAPI):
logging.info(f"\n\n{settings.get_postgres_database_url()}")
runner = MigrationRunner(settings.get_postgres_database_url())
await runner.run_migrations()
yield
# TODO: (#ToLearn) Почитать про lifespan в FastAPI приложениях
app = FastAPI( app = FastAPI(
title="Fitness Parser API", title="Fitness Parser API",
description="API for parsing fitness training data from various sources", description="API for parsing fitness training data from various sources",
version="0.1.0", version="0.1.0",
lifespan=lifespan,
) )
app.mount("/static", StaticFiles(directory="app/static"), name="static") app.mount("/static", StaticFiles(directory="app/static"), name="static")

32
app/sandbox.py Normal file
View file

@ -0,0 +1,32 @@
from collections import defaultdict
from app.core.parsers.apple import AppleNotesParser
from app.core.parsers.obsidian import ObsidianNotesParser
from pprint import pprint
om = ObsidianNotesParser()
obsidian_result = defaultdict(int)
o_mapped_trainings = om.parse_and_map_training_data()
for training in o_mapped_trainings:
for exercise in training.exercises:
obsidian_result[exercise.name] += 1
am = AppleNotesParser()
apple_result = defaultdict(int)
a_mapped_trainings = am.parse_and_map_training_data()
for training in a_mapped_trainings:
for exercise in training.exercises:
apple_result[exercise.name] += 1
print(f"apple trainings:\t{len(a_mapped_trainings)}")
print(f"osididian trainings:\t{len(o_mapped_trainings)}")
overall_trainings = a_mapped_trainings + o_mapped_trainings
print(f"overall trainings:\t{len(overall_trainings)}")
overall_result = defaultdict(int)
for training in overall_trainings:
for exercise in training.exercises:
overall_result[exercise.name] += 1
pprint(overall_result)

View file

@ -13,7 +13,8 @@
<!-- Navigation --> <!-- Navigation -->
<nav class="navbar navbar-dark bg-primary"> <nav class="navbar navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/"> <!-- TODO: find solution to not harcode links in hrefs -->
<a class="navbar-brand" href="/app/obsidian/list">
<strong>{{ app_name }}</strong> <strong>{{ app_name }}</strong>
</a> </a>
</div> </div>

View file

@ -6,3 +6,10 @@ services:
environment: environment:
- DEBUG=true - DEBUG=true
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /app/app --log-level debug command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /app/app --log-level debug
develop:
watch:
- action: sync
path: ./app
target: /app/app
- action: rebuild
path: ./pyproject.toml

View file

@ -11,13 +11,32 @@ services:
- ./app:/app/app:ro - ./app:/app/app:ro
- ./data:/app/data:ro - ./data:/app/data:ro
- ./tests:/app/tests:ro - ./tests:/app/tests:ro
- ./migrations:/app/migrations:ro
env_file:
- .env
environment: environment:
- PYTHONPATH=/app - PYTHONPATH=${PYTHONPATH}
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /app/app command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /app/app
develop: depends_on:
watch: postgres:
- action: sync condition: service_healthy
path: ./app postgres:
target: /app/app image: postgres:15-alpine
- action: rebuild env_file:
path: ./pyproject.toml - .env
environment:
- POSTGRES_DB=${POSTGRES_DATABASE_DB}
- POSTGRES_USER=${POSTGRES_DATABASE_USER}
- POSTGRES_PASSWORD=${POSTGRES_DATABASE_PASSWORD}
ports:
- "${POSTGRES_DATABASE_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_DATABASE_USER} -d ${POSTGRES_DATABASE_DB}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:

View file

@ -1,3 +1,161 @@
# 2025-10-09 (solo-107)
| Упражнение | Вес | Подходы |
| ---------------------------------------- | ----------- | ----------- |
| Подтягивания | | 10-7-6-5 |
| Тяга горизонтального блока | 60-65 | 12-12-12-12 |
| Жим от груди лежа с гантелями | 22.5х2-25х2 | 12-10-10-8 |
| Жим от груди сидя в тренажёре (блинчики) | 30х2 | 10-10-8-8 |
| Подъем ног в висе | | 12-10-10-8 |
# 2025-10-06 (solo-106)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ------ | ----------- |
| Подъем штанги стоя | 30 | 12-12-12-12 |
| Молоты стоя наискосок | 17.5х2 | 10-10-10-9 |
| Подъем гартелей на предплечье обратным хватом | 15х2 | 12-12-12-12 |
| Французский жим лежа со штангой | 25 | 12-12-12-12 |
| Жим от плеч вверх сидя в тренажёре (блинчики) | 40-45 | 12-10-10-10 |
# 2025-10-04 (solo-105)
| Упражнение | Вес | Подходы |
| ------------------------------------------------ | ----------- | -------------- |
| Подтягивания | | 8-7-6-6 |
| Тяга вертикального блока в тренажёре (кирпичики) | 80 | 12-12-12-12-11 |
| Сведение рук пек дек | 65-65-65-60 | 12-12-8-10 |
| Жим гантелей лежа | 22.5х2 | 12-10-10-8 |
| Пресс на скамье | | 12-12-12-12 |
# 2025-09-29 (solo-104)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ----------- | ----------- |
| Молоты стоя наискосок | 15х2-17.5х2 | 12-12-12-10 |
| Подъем гантелей на предплечье обратным хватом | 17.5х2 | 12-12-12-12 |
| Отдикна трицепс в кроссовере | 60 | 12-12-12-10 |
| Французский жим сидя | 22.5 | 9-10-10-8 |
| Жим от плеч вверх сидя | 20х2 | 12-12-12-10 |
# 2025-09-27 (solo-103)
| Упражнение | Вес | Подходы |
| ----------------------------------------- | ----- | ----------- |
| Подтягивания | | 10-7-6-5 |
| Тяга вертикального блока | 60 | 15-14-13-14 |
| Жим гантелей лежа | 22х2 | 12-12-12-11 |
| Жим от груди сидя в тренажёре (кирпичики) | 70-55 | 10-15-15-17 |
| Пресс на скамье | | 12-12-12 |
# 2025-09-24 (solo-102)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ------------ | ----------- |
| Подъем штанги стоя | 35 | 10-10-10-9 |
| Подъем гантелей на предплечье обратным хватом | 17.5х2 | 12-12-12-12 |
| Французский жим лежа со штангой | 20-25 | 12-12-12-12 |
| Поворот гантели на лямке | 8х2-10x2-8x2 | 12-9-12-12 |
| Жим от плеч вверх сидя с гантелями | 22.5х2 | 12-11-10-9 |
# 2025-09-22 (solo-101)
| Упражнение | Вес | Подходы |
| ---------------------------------------- | ----------- | ----------- |
| Подтягивания | | 10-7-6-5 |
| Гребная тяга с упором в грудь | 80-80-85 | 12-12-12-12 |
| Жим гантелей лежа | 22.5х2-25х2 | 12-12-9-8 |
| Жим от груди сидя в тренажёре (блинчики) | 30х2 | 8-9-8-8 |
| Махи гантелей стоя | 12.5х2 | 12-12-12-12 |
# 2025-09-20 (solo-100)
| Упражнение | Вес | Подходы |
| -------------------------- | ------- | ----------- |
| Подьем штанги стоя | 30 | 12-12-12-12 |
| Молоты стоя (наискосок) | 15х2 | 12-12-12-12 |
| Французский жим с гантелей | 20-22.5 | 12-10-12-10 |
| Поворот гантели на лямке | 8х2 | 12-12-12-12 |
| Пресс на скамье | | 15-12-12-12 |
# 2025-09-18 (solo-99)
| Упражнение | Вес | Подходы |
| ----------------------------------------- | ---- | ----------- |
| Подтягивания | | 10-6-6-6 |
| Тяга Т-грифа | 40 | 12-12-12-10 |
| Жим от груди лежа с гантелями | 25х2 | 10-10-10-10 |
| Жим от груди сидя в тренажёре (кирпичики) | 70 | 12-12-12-12 |
| Разведение рук пек-дек | 35 | 12-12-10-11 |
# 2025-09-15 (solo-98)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ------ | ----------- |
| Молоты стоя (наискосок) | 15х2 | 12-12-12-12 |
| Отжимания на трицепс в кроссовере | 60 | 12-12-11-10 |
| Подъем гантелей на предплечье обратным хватом | 15х2 | 12-12-12-12 |
| Жим от плеч вверх сидя с гантелями | 22.5х2 | 10-12-8-5 |
| Махи гантелей в сторону | 10х2 | 12-12- |
# 2025-09-13 (solo-97)
| Упражнение | Вес | Подходы |
| ------------------------------------ | ----- | ----------- |
| Подтягивания | | 10-7-6-6 |
| Тяга вертикального блока (кирпичики) | 80-85 | 12-12-12-12 |
| Жим от груди лежа с гантелями | 25х2 | 12-11-8-10 |
| Сведение рук пек-дек | 65-60 | 8-10-9-10 |
| Подъем ног в висе | | 12-10-10-7 |
# 2025-09-10 (solo-96)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ------ | ----------- |
| Подъем штанги стоя | 30 | 12-12-12-12 |
| Молоты стоя | 15х2 | 12-12-12- |
| Поворот гантели на лямке | 8х2 | 12-12-12-12 |
| Отжимания на трицепс в кроссовере | 60 | 12-11-11-10 |
| Жим от плеч вверх сидя в тренажёре (блинчики) | 17.5х2 | 10-10-9-8 |
# 2025-09-08 (solo-95)
| Упражнение | Вес | Подходы |
| ---------------------------------------- | ---- | ----------- |
| Подтягивания | | 10-8-7-7 |
| Тяга Т-грифа | 35 | 12-12-12-12 |
| Жим гантелей лежа | 25х2 | 12-11-9-9 |
| Жим от груди сидя в тренажере (блинчики) | 30х2 | 7-10-8-9 |
| Пресс на наклонной скамье | | 15-12-12-12 |
# 2025-09-06 (solo-94)
| Упражнение | Вес | Подходы |
| --------------------------------------------- | ---------------- | ----------- |
| Молоты стоя (наискосок) | 15х2 | 12-12-12-12 |
| Молоты стоя | 12.5х2-15х2 | 12-12-12-12 |
| Подъем гантелей на предплечья обратным хватом | 10х2-12.5х2-15х2 | 15-15-12-12 |
| Отжимания на трицепс в кроссовере | 60 | 12-12-12-10 |
| Жим от плеч вверх сидя с гантелями | 22.5х2 | 9-8-8-8 |
# 2025-09-04 (solo-93)
| Упражнение | Вес | Подходы |
| -------------------------- | ----------- | ----------- |
| Подтягивания | | 10-8-7-6 |
| Тяга горизонтального блока | 60-65-65-60 | 12-12-12-12 |
| Сведение рук пек-дек | 60-65 | 12-12-10-8 |
| Жим гантелей лежа | 25х2 | 9-9-9-9 |
| Пресс на скамье | | 12-13-12-12 |
# 2025-09-01 (solo-92)
| Упражнение | Вес | Подходы |
| ---------------------------------- | --------------------- | ----------- |
| Подъем штанги стоя | 30 | 12-12-12-12 |
| Молоты стоя (наискосок) | 15х2-15х2-15х2-12.5х2 | 10-11-10-12 |
| Молоты лежа | 12.5х2-15x2 | 12-12-12-12 |
| Поворот гантели на лямке | 6х2-6х2-8х2 | 12-12-12-12 |
| Жим от плеч вверх сидя с гантелями | 22.5х2 | 12-11-9-8 |
# 2025-08-30 (solo-91) # 2025-08-30 (solo-91)
| Упражнение | Вес | Подходы | | Упражнение | Вес | Подходы |

44
dump_trainings.py Normal file
View file

@ -0,0 +1,44 @@
import json
from typing import List
from app.core.parsers.apple import parse_training_data as adp
from app.core.parsers.obsidian import parse_training_data as odp
from app.core.dto.training import TrainingDTO
def dump_trainings_to_json(trainings: List[TrainingDTO], filename="trainings.json"):
"""Dump training data to JSON file using DTO format."""
trainings_data = []
for training in trainings:
training_dict = training.model_dump()
trainings_data.append(training_dict)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(trainings_data, f, indent=2, ensure_ascii=False, default=str)
print(f"Trainings dumped to {filename}")
def main():
# Parse Obsidian notes
obsidian_trainings = odp()
for training in obsidian_trainings:
for exercise in training.exercises:
print(exercise.name)
# Parse Apple notes
apple_trainings = adp()
# Combine trainings
all_trainings = obsidian_trainings + apple_trainings
print(f"Obsidian trainings: {len(obsidian_trainings)}")
print(f"Apple trainings: {len(apple_trainings)}")
print(f"Total trainings: {len(all_trainings)}")
# Dump to JSON
dump_trainings_to_json(all_trainings)
if __name__ == "__main__":
main()

0
migrations/__init__.py Normal file
View file

76
migrations/runner.py Normal file
View file

@ -0,0 +1,76 @@
import logging
from pathlib import Path
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import text
class MigrationRunner:
MIGRATIONS_SCHEMA_NAME: str = "schema_migrations"
def __init__(self, database_url: str) -> None:
self.engine = create_async_engine(database_url)
self.migrations_dir = Path("migrations/sql")
logging.info(
f"Migrator initializer: engine = {self.engine}, migrations_dir = {self.migrations_dir}"
)
async def get_applied_migrations(self) -> set:
"""Get list of applied migrations"""
async with self.engine.begin() as conn:
# Create migrations list table if not exists
await conn.execute(
text(f"""
CREATE TABLE IF NOT EXISTS {self.MIGRATIONS_SCHEMA_NAME} (
version VARCHAR(50) PRIMARY KEY,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""")
)
# Receiving list of applied migrations
result = await conn.execute(
text(f"SELECT version FROM {self.MIGRATIONS_SCHEMA_NAME}")
)
result_data = {row[0] for row in result.fetchall()}
logging.info(f"Received migrator data: {result_data}")
return result_data
async def run_migrations(self):
"""Run all unapplied migrations"""
applied = await self.get_applied_migrations()
logging.info(f"Applied migrations: {applied}")
# Getting all sql files from migrations_dir
# TODO: (#ToLearn) Read about Path.glob function
migration_files = self.migrations_dir.glob('.*sql')
migration_files = sorted([file for file in self.migrations_dir.glob("*.sql")])
logging.info(f"Migration files: {migration_files}")
for migration_file in migration_files:
# TODO: (#ToLearn) Read about stem property
migration_name = migration_file.stem
if migration_name not in applied:
logging.info(f"Applying migration: {migration_name}")
await self._apply_migration(migration_file, migration_name)
else:
logging.info(f"Skipping migration: {migration_name} (already applied)")
async def _apply_migration(self, migration_file: Path, migration_name: str):
"""Apply migrations from migrations/sql folder"""
with open(migration_file, "r", encoding="utf-8") as f:
sql_content = f.read()
async with self.engine.begin() as conn:
try:
statements = [s.strip() for s in sql_content.split(";") if s.strip()]
for statement in statements:
if statement:
await conn.execute(text(statement))
logging.info(f"Migration {migration_name} applied successfully")
except Exception as e:
logging.error(f"Error applying migration {migration_name}: {e}")
raise

View file

@ -0,0 +1,19 @@
-- Migration: 0001_initial
-- Description: Create initial migration with user for f1tness app
-- Default user schema
-- TODO: (#ToLearn) Read about TIMESTAMP and CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(250) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Schema for tracking applied migrations
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(50) PRIMARY KEY,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO schema_migrations (version) VALUES ('0001_initial');

View file

@ -0,0 +1,28 @@
-- Migration: 0002_trainings
-- Description: Basic migration to initialize and create basic tarinings table
-- Schemas for trainings
-- Trainings table
CREATE TABLE IF NOT EXISTS trainings (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
trainer VARCHAR(100)
);
-- Exercies table
CREATE TABLE IF NOT EXISTS exercises (
id SERIAL PRIMARY KEY,
training_id INTEGER REFERENCES trainings(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
splitted_weigh BOOLEAN DEFAULT FALSE
);
-- Approaches table
CREATE TABLE IF NOT EXISTS approaches (
id SERIAL PRIMARY KEY,
approach_id INTEGER REFERENCES approaches(id) ON DELETE CASCADE,
weight FLOAT NOT NULL,
reps INTEGER NOT NULL
);
INSERT INTO schema_migrations (version) VALUES ('0002_trainings');

View file

@ -0,0 +1,37 @@
-- Migration: 0003_fix_trainings_id
-- Description: Fix trainings table to use proper SQLite autoincrement ID
-- Drop the existing trainings table (data will be lost)
DROP TABLE IF EXISTS trainings;
-- Recreate with proper SQLite syntax
CREATE TABLE trainings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
trainer VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Also fix exercises table if it exists
DROP TABLE IF EXISTS exercises;
CREATE TABLE exercises (
id INTEGER PRIMARY KEY AUTOINCREMENT,
training_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
splitted_weight BOOLEAN DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (training_id) REFERENCES trainings(id) ON DELETE CASCADE
);
-- Fix approaches table if it exists
DROP TABLE IF EXISTS approaches;
CREATE TABLE approaches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exercise_id INTEGER NOT NULL,
weight REAL,
reps INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE
);
INSERT INTO schema_migrations (version) VALUES ('0003_fix_trainings_id');

View file

@ -13,6 +13,9 @@ dependencies = [
"python-dotenv>=1.0.1", "python-dotenv>=1.0.1",
"jinja2>=3.1.0", "jinja2>=3.1.0",
"python-multipart>=0.0.6", "python-multipart>=0.0.6",
"sqlalchemy>=2.0.43",
"asyncpg>=0.30.0",
"aiosqlite>=0.21.0",
] ]
[dependency-groups] [dependency-groups]
@ -21,3 +24,7 @@ dev = [
"types-grpcio>=1.0.0.20250703", "types-grpcio>=1.0.0.20250703",
"types-protobuf>=6.30.2.20250822", "types-protobuf>=6.30.2.20250822",
] ]
[tools.pyright]
venvPath = "."
venv = ".venv"

79
uv.lock
View file

@ -2,6 +2,18 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.13" requires-python = ">=3.13"
[[package]]
name = "aiosqlite"
version = "0.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" },
]
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
version = "0.7.0" version = "0.7.0"
@ -24,6 +36,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
] ]
[[package]]
name = "asyncpg"
version = "0.30.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" },
{ url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" },
{ url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" },
{ url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" },
{ url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" },
{ url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" },
{ url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" },
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.2.1" version = "8.2.1"
@ -50,6 +78,8 @@ name = "f1tness-parser"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aiosqlite" },
{ name = "asyncpg" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "jinja2" }, { name = "jinja2" },
{ name = "pydantic" }, { name = "pydantic" },
@ -57,6 +87,7 @@ dependencies = [
{ name = "pytest" }, { name = "pytest" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "sqlalchemy" },
{ name = "uvicorn", extra = ["standard"] }, { name = "uvicorn", extra = ["standard"] },
] ]
@ -69,6 +100,8 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "aiosqlite", specifier = ">=0.21.0" },
{ name = "asyncpg", specifier = ">=0.30.0" },
{ name = "fastapi", specifier = ">=0.115.0" }, { name = "fastapi", specifier = ">=0.115.0" },
{ name = "jinja2", specifier = ">=3.1.0" }, { name = "jinja2", specifier = ">=3.1.0" },
{ name = "pydantic", specifier = ">=2.11.7" }, { name = "pydantic", specifier = ">=2.11.7" },
@ -76,6 +109,7 @@ requires-dist = [
{ name = "pytest", specifier = ">=8.3.4" }, { name = "pytest", specifier = ">=8.3.4" },
{ name = "python-dotenv", specifier = ">=1.0.1" }, { name = "python-dotenv", specifier = ">=1.0.1" },
{ name = "python-multipart", specifier = ">=0.0.6" }, { name = "python-multipart", specifier = ">=0.0.6" },
{ name = "sqlalchemy", specifier = ">=2.0.43" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
] ]
@ -100,6 +134,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" },
] ]
[[package]]
name = "greenlet"
version = "3.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
{ url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
{ url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" },
{ url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" },
{ url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" },
{ url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
{ url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
{ url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
{ url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
]
[[package]] [[package]]
name = "h11" name = "h11"
version = "0.16.0" version = "0.16.0"
@ -370,6 +428,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
] ]
[[package]]
name = "sqlalchemy"
version = "2.0.43"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" },
{ url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" },
{ url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" },
{ url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" },
{ url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" },
{ url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" },
{ url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" },
{ url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" },
{ url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" },
]
[[package]] [[package]]
name = "starlette" name = "starlette"
version = "0.47.3" version = "0.47.3"