This commit is contained in:
Dominik Klarkowski
2026-06-18 11:02:31 +02:00
parent d00a5a42ac
commit 047e580da0
32 changed files with 735 additions and 189 deletions

View File

@@ -28,9 +28,15 @@ 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 secToMinKm(sec: number): string {
const m = Math.floor(sec / 60);
const s = Math.round(sec % 60);
return `${m}:${s.toString().padStart(2, "0")} min/km`;
}
function buildRunMetricsSummary(metrics: RunMetrics, totalDistanceM: number): string[] {
const { distanceKm, hrBpm, gcbLeftPct } = metrics;
if (!hrBpm && !gcbLeftPct) return [];
const { distanceKm, hrBpm, gcbLeftPct, paceSec } = metrics;
if (!hrBpm && !gcbLeftPct && !paceSec) return [];
const n = distanceKm.length;
if (n < 8) return [];
@@ -60,6 +66,11 @@ function buildRunMetricsSummary(metrics: RunMetrics, totalDistanceM: number): st
const a = avg(vals);
if (a !== null) parts.push(`HR śr. ${a} bpm`);
}
if (paceSec) {
const vals = idx.map((i) => paceSec[i]).filter((v) => v > 0 && v < 1800);
const a = avg(vals);
if (a !== null) parts.push(`tempo śr. ${secToMinKm(a)}`);
}
if (gcbLeftPct) {
const vals = idx.map((i) => gcbLeftPct[i]).filter((v) => v > 0);
if (vals.length > 0) {
@@ -184,6 +195,7 @@ function parseAnalysisResponse(text: string): { summary: string; tips: string[]
}
export async function generateAnalysis(
userId: string,
targetType: AiAnalysisTargetType,
targetId: string
): Promise<AiAnalysis> {
@@ -194,28 +206,28 @@ export async function generateAnalysis(
let prompt: string;
if (targetType === "running") {
const activity = await getRunningActivity(targetId);
const activity = await getRunningActivity(userId, targetId);
if (!activity) throw new Error("Nie znaleziono biegu.");
const previousRuns = (await listRunningActivities())
const previousRuns = (await listRunningActivities(userId))
.filter((run) => run.startTime < activity.startTime)
.slice(0, PREVIOUS_RUNS_LIMIT);
const previousRunsWithAnalysis: PreviousRun[] = await Promise.all(
previousRuns.map(async (run) => ({
run,
analysis: await getLatestAnalysisForTarget("running", run._id),
analysis: await getLatestAnalysisForTarget(userId, "running", run._id),
}))
);
prompt = buildRunningPrompt(activity, previousRunsWithAnalysis);
} else {
const workout = await getStrengthWorkout(targetId);
const workout = await getStrengthWorkout(userId, targetId);
if (!workout) throw new Error("Nie znaleziono treningu.");
const previousWorkouts = (await listStrengthWorkouts())
const previousWorkouts = (await listStrengthWorkouts(userId))
.filter((previous) => previous.date < workout.date)
.slice(0, PREVIOUS_WORKOUTS_LIMIT);
const previousWorkoutsWithAnalysis: PreviousWorkout[] = await Promise.all(
previousWorkouts.map(async (previous) => ({
workout: previous,
analysis: await getLatestAnalysisForTarget("strength", previous._id),
analysis: await getLatestAnalysisForTarget(userId, "strength", previous._id),
}))
);
prompt = buildStrengthPrompt(workout, previousWorkoutsWithAnalysis);
@@ -234,6 +246,7 @@ export async function generateAnalysis(
const { summary, tips } = parseAnalysisResponse(text);
return saveAiAnalysis({
userId,
targetType,
targetId: new ObjectId(targetId),
summary,
@@ -332,18 +345,18 @@ function buildDashboardPrompt(
return lines.join("\n");
}
export async function generateDashboardAnalysis(): Promise<AiAnalysis> {
export async function generateDashboardAnalysis(userId: string): Promise<AiAnalysis> {
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("Brak klucza ANTHROPIC_API_KEY w konfiguracji.");
const [runs, workouts] = await Promise.all([
listRunningActivities().then((r) => r.slice(0, DASHBOARD_RUNS_LIMIT)),
listStrengthWorkouts().then((w) => w.slice(0, DASHBOARD_WORKOUTS_LIMIT)),
listRunningActivities(userId).then((r) => r.slice(0, DASHBOARD_RUNS_LIMIT)),
listStrengthWorkouts(userId).then((w) => w.slice(0, DASHBOARD_WORKOUTS_LIMIT)),
]);
let wellness: DayWellness[] = [];
try {
const garminClient = await getAuthorizedClient();
const garminClient = await getAuthorizedClient(userId);
wellness = await fetchRecentWellness(garminClient, DASHBOARD_WELLNESS_DAYS);
} catch {
// Wellness data not available, proceed without it
@@ -361,5 +374,5 @@ export async function generateDashboardAnalysis(): Promise<AiAnalysis> {
const textBlock = message.content.find((b) => b.type === "text");
const text = textBlock && textBlock.type === "text" ? textBlock.text : "";
const { summary, tips } = parseAnalysisResponse(text);
return saveDashboardAnalysis(summary, tips, model);
return saveDashboardAnalysis(userId, summary, tips, model);
}