This commit is contained in:
Dominik Klarkowski
2026-06-18 09:43:25 +02:00
parent ee178feff0
commit d00a5a42ac
9 changed files with 621 additions and 21 deletions

View File

@@ -3,7 +3,7 @@ import { ObjectId } from "mongodb";
import { formatDate, formatDateShort, formatDistance, formatDuration, formatPace } from "@/lib/format";
import { fetchRecentWellness, type DayWellness } from "@/lib/garmin/wellness";
import { getAuthorizedClient } from "@/lib/garmin/client";
import { getRunningActivity, listRunningActivities, type RunningActivity } from "@/lib/models/running";
import { getRunningActivity, listRunningActivities, type RunMetrics, type RunningActivity } from "@/lib/models/running";
import { getStrengthWorkout, listStrengthWorkouts, type StrengthWorkout } from "@/lib/models/strength";
import {
getLatestAnalysisForTarget,
@@ -28,6 +28,50 @@ Jeśli podano dane z poprzednich treningów, odnieś się do progresu (np. zmian
type PreviousRun = { run: RunningActivity; analysis: AiAnalysis | null };
type PreviousWorkout = { workout: StrengthWorkout; analysis: AiAnalysis | null };
function buildRunMetricsSummary(metrics: RunMetrics, totalDistanceM: number): string[] {
const { distanceKm, hrBpm, gcbLeftPct } = metrics;
if (!hrBpm && !gcbLeftPct) return [];
const n = distanceKm.length;
if (n < 8) return [];
const maxDist = Math.max(...distanceKm);
const totalKm = totalDistanceM / 1000;
const useIndex = maxDist === 0;
const position = (i: number) => (useIndex ? i / n : distanceKm[i] / maxDist);
const kmLabel = (i: number) =>
useIndex ? ((i / n) * totalKm).toFixed(1) : distanceKm[i].toFixed(1);
const avg = (vals: number[]) =>
vals.length ? Math.round(vals.reduce((s, v) => s + v, 0) / vals.length) : null;
const lines = [`Dane w trakcie biegu (4 kwartyle):`];
for (let q = 0; q < 4; q++) {
const from = q / 4;
const to = (q + 1) / 4;
const idx = Array.from({ length: n }, (_, i) => i).filter(
(i) => position(i) >= from && position(i) < to
);
if (idx.length === 0) continue;
const parts = [`${kmLabel(idx[0])}${kmLabel(idx[idx.length - 1])} km`];
if (hrBpm) {
const vals = idx.map((i) => hrBpm[i]).filter((v) => v > 0);
const a = avg(vals);
if (a !== null) parts.push(`HR śr. ${a} bpm`);
}
if (gcbLeftPct) {
const vals = idx.map((i) => gcbLeftPct[i]).filter((v) => v > 0);
if (vals.length > 0) {
const mean = vals.reduce((s, v) => s + v, 0) / vals.length;
parts.push(`balans L/P ${mean.toFixed(1)}%/${(100 - mean).toFixed(1)}%`);
}
}
lines.push(`- ${parts.join(", ")}`);
}
return lines.length > 1 ? lines : [];
}
function buildRunningPrompt(activity: RunningActivity, previousRuns: PreviousRun[]): string {
const lines = [
`Przeanalizuj poniższy bieg i podaj krótkie podsumowanie oraz wskazówki potreningowe.`,
@@ -58,6 +102,13 @@ function buildRunningPrompt(activity: RunningActivity, previousRuns: PreviousRun
if (activity.aerobicTrainingEffect) lines.push(`Efekt treningowy aerobowy: ${activity.aerobicTrainingEffect.toFixed(1)}`);
if (activity.anaerobicTrainingEffect) lines.push(`Efekt treningowy anaerobowy: ${activity.anaerobicTrainingEffect.toFixed(1)}`);
if (activity.runMetrics) {
const metricLines = buildRunMetricsSummary(activity.runMetrics, activity.distanceM);
if (metricLines.length > 0) {
lines.push(``, ...metricLines);
}
}
if (previousRuns.length > 0) {
lines.push(``, `Poprzednie biegi (od najnowszego):`);
for (const { run, analysis } of previousRuns) {