Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions web/app/(app)/progression/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Metadata } from "next"
import { startOfWeek, subMonths, subWeeks, format } from "date-fns"
import { startOfWeek, subYears, subWeeks, format } from "date-fns"
import { fr } from "date-fns/locale"
import { LineChart } from "lucide-react"

Expand Down Expand Up @@ -36,14 +36,14 @@ export default async function ProgressionPage() {
if (!user) return null

const now = new Date()
const sixMonthsAgo = subMonths(now, 6)
const vmaHistoryStart = subYears(now, 4)

const [activitiesRes, zonesRes, prActivitiesRes] = await Promise.all([
supabase
.from("activities")
.select("provider, provider_activity_id, sport_type, start_date, duration_sec, moving_time_sec, distance_m, elevation_gain_m, average_heartrate, max_heartrate, time_in_zones_json")
.select("provider, provider_activity_id, sport_type, start_date, duration_sec, moving_time_sec, distance_m, elevation_gain_m, average_heartrate, max_heartrate, max_speed, time_in_zones_json")
.eq("user_id", user.id)
.gte("start_date", sixMonthsAgo.toISOString())
.gte("start_date", vmaHistoryStart.toISOString())
.order("start_date"),
supabase
.from("hr_zones")
Expand Down
1 change: 1 addition & 0 deletions web/lib/compute/vma-estimate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type VmaActivity = {
elevation_gain_m: number | null
average_heartrate: number | null
max_heartrate: number | null
max_speed?: number | null
}

export type VmaZone = {
Expand Down
41 changes: 22 additions & 19 deletions web/lib/server/strava/vma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ function candidateActivities(activities: VmaStravaActivity[]): VmaStravaActivity
return distanceKm >= 2 && durationSec >= 600 && elevationPerKm <= 35
})
.sort((a, b) => {
const speedA = (a.distance_m ?? 0) / Math.max(1, a.moving_time_sec ?? a.duration_sec ?? 1)
const speedB = (b.distance_m ?? 0) / Math.max(1, b.moving_time_sec ?? b.duration_sec ?? 1)
return speedB - speedA
const score = (activity: VmaStravaActivity) => {
const avgSpeed = ((activity.distance_m ?? 0) / Math.max(1, activity.moving_time_sec ?? activity.duration_sec ?? 1)) * 3.6
const maxSpeed = (activity.max_speed ?? 0) * 3.6
const hrScore = Math.max(activity.max_heartrate ?? 0, activity.average_heartrate ?? 0) / 10
const daysAgo = Math.max(0, (Date.now() - new Date(activity.start_date).getTime()) / 86_400_000)
const recency = Math.max(0, 4 - daysAgo / 180)
return Math.max(avgSpeed, maxSpeed * 0.82) + hrScore + recency
}
return score(b) - score(a)
})
.slice(0, 6)
.slice(0, 12)
}

async function fetchStreams(token: string, providerActivityId: string): Promise<StreamPayload | null> {
Expand All @@ -58,23 +64,20 @@ export async function getVmaStreamEfforts(
token: string,
activities: VmaStravaActivity[],
): Promise<VmaStreamEffort[]> {
const efforts: VmaStreamEffort[] = []
for (const activity of candidateActivities(activities)) {
const results = await Promise.all(candidateActivities(activities).map(async (activity) => {
const payload = await fetchStreams(token, activity.provider_activity_id as string)
const time = payload?.time?.data
const distance = payload?.distance?.data
if (!time || !distance || time.length !== distance.length) continue
if (!time || !distance || time.length !== distance.length) return []

efforts.push(
...bestStreamEfforts({
time,
distance,
velocity: payload?.velocity_smooth?.data,
heartrate: payload?.heartrate?.data,
altitude: payload?.altitude?.data,
date: activity.start_date,
}),
)
}
return efforts
return bestStreamEfforts({
time,
distance,
velocity: payload?.velocity_smooth?.data,
heartrate: payload?.heartrate?.data,
altitude: payload?.altitude?.data,
date: activity.start_date,
})
}))
return results.flat()
}
Loading