File size: 5,751 Bytes
35527e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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}`));
            },
        });
    });
}