first commit
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# TCX Parser - Snabbstart
|
||||
|
||||
## 1. Installera (en gång)
|
||||
|
||||
```bash
|
||||
cd ~/Documents/training
|
||||
chmod +x scripts/*.sh scripts/*.py
|
||||
```
|
||||
|
||||
## 2. (Valfritt) Lägg till alias
|
||||
|
||||
Lägg till i din `~/.bashrc` eller `~/.zshrc`:
|
||||
|
||||
```bash
|
||||
source ~/Documents/training/scripts/aliases.sh
|
||||
```
|
||||
|
||||
Sedan ladda om:
|
||||
```bash
|
||||
source ~/.bashrc # eller source ~/.zshrc
|
||||
```
|
||||
|
||||
## 3. Efter varje träning
|
||||
|
||||
### Metod A: Automatiskt (enklast)
|
||||
|
||||
```bash
|
||||
# 1. Flytta TCX-fil till Data/training/
|
||||
mv ~/Downloads/activity_*.tcx ~/Documents/training/Data/training/
|
||||
|
||||
# 2. Parsa alla nya filer
|
||||
cd ~/Documents/training
|
||||
parse-all
|
||||
```
|
||||
|
||||
### Metod B: Snabbparsning (bara senaste)
|
||||
|
||||
```bash
|
||||
# 1. Flytta TCX-fil till Data/training/
|
||||
mv ~/Downloads/activity_*.tcx ~/Documents/training/Data/training/
|
||||
|
||||
# 2. Parsa senaste filen
|
||||
cd ~/Documents/training
|
||||
parse-latest
|
||||
```
|
||||
|
||||
### Metod C: Utan alias
|
||||
|
||||
```bash
|
||||
# 1. Flytta TCX-fil
|
||||
mv ~/Downloads/activity_*.tcx ~/Documents/training/Data/training/
|
||||
|
||||
# 2. Parsa
|
||||
cd ~/Documents/training
|
||||
./scripts/parse_all_tcx.sh
|
||||
```
|
||||
|
||||
## 4. Be Claude analysera
|
||||
|
||||
När scriptet är klart, be Claude:
|
||||
|
||||
```
|
||||
"Analysera Data/training/activity_12345.tcx.json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tillgängliga kommandon
|
||||
|
||||
(kräver att alias är laddade)
|
||||
|
||||
| Kommando | Beskrivning |
|
||||
|----------|-------------|
|
||||
| `parse-all` | Parsa alla TCX-filer som inte har JSON |
|
||||
| `parse-latest` | Parsa senaste TCX-filen |
|
||||
| `parse-tcx <fil>` | Parsa en specifik fil |
|
||||
| `training` | Gå till träningsrepo |
|
||||
| `training-data` | Gå till Data/training/ |
|
||||
|
||||
## Felsökning
|
||||
|
||||
**Problem: "command not found: parse-all"**
|
||||
- Du har inte laddat alias
|
||||
- Kör: `source ~/Documents/training/scripts/aliases.sh`
|
||||
- Eller använd fullständig sökväg: `./scripts/parse_all_tcx.sh`
|
||||
|
||||
**Problem: "Inga TCX-filer hittades"**
|
||||
- Kolla att filerna ligger i `Data/training/`
|
||||
- Kontrollera att de har `.tcx`-ändelse
|
||||
|
||||
**Problem: "Permission denied"**
|
||||
- Gör scripten körbara: `chmod +x scripts/*.sh scripts/*.py`
|
||||
|
||||
---
|
||||
|
||||
## Snabbreferens
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
chmod +x scripts/*.sh scripts/*.py
|
||||
|
||||
# Lägg till alias (valfritt)
|
||||
echo 'source ~/Documents/training/scripts/aliases.sh' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Efter träning
|
||||
mv ~/Downloads/activity_*.tcx Data/training/
|
||||
parse-all
|
||||
|
||||
# Analysera
|
||||
# Be Claude: "Analysera Data/training/activity_X.tcx.json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Se [README.md](README.md) för fullständig dokumentation**
|
||||
@@ -0,0 +1,261 @@
|
||||
# TCX Parser - Instruktioner
|
||||
|
||||
## Översikt
|
||||
|
||||
`parse_tcx.py` är ett script som extraherar all viktig träningsdata från stora TCX-filer och skapar kompakta JSON-sammanfattningar.
|
||||
|
||||
**Storlek:**
|
||||
- TCX-fil: ~1.3 MB
|
||||
- JSON-fil: ~8 KB
|
||||
- **Besparing: 99.4% mindre!**
|
||||
|
||||
## Installation
|
||||
|
||||
Scripten kräver bara Python 3 och Bash (ingen extra paket).
|
||||
|
||||
```bash
|
||||
# Gör scripten körbara (behöver bara göras en gång)
|
||||
chmod +x scripts/parse_tcx.py
|
||||
chmod +x scripts/parse_all_tcx.sh
|
||||
chmod +x scripts/quick_parse.sh
|
||||
```
|
||||
|
||||
### Optional: Lägg till alias i din shell
|
||||
|
||||
För extra bekvämlighet, lägg till detta i din `~/.bashrc` eller `~/.zshrc`:
|
||||
|
||||
```bash
|
||||
# Lägg till i ~/.bashrc eller ~/.zshrc
|
||||
source ~/Documents/training/scripts/aliases.sh
|
||||
```
|
||||
|
||||
Efter detta kan du använda korta kommandon som `parse-all` istället för `bash scripts/parse_all_tcx.sh`.
|
||||
|
||||
## Användning
|
||||
|
||||
### 🚀 Rekommenderat: Automatisk parsning av alla filer
|
||||
|
||||
**Enklast - kör detta efter varje träning:**
|
||||
|
||||
```bash
|
||||
./scripts/parse_all_tcx.sh
|
||||
```
|
||||
|
||||
eller (om du laddat alias):
|
||||
|
||||
```bash
|
||||
parse-all
|
||||
```
|
||||
|
||||
Detta script:
|
||||
- ✅ Hittar alla TCX-filer i `Data/training/`
|
||||
- ✅ Parsar bara filer som inte redan har JSON-motsvarigheter
|
||||
- ✅ Uppdaterar JSON om TCX-filen är nyare
|
||||
- ✅ Ger en fin sammanfattning av vad som gjordes
|
||||
|
||||
**Output:**
|
||||
```
|
||||
🔍 Söker efter TCX-filer i Data/training
|
||||
|
||||
📝 Parsar ny fil: activity_20922168380.tcx
|
||||
✓ Redan parsad: activity_12345678901.tcx
|
||||
|
||||
════════════════════════════════════════
|
||||
📊 Sammanfattning
|
||||
════════════════════════════════════════
|
||||
Total TCX-filer funna: 2
|
||||
Redan parsade: 1
|
||||
Nyligen parsade: 1
|
||||
|
||||
✅ Klart! 1 fil(er) parsade.
|
||||
```
|
||||
|
||||
### ⚡ Snabbparsning av senaste filen
|
||||
|
||||
Bara vill parsa den senaste TCX-filen?
|
||||
|
||||
```bash
|
||||
./scripts/quick_parse.sh
|
||||
```
|
||||
|
||||
eller:
|
||||
|
||||
```bash
|
||||
parse-latest
|
||||
```
|
||||
|
||||
### 🎯 Parsa en specifik fil
|
||||
|
||||
```bash
|
||||
python3 scripts/parse_tcx.py Data/training/activity_20922168380.tcx
|
||||
```
|
||||
|
||||
eller:
|
||||
|
||||
```bash
|
||||
parse-tcx Data/training/activity_20922168380.tcx
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
Parsar Data/training/activity_20922168380.tcx...
|
||||
✓ Klar! Sammanfattning sparad i: Data/training/activity_20922168380.tcx.json
|
||||
|
||||
Snabb sammanfattning:
|
||||
Sport: Biking
|
||||
Datum: 2025-11-07
|
||||
Tid: 29:42
|
||||
Distans: 11.51 km
|
||||
Genomsnittshastighet: 23.3 km/h
|
||||
Genomsnittspuls: 161.2 bpm
|
||||
Max puls: 184 bpm
|
||||
Genomsnittseffekt: 106.0 W
|
||||
```
|
||||
|
||||
## Data som extraheras
|
||||
|
||||
JSON-filen innehåller:
|
||||
|
||||
### 1. Grundläggande info
|
||||
- Sport (Biking, Running, Swimming, etc.)
|
||||
- Datum och tid
|
||||
- Total tid, distans, hastighet
|
||||
- Kalorier
|
||||
|
||||
### 2. Pulsdata
|
||||
- Genomsnitt, min, max puls
|
||||
- **Pulszonfördelning (%)** - mycket viktigt för analys!
|
||||
- Zon 1: <110 bpm (Återhämtning)
|
||||
- Zon 2: 110-129 bpm (Grundträning)
|
||||
- Zon 3: 129-147 bpm (Tempo)
|
||||
- Zon 4: 147-166 bpm (Högt tempo)
|
||||
- Zon 5: >166 bpm (Maximum)
|
||||
|
||||
### 3. Effektdata (om tillgänglig)
|
||||
- Genomsnitt, min, max watt
|
||||
|
||||
### 4. Kadansdata
|
||||
- Genomsnitt, min, max rpm
|
||||
|
||||
### 5. Höjddata
|
||||
- Min, max höjd
|
||||
- Total stigning
|
||||
|
||||
### 6. Varvdata
|
||||
- Detaljerad data för varje varv/intervall
|
||||
|
||||
## Använda JSON-filen för analys
|
||||
|
||||
När du vill att jag ska analysera ett träningspass:
|
||||
|
||||
**Istället för att be mig läsa den stora TCX-filen:**
|
||||
```
|
||||
"Kan du analysera activity_20922168380.tcx?" ❌
|
||||
```
|
||||
|
||||
**Be mig läsa JSON-filen:**
|
||||
```
|
||||
"Kan du analysera activity_20922168380.tcx.json?" ✅
|
||||
```
|
||||
|
||||
### Fördelar:
|
||||
- **99.4% mindre fil** = mycket snabbare
|
||||
- **Färre tokens** = sparar kostnader
|
||||
- **Alla viktiga data** finns kvar
|
||||
- **Lättare att läsa** för både människa och AI
|
||||
|
||||
## Exempel: Komplett workflow
|
||||
|
||||
### Workflow 1: Automatiskt (Rekommenderat)
|
||||
|
||||
1. **Exportera träning från Garmin/Strava/Zwift** som TCX
|
||||
2. **Spara i Data/training/**
|
||||
```bash
|
||||
mv ~/Downloads/activity_12345.tcx Data/training/
|
||||
```
|
||||
3. **Parsa alla nya filer**
|
||||
```bash
|
||||
parse-all # eller: ./scripts/parse_all_tcx.sh
|
||||
```
|
||||
4. **Be Claude om analys**
|
||||
```
|
||||
"Kan du analysera Data/training/activity_12345.tcx.json?"
|
||||
```
|
||||
|
||||
### Workflow 2: Snabb (för senaste filen)
|
||||
|
||||
1. **Exportera och spara TCX-fil**
|
||||
```bash
|
||||
mv ~/Downloads/activity_*.tcx Data/training/
|
||||
```
|
||||
2. **Parsa senaste filen**
|
||||
```bash
|
||||
parse-latest # eller: ./scripts/quick_parse.sh
|
||||
```
|
||||
3. **Be Claude om analys** - filnamnet visas i output
|
||||
|
||||
### Workflow 3: Manuellt (specifik fil)
|
||||
|
||||
1. **Exportera träning från Garmin/Strava/Zwift** som TCX
|
||||
2. **Spara i Data/training/**
|
||||
```bash
|
||||
mv ~/Downloads/activity_12345.tcx Data/training/
|
||||
```
|
||||
3. **Parsa TCX-filen**
|
||||
```bash
|
||||
parse-tcx Data/training/activity_12345.tcx
|
||||
```
|
||||
4. **Be om analys**
|
||||
```
|
||||
"Kan du analysera Data/training/activity_12345.tcx.json?"
|
||||
```
|
||||
|
||||
## Uppdatera pulszoner
|
||||
|
||||
Om din maxpuls ändras (t.ex. ny maxpuls 190 bpm), uppdatera zonerna i scriptet:
|
||||
|
||||
Öppna `scripts/parse_tcx.py` och ändra raderna ~107-111:
|
||||
|
||||
```python
|
||||
# Gamla zoner (baserat på maxpuls 184 bpm)
|
||||
zone1 = sum(1 for hr in hr_data if hr < 110)
|
||||
zone2 = sum(1 for hr in hr_data if 110 <= hr < 129)
|
||||
zone3 = sum(1 for hr in hr_data if 129 <= hr < 147)
|
||||
zone4 = sum(1 for hr in hr_data if 147 <= hr < 166)
|
||||
zone5 = sum(1 for hr in hr_data if hr >= 166)
|
||||
|
||||
# Nya zoner (exempel för maxpuls 190 bpm)
|
||||
zone1 = sum(1 for hr in hr_data if hr < 114) # <60%
|
||||
zone2 = sum(1 for hr in hr_data if 114 <= hr < 133) # 60-70%
|
||||
zone3 = sum(1 for hr in hr_data if 133 <= hr < 152) # 70-80%
|
||||
zone4 = sum(1 for hr in hr_data if 152 <= hr < 171) # 80-90%
|
||||
zone5 = sum(1 for hr in hr_data if hr >= 171) # >90%
|
||||
```
|
||||
|
||||
Eller använd dynamiska zoner baserat på uppmätt maxpuls - kontakta mig om du vill ha den funktionen!
|
||||
|
||||
## Felsökning
|
||||
|
||||
**Problem:** `Fel: Filen X hittades inte`
|
||||
- Kolla att sökvägen är korrekt
|
||||
- Använd relativ sökväg från repository-roten
|
||||
|
||||
**Problem:** `Ingen aktivitet hittades i TCX-filen`
|
||||
- TCX-filen kan vara korrupt
|
||||
- Kontrollera att den är exporterad korrekt från din enhet
|
||||
|
||||
**Problem:** Script kraschar
|
||||
- Rapportera felet så fixar jag det!
|
||||
|
||||
## Framtida förbättringar
|
||||
|
||||
Möjliga tillägg:
|
||||
- [ ] Auto-detektera maxpuls och beräkna dynamiska zoner
|
||||
- [ ] Markdown-export istället för JSON
|
||||
- [ ] Automatisk uppdatering av träningsdagbok
|
||||
- [ ] Jämförelse mellan flera pass
|
||||
- [ ] Graf-generering (puls över tid, etc.)
|
||||
|
||||
## Support
|
||||
|
||||
Vid problem eller frågor, kontakta Claude Code!
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Bash Aliases för Träningsdata
|
||||
#
|
||||
# Lägg till dessa i din ~/.bashrc eller ~/.zshrc:
|
||||
# source ~/Documents/training/scripts/aliases.sh
|
||||
#
|
||||
# Eller kör direkt:
|
||||
# source scripts/aliases.sh
|
||||
#
|
||||
|
||||
# Hämta repo-sökvägen
|
||||
TRAINING_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Alias för TCX-parsning
|
||||
alias parse-tcx='python3 "$TRAINING_REPO/scripts/parse_tcx.py"'
|
||||
alias parse-all='bash "$TRAINING_REPO/scripts/parse_all_tcx.sh"'
|
||||
alias parse-latest='bash "$TRAINING_REPO/scripts/quick_parse.sh"'
|
||||
|
||||
# Alias för att gå till träningskatalogen
|
||||
alias training='cd "$TRAINING_REPO"'
|
||||
alias training-data='cd "$TRAINING_REPO/Data/training"'
|
||||
|
||||
# Visa alla alias
|
||||
alias show-training-aliases='cat "$TRAINING_REPO/scripts/aliases.sh" | grep "^alias"'
|
||||
|
||||
echo "✅ Träningsaliaser laddade!"
|
||||
echo ""
|
||||
echo "Tillgängliga kommandon:"
|
||||
echo " parse-tcx <fil> - Parsa en specifik TCX-fil"
|
||||
echo " parse-all - Parsa alla TCX-filer i Data/training/"
|
||||
echo " parse-latest - Parsa senaste TCX-filen"
|
||||
echo " training - Gå till träningsrepo"
|
||||
echo " training-data - Gå till Data/training/"
|
||||
echo " show-training-aliases - Visa alla alias"
|
||||
echo ""
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Parse All TCX Files
|
||||
# Hittar alla TCX-filer i Data/training/ som inte har JSON-sammanfattningar
|
||||
# och parsar dem automatiskt
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Färger för output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TRAINING_DIR="$REPO_ROOT/Data/training"
|
||||
|
||||
echo -e "${BLUE}🔍 Söker efter TCX-filer i $TRAINING_DIR${NC}\n"
|
||||
|
||||
# Räknare
|
||||
total_tcx=0
|
||||
already_parsed=0
|
||||
newly_parsed=0
|
||||
errors=0
|
||||
|
||||
# Hitta alla TCX-filer
|
||||
if [ ! -d "$TRAINING_DIR" ]; then
|
||||
echo -e "${YELLOW}⚠️ Katalogen $TRAINING_DIR finns inte${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
for tcx_file in "$TRAINING_DIR"/*.tcx; do
|
||||
# Kolla om glob hittade något
|
||||
[ -e "$tcx_file" ] || continue
|
||||
|
||||
total_tcx=$((total_tcx + 1))
|
||||
json_file="${tcx_file}.json"
|
||||
|
||||
# Kolla om JSON redan finns
|
||||
if [ -f "$json_file" ]; then
|
||||
# Kolla om TCX är nyare än JSON
|
||||
if [ "$tcx_file" -nt "$json_file" ]; then
|
||||
echo -e "${YELLOW}🔄 Uppdaterar: $(basename "$tcx_file")${NC}"
|
||||
if python3 "$SCRIPT_DIR/parse_tcx.py" "$tcx_file"; then
|
||||
newly_parsed=$((newly_parsed + 1))
|
||||
else
|
||||
echo -e "${YELLOW}❌ Misslyckades: $(basename "$tcx_file")${NC}"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "✓ Redan parsad: $(basename "$tcx_file")"
|
||||
already_parsed=$((already_parsed + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}📝 Parsar ny fil: $(basename "$tcx_file")${NC}"
|
||||
if python3 "$SCRIPT_DIR/parse_tcx.py" "$tcx_file"; then
|
||||
newly_parsed=$((newly_parsed + 1))
|
||||
else
|
||||
echo -e "${YELLOW}❌ Misslyckades: $(basename "$tcx_file")${NC}"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Sammanfattning
|
||||
echo ""
|
||||
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}📊 Sammanfattning${NC}"
|
||||
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
||||
echo "Total TCX-filer funna: $total_tcx"
|
||||
echo "Redan parsade: $already_parsed"
|
||||
echo -e "${GREEN}Nyligen parsade: $newly_parsed${NC}"
|
||||
|
||||
if [ $errors -gt 0 ]; then
|
||||
echo -e "${YELLOW}Fel vid parsning: $errors${NC}"
|
||||
fi
|
||||
|
||||
if [ $total_tcx -eq 0 ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Inga TCX-filer hittades i $TRAINING_DIR${NC}"
|
||||
echo "Lägg dina TCX-filer där och kör scriptet igen."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $newly_parsed -gt 0 ]; then
|
||||
echo -e "${GREEN}✅ Klart! $newly_parsed fil(er) parsade.${NC}"
|
||||
echo "Du kan nu be Claude analysera JSON-filerna för snabbare analys."
|
||||
elif [ $total_tcx -gt 0 ]; then
|
||||
echo -e "✅ Alla TCX-filer är redan parsade!"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TCX Parser - Extraherar träningsdata från TCX-filer
|
||||
Skapar kompakt JSON-sammanfattning för analys
|
||||
|
||||
Användning:
|
||||
python3 parse_tcx.py <tcx-fil>
|
||||
python3 parse_tcx.py Data/training/activity_12345.tcx
|
||||
|
||||
Output:
|
||||
Skapar <tcx-fil>.json med sammanfattad data
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# Namespace definitions för TCX
|
||||
NAMESPACES = {
|
||||
'tcx': 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2',
|
||||
'ns3': 'http://www.garmin.com/xmlschemas/ActivityExtension/v2'
|
||||
}
|
||||
|
||||
|
||||
def parse_tcx_file(tcx_path):
|
||||
"""Parsar TCX-fil och extraherar all relevant data"""
|
||||
|
||||
tree = ET.parse(tcx_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Hitta aktiviteten
|
||||
activity = root.find('.//tcx:Activity', NAMESPACES)
|
||||
if activity is None:
|
||||
raise ValueError("Ingen aktivitet hittades i TCX-filen")
|
||||
|
||||
# Grundläggande info
|
||||
sport = activity.get('Sport', 'Unknown')
|
||||
activity_id = activity.find('tcx:Id', NAMESPACES).text
|
||||
|
||||
# Samla data från varv
|
||||
laps = activity.findall('.//tcx:Lap', NAMESPACES)
|
||||
lap_data = []
|
||||
total_time = 0
|
||||
total_distance = 0
|
||||
total_calories = 0
|
||||
max_speed = 0
|
||||
|
||||
for i, lap in enumerate(laps, 1):
|
||||
lap_info = {
|
||||
'number': i,
|
||||
'start_time': lap.get('StartTime'),
|
||||
}
|
||||
|
||||
# Extrahera varvdata
|
||||
time_elem = lap.find('tcx:TotalTimeSeconds', NAMESPACES)
|
||||
dist_elem = lap.find('tcx:DistanceMeters', NAMESPACES)
|
||||
cal_elem = lap.find('tcx:Calories', NAMESPACES)
|
||||
max_speed_elem = lap.find('tcx:MaximumSpeed', NAMESPACES)
|
||||
avg_hr_elem = lap.find('.//tcx:AverageHeartRateBpm/tcx:Value', NAMESPACES)
|
||||
max_hr_elem = lap.find('.//tcx:MaximumHeartRateBpm/tcx:Value', NAMESPACES)
|
||||
avg_cad_elem = lap.find('tcx:Cadence', NAMESPACES)
|
||||
|
||||
if time_elem is not None:
|
||||
lap_time = float(time_elem.text)
|
||||
lap_info['time_seconds'] = lap_time
|
||||
total_time += lap_time
|
||||
|
||||
if dist_elem is not None:
|
||||
lap_dist = float(dist_elem.text)
|
||||
lap_info['distance_meters'] = lap_dist
|
||||
total_distance += lap_dist
|
||||
|
||||
if cal_elem is not None:
|
||||
lap_cal = int(cal_elem.text)
|
||||
lap_info['calories'] = lap_cal
|
||||
total_calories += lap_cal
|
||||
|
||||
if max_speed_elem is not None:
|
||||
lap_max_speed = float(max_speed_elem.text)
|
||||
lap_info['max_speed_ms'] = lap_max_speed
|
||||
max_speed = max(max_speed, lap_max_speed)
|
||||
|
||||
if avg_hr_elem is not None:
|
||||
lap_info['avg_hr'] = int(avg_hr_elem.text)
|
||||
if max_hr_elem is not None:
|
||||
lap_info['max_hr'] = int(max_hr_elem.text)
|
||||
if avg_cad_elem is not None:
|
||||
lap_info['avg_cadence'] = int(avg_cad_elem.text)
|
||||
|
||||
lap_data.append(lap_info)
|
||||
|
||||
# Samla data från trackpoints
|
||||
trackpoints = root.findall('.//tcx:Trackpoint', NAMESPACES)
|
||||
|
||||
hr_data = []
|
||||
power_data = []
|
||||
cadence_data = []
|
||||
altitude_data = []
|
||||
speed_data = []
|
||||
|
||||
for tp in trackpoints:
|
||||
# Puls
|
||||
hr_elem = tp.find('.//tcx:HeartRateBpm/tcx:Value', NAMESPACES)
|
||||
if hr_elem is not None:
|
||||
hr_data.append(int(hr_elem.text))
|
||||
|
||||
# Effekt
|
||||
power_elem = tp.find('.//ns3:Watts', NAMESPACES)
|
||||
if power_elem is not None:
|
||||
power_data.append(int(power_elem.text))
|
||||
|
||||
# Kadans
|
||||
cad_elem = tp.find('tcx:Cadence', NAMESPACES)
|
||||
if cad_elem is not None:
|
||||
cadence_data.append(int(cad_elem.text))
|
||||
|
||||
# Höjd
|
||||
alt_elem = tp.find('tcx:AltitudeMeters', NAMESPACES)
|
||||
if alt_elem is not None:
|
||||
altitude_data.append(float(alt_elem.text))
|
||||
|
||||
# Hastighet
|
||||
speed_elem = tp.find('.//ns3:Speed', NAMESPACES)
|
||||
if speed_elem is not None:
|
||||
speed_data.append(float(speed_elem.text))
|
||||
|
||||
# Beräkna pulsstatistik och zoner
|
||||
hr_stats = None
|
||||
hr_zones = None
|
||||
|
||||
if hr_data:
|
||||
avg_hr = sum(hr_data) / len(hr_data)
|
||||
min_hr = min(hr_data)
|
||||
max_hr = max(hr_data)
|
||||
|
||||
# Pulszoner (baserat på 5-zonssystem)
|
||||
# Kan justeras baserat på individuell maxpuls
|
||||
zone1 = sum(1 for hr in hr_data if hr < 110)
|
||||
zone2 = sum(1 for hr in hr_data if 110 <= hr < 129)
|
||||
zone3 = sum(1 for hr in hr_data if 129 <= hr < 147)
|
||||
zone4 = sum(1 for hr in hr_data if 147 <= hr < 166)
|
||||
zone5 = sum(1 for hr in hr_data if hr >= 166)
|
||||
total_points = len(hr_data)
|
||||
|
||||
hr_stats = {
|
||||
'avg': round(avg_hr, 1),
|
||||
'min': min_hr,
|
||||
'max': max_hr,
|
||||
}
|
||||
|
||||
hr_zones = {
|
||||
'zone1_pct': round(zone1 / total_points * 100, 1),
|
||||
'zone2_pct': round(zone2 / total_points * 100, 1),
|
||||
'zone3_pct': round(zone3 / total_points * 100, 1),
|
||||
'zone4_pct': round(zone4 / total_points * 100, 1),
|
||||
'zone5_pct': round(zone5 / total_points * 100, 1),
|
||||
'note': 'Zoner baserade på: Z1<110, Z2:110-129, Z3:129-147, Z4:147-166, Z5>166 bpm'
|
||||
}
|
||||
|
||||
# Beräkna effektstatistik
|
||||
power_stats = None
|
||||
if power_data:
|
||||
power_stats = {
|
||||
'avg': round(sum(power_data) / len(power_data), 1),
|
||||
'min': min(power_data),
|
||||
'max': max(power_data),
|
||||
}
|
||||
|
||||
# Beräkna kadansstatistik
|
||||
cadence_stats = None
|
||||
if cadence_data:
|
||||
cadence_stats = {
|
||||
'avg': round(sum(cadence_data) / len(cadence_data), 1),
|
||||
'min': min(cadence_data),
|
||||
'max': max(cadence_data),
|
||||
}
|
||||
|
||||
# Beräkna höjdstatistik
|
||||
altitude_stats = None
|
||||
if altitude_data:
|
||||
elevation_gain = 0
|
||||
for i in range(1, len(altitude_data)):
|
||||
diff = altitude_data[i] - altitude_data[i-1]
|
||||
if diff > 0:
|
||||
elevation_gain += diff
|
||||
|
||||
altitude_stats = {
|
||||
'min': round(min(altitude_data), 1),
|
||||
'max': round(max(altitude_data), 1),
|
||||
'gain': round(elevation_gain, 1),
|
||||
}
|
||||
|
||||
# Beräkna hastighetsstatistik
|
||||
speed_stats = None
|
||||
if speed_data:
|
||||
avg_speed = sum(speed_data) / len(speed_data)
|
||||
speed_stats = {
|
||||
'avg_ms': round(avg_speed, 2),
|
||||
'avg_kmh': round(avg_speed * 3.6, 1),
|
||||
'max_ms': round(max(speed_data), 2),
|
||||
'max_kmh': round(max(speed_data) * 3.6, 1),
|
||||
}
|
||||
|
||||
# Bygg sammanfattning
|
||||
summary = {
|
||||
'file': str(tcx_path),
|
||||
'parsed_at': datetime.now().isoformat(),
|
||||
'activity': {
|
||||
'sport': sport,
|
||||
'id': activity_id,
|
||||
'date': activity_id.split('T')[0] if 'T' in activity_id else activity_id,
|
||||
},
|
||||
'summary': {
|
||||
'total_time_seconds': round(total_time, 1),
|
||||
'total_time_formatted': f"{int(total_time//60)}:{int(total_time%60):02d}",
|
||||
'total_distance_meters': round(total_distance, 2),
|
||||
'total_distance_km': round(total_distance / 1000, 2),
|
||||
'avg_speed_kmh': round((total_distance / total_time) * 3.6, 1) if total_time > 0 else 0,
|
||||
'max_speed_kmh': round(max_speed * 3.6, 1),
|
||||
'total_calories': total_calories,
|
||||
'num_laps': len(laps),
|
||||
'num_trackpoints': len(trackpoints),
|
||||
},
|
||||
'heart_rate': hr_stats,
|
||||
'heart_rate_zones': hr_zones,
|
||||
'power': power_stats,
|
||||
'cadence': cadence_stats,
|
||||
'altitude': altitude_stats,
|
||||
'speed': speed_stats,
|
||||
'laps': lap_data,
|
||||
}
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Användning: python3 parse_tcx.py <tcx-fil>")
|
||||
print("Exempel: python3 parse_tcx.py Data/training/activity_12345.tcx")
|
||||
sys.exit(1)
|
||||
|
||||
tcx_file = Path(sys.argv[1])
|
||||
|
||||
if not tcx_file.exists():
|
||||
print(f"Fel: Filen {tcx_file} hittades inte")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
print(f"Parsar {tcx_file}...")
|
||||
summary = parse_tcx_file(tcx_file)
|
||||
|
||||
# Spara som JSON
|
||||
output_file = tcx_file.with_suffix('.tcx.json')
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(summary, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"✓ Klar! Sammanfattning sparad i: {output_file}")
|
||||
print(f"\nSnabb sammanfattning:")
|
||||
print(f" Sport: {summary['activity']['sport']}")
|
||||
print(f" Datum: {summary['activity']['date']}")
|
||||
print(f" Tid: {summary['summary']['total_time_formatted']}")
|
||||
print(f" Distans: {summary['summary']['total_distance_km']} km")
|
||||
print(f" Genomsnittshastighet: {summary['summary']['avg_speed_kmh']} km/h")
|
||||
|
||||
if summary['heart_rate']:
|
||||
print(f" Genomsnittspuls: {summary['heart_rate']['avg']} bpm")
|
||||
print(f" Max puls: {summary['heart_rate']['max']} bpm")
|
||||
|
||||
if summary['power']:
|
||||
print(f" Genomsnittseffekt: {summary['power']['avg']} W")
|
||||
|
||||
print(f"\nAnvänd denna JSON-fil för snabb analys istället för stora TCX-filen!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fel vid parsning: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Quick Parse - Snabbparsning av senaste TCX-filen
|
||||
# Användning: ./quick_parse.sh
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TRAINING_DIR="$REPO_ROOT/Data/training"
|
||||
|
||||
# Hitta senaste TCX-filen
|
||||
latest_tcx=$(ls -t "$TRAINING_DIR"/*.tcx 2>/dev/null | head -n 1)
|
||||
|
||||
if [ -z "$latest_tcx" ]; then
|
||||
echo "❌ Inga TCX-filer hittades i $TRAINING_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Parsar senaste TCX-fil: $(basename "$latest_tcx")"
|
||||
echo ""
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
python3 "$SCRIPT_DIR/parse_tcx.py" "$latest_tcx"
|
||||
|
||||
echo ""
|
||||
echo "✅ JSON-fil skapad! Be Claude analysera:"
|
||||
echo " $(basename "$latest_tcx").json"
|
||||
Reference in New Issue
Block a user