fix: Apple Health import - German names + duplicate detection
Issue 1: Automatic training type mapping didn't work - Root cause: Only English workout names were mapped - Solution: Added 20+ German workout type mappings: - "Traditionelles Krafttraining" → hypertrophy - "Outdoor Spaziergang" → walk - "Innenräume Spaziergang" → walk - "Matrial Arts" → technique (handles typo) - "Cardio Dance" → dance - "Geist & Körper" → yoga - Plus: Laufen, Gehen, Radfahren, Schwimmen, etc. Issue 2: Reimporting CSV created duplicates without training types - Root cause: Import always did INSERT with new UUID, no duplicate check - Solution: Check if entry exists (profile_id + date + start_time) - If exists: UPDATE with new data + training type mapping - If new: INSERT as before - Handles multiple workouts per day (different start times) - "Skipped" count now includes updated entries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4d9ef5b33b
commit
a4bd738e6f
|
|
@ -122,7 +122,9 @@ def get_training_type_for_apple_health(workout_type: str):
|
||||||
cur = get_cursor(conn)
|
cur = get_cursor(conn)
|
||||||
|
|
||||||
# Mapping: Apple Health Workout Type → training_type subcategory
|
# Mapping: Apple Health Workout Type → training_type subcategory
|
||||||
|
# Supports English and German workout names
|
||||||
mapping = {
|
mapping = {
|
||||||
|
# English
|
||||||
'running': 'running',
|
'running': 'running',
|
||||||
'walking': 'walk',
|
'walking': 'walk',
|
||||||
'hiking': 'walk',
|
'hiking': 'walk',
|
||||||
|
|
@ -141,6 +143,33 @@ def get_training_type_for_apple_health(workout_type: str):
|
||||||
'cooldown': 'regeneration',
|
'cooldown': 'regeneration',
|
||||||
'meditation': 'meditation',
|
'meditation': 'meditation',
|
||||||
'mindfulness': 'mindfulness',
|
'mindfulness': 'mindfulness',
|
||||||
|
|
||||||
|
# German (Deutsch)
|
||||||
|
'laufen': 'running',
|
||||||
|
'gehen': 'walk',
|
||||||
|
'wandern': 'walk',
|
||||||
|
'outdoor spaziergang': 'walk',
|
||||||
|
'innenräume spaziergang': 'walk',
|
||||||
|
'spaziergang': 'walk',
|
||||||
|
'radfahren': 'cycling',
|
||||||
|
'schwimmen': 'swimming',
|
||||||
|
'traditionelles krafttraining': 'hypertrophy',
|
||||||
|
'funktionelles krafttraining': 'functional',
|
||||||
|
'hochintensives intervalltraining': 'hiit',
|
||||||
|
'yoga': 'yoga',
|
||||||
|
'kampfsport': 'technique',
|
||||||
|
'matrial arts': 'technique', # Common typo in Apple Health
|
||||||
|
'boxen': 'sparring',
|
||||||
|
'rudern': 'rowing',
|
||||||
|
'tanzen': 'dance',
|
||||||
|
'cardio dance': 'dance',
|
||||||
|
'core training': 'functional',
|
||||||
|
'flexibilität': 'static',
|
||||||
|
'abwärmen': 'regeneration',
|
||||||
|
'cooldown': 'regeneration',
|
||||||
|
'meditation': 'meditation',
|
||||||
|
'achtsamkeit': 'mindfulness',
|
||||||
|
'geist & körper': 'yoga', # Mind & Body → Yoga category
|
||||||
}
|
}
|
||||||
|
|
||||||
subcategory = mapping.get(workout_type.lower())
|
subcategory = mapping.get(workout_type.lower())
|
||||||
|
|
@ -255,6 +284,42 @@ async def import_activity_csv(file: UploadFile=File(...), x_profile_id: Optional
|
||||||
training_type_id, training_category, training_subcategory = get_training_type_for_apple_health(wtype)
|
training_type_id, training_category, training_subcategory = get_training_type_for_apple_health(wtype)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Check if entry already exists (duplicate detection by date + start_time)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id FROM activity_log
|
||||||
|
WHERE profile_id = %s AND date = %s AND start_time = %s
|
||||||
|
""", (pid, date, start))
|
||||||
|
existing = cur.fetchone()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
# Update existing entry (e.g., to add training type mapping)
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE activity_log
|
||||||
|
SET end_time = %s,
|
||||||
|
activity_type = %s,
|
||||||
|
duration_min = %s,
|
||||||
|
kcal_active = %s,
|
||||||
|
kcal_resting = %s,
|
||||||
|
hr_avg = %s,
|
||||||
|
hr_max = %s,
|
||||||
|
distance_km = %s,
|
||||||
|
training_type_id = %s,
|
||||||
|
training_category = %s,
|
||||||
|
training_subcategory = %s
|
||||||
|
WHERE id = %s
|
||||||
|
""", (
|
||||||
|
row.get('End',''), wtype, duration_min,
|
||||||
|
kj(row.get('Aktive Energie (kJ)','')),
|
||||||
|
kj(row.get('Ruheeinträge (kJ)','')),
|
||||||
|
tf(row.get('Durchschn. Herzfrequenz (count/min)','')),
|
||||||
|
tf(row.get('Max. Herzfrequenz (count/min)','')),
|
||||||
|
tf(row.get('Distanz (km)','')),
|
||||||
|
training_type_id, training_category, training_subcategory,
|
||||||
|
existing['id']
|
||||||
|
))
|
||||||
|
skipped += 1 # Count as skipped (not newly inserted)
|
||||||
|
else:
|
||||||
|
# Insert new entry
|
||||||
cur.execute("""INSERT INTO activity_log
|
cur.execute("""INSERT INTO activity_log
|
||||||
(id,profile_id,date,start_time,end_time,activity_type,duration_min,kcal_active,kcal_resting,
|
(id,profile_id,date,start_time,end_time,activity_type,duration_min,kcal_active,kcal_resting,
|
||||||
hr_avg,hr_max,distance_km,source,training_type_id,training_category,training_subcategory,created)
|
hr_avg,hr_max,distance_km,source,training_type_id,training_category,training_subcategory,created)
|
||||||
|
|
@ -266,5 +331,7 @@ async def import_activity_csv(file: UploadFile=File(...), x_profile_id: Optional
|
||||||
tf(row.get('Distanz (km)','')),
|
tf(row.get('Distanz (km)','')),
|
||||||
training_type_id,training_category,training_subcategory))
|
training_type_id,training_category,training_subcategory))
|
||||||
inserted+=1
|
inserted+=1
|
||||||
except: skipped+=1
|
except Exception as e:
|
||||||
|
logger.warning(f"Import row failed: {e}")
|
||||||
|
skipped+=1
|
||||||
return {"inserted":inserted,"skipped":skipped,"message":f"{inserted} Trainings importiert"}
|
return {"inserted":inserted,"skipped":skipped,"message":f"{inserted} Trainings importiert"}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user