f1tness_parser/app/api/v1/web.py
2026-10-22 23:46:29 +03:00

124 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import calendar
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from app.api.deps import get_training_repository
from app.core.database.repository import TrainingRepository
from app.core.metrics import ExerciseProgress, summarize
from app.core.parsers.obsidian import ObsidianNotesParser, parse_training_data
from app.core.templates.templates import BaseTemplateResponse
web_router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
template_renderer = BaseTemplateResponse(templates)
def build_progress_chart(progress: ExerciseProgress) -> dict:
"""Data for the progress chart: two series over time, x is a UTC timestamp in ms."""
if progress.weighted:
series = [
("e1RM, кг", lambda metrics: metrics.e1rm),
("Тоннаж, кг", lambda metrics: metrics.volume or None),
]
else:
series = [
("Лучший подход, повторы", lambda metrics: metrics.best_reps),
("Всего повторов за тренировку", lambda metrics: metrics.total_reps),
]
chronological = list(reversed(progress.rows))
return {
"series": [
{
"label": label,
"points": [
[calendar.timegm(row.session.date.timetuple()) * 1000, round(value, 1)]
for row in chronological
if (value := metric(row.metrics)) is not None
],
}
for label, metric in series
]
}
@web_router.get("/", response_class=HTMLResponse)
async def home(
request: Request,
repository: TrainingRepository = Depends(get_training_repository),
):
"""Home page: all trainings from the database in a single table"""
return template_renderer.render(
request,
"index.html",
context={"trainings": await repository.list()},
)
@web_router.get("/exercises/", response_class=HTMLResponse)
async def exercises(
request: Request,
name: Optional[str] = None,
repository: TrainingRepository = Depends(get_training_repository),
):
"""Exercise search box and the whole history of the chosen exercise"""
query = (name or "").strip()
exercise_names = await repository.list_exercise_names()
# Exact name, but case-insensitive: what was typed may differ in case
names_by_casefold = {item.name.casefold(): item.name for item in exercise_names}
selected = names_by_casefold.get(query.casefold())
history = await repository.exercise_history(selected) if selected else []
# Nothing matched exactly: offer the names containing the typed text
suggestions = (
[item for item in exercise_names if query.casefold() in item.name.casefold()]
if query and not selected
else []
)
progress = summarize(history) if history else None
return template_renderer.render(
request,
"exercises.html",
context={
"exercise_names": exercise_names,
"query": query,
"selected": selected,
"suggestions": suggestions,
"progress": progress,
"chart": build_progress_chart(progress) if progress else None,
},
)
@web_router.get("/obsidian/list/", response_class=HTMLResponse)
async def obsidian_list(request: Request):
data = parse_training_data()
return template_renderer.render(
request=request, template_name="trainings.html", context={"trainings": data}
)
@web_router.get("/obsidian/parse", response_class=HTMLResponse)
async def obsidian_parse_form(request: Request):
return template_renderer.render(
request=request, template_name="obsidian/parser.html", context={"trainings": {}}
)
@web_router.post("/obsidian/parse", response_class=HTMLResponse)
async def obsidian_parse(request: Request, training_data: str = Form(...)):
logging.error(training_data)
op = ObsidianNotesParser()
result = op.parse(data=training_data)
logging.error(result)
return template_renderer.render(
request=request, template_name="trainings.html", context={"trainings": result}
)