glutamatt's picture
glutamatt HF Staff
init
35527e2 verified
raw
history blame
5.75 kB
import Papa from 'papaparse';
import type { Activity } from '@/types';
import { calculateTSS } from './tssCalculator';
export function parseCSV(file: File, userFTP: number = 343): Promise<Activity[]> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
header: true,
skipEmptyLines: true,
complete: (results) => {
try {
const activities = results.data.map((row: any) => {
// Parse date - Garmin format: "2025-12-02 09:19:49"
const dateStr = row['Date'] || row['Activity Date'] || row['date'];
if (!dateStr || dateStr === '--') {
return null;
}
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
return null;
}
// Parse distance - Garmin format: "8.34" (in km)
let distance: number | undefined;
const distanceStr = row['Distance'] || row['distance'];
if (distanceStr && distanceStr !== '--' && distanceStr !== '0.00') {
const parsed = parseFloat(distanceStr.replace(/,/g, ''));
if (!isNaN(parsed) && parsed > 0) {
distance = parsed;
}
}
// Parse duration - Garmin format: "00:48:51" (HH:MM:SS)
let duration: number | undefined;
let durationSeconds: number | undefined;
const durationStr = row['Time'] || row['Duration'] || row['Moving Time'] || row['Elapsed Time'];
if (durationStr && durationStr !== '--') {
if (durationStr.includes(':')) {
const parts = durationStr.split(':');
const hours = parseInt(parts[0]) || 0;
const minutes = parseInt(parts[1]) || 0;
const seconds = parseFloat(parts[2]) || 0;
duration = hours * 60 + minutes + seconds / 60;
durationSeconds = hours * 3600 + minutes * 60 + seconds;
} else {
const parsed = parseFloat(durationStr);
if (!isNaN(parsed)) {
duration = parsed;
durationSeconds = parsed * 60; // Assume minutes if no colon
}
}
}
// Parse TSS - Try to calculate from power data first
let trainingStressScore: number | undefined;
// First, check if TSS is already provided in CSV
const tssStr = row['Training Stress Score®'] || row['Training Stress Score'] || row['TSS'];
if (tssStr && tssStr !== '--' && tssStr !== '0.0' && tssStr !== '0') {
const parsed = parseFloat(tssStr);
if (!isNaN(parsed) && parsed > 0) {
trainingStressScore = parsed;
}
}
// If no TSS provided, try to calculate from power data
if (!trainingStressScore && durationSeconds) {
// Parse Normalized Power (NP)
const npStr = row['Normalized Power (NP)'] || row['Normalized Power'] || row['NP'];
const normalizedPower = npStr && npStr !== '--' ? parseFloat(npStr) : undefined;
// Use FTP from CSV if available, otherwise use user-provided FTP
const ftpStr = row['FTP'] || row['Functional Threshold Power'];
const ftp = ftpStr && ftpStr !== '--' ? parseFloat(ftpStr) : userFTP;
if (normalizedPower && ftp) {
const calculatedTSS = calculateTSS({
durationSeconds,
normalizedPower,
ftp,
});
if (calculatedTSS !== undefined) {
trainingStressScore = calculatedTSS;
}
}
}
// Get activity type
const activityType = row['Activity Type'] || row['Type'] || row['Sport'];
const activity: Activity = {
date,
activityType,
distance,
duration,
trainingStressScore,
};
return activity;
}).filter((activity): activity is Activity => activity !== null);
// Sort by date
activities.sort((a, b) => a.date.getTime() - b.date.getTime());
resolve(activities);
} catch (error) {
reject(new Error('Failed to parse CSV data'));
}
},
error: (error) => {
reject(new Error(`CSV parsing error: ${error.message}`));
},
});
});
}