diff --git a/backend/routers/activity.py b/backend/routers/activity.py index 956dc0c..8b9da64 100644 --- a/backend/routers/activity.py +++ b/backend/routers/activity.py @@ -122,7 +122,9 @@ def get_training_type_for_apple_health(workout_type: str): cur = get_cursor(conn) # Mapping: Apple Health Workout Type → training_type subcategory + # Supports English and German workout names mapping = { + # English 'running': 'running', 'walking': 'walk', 'hiking': 'walk', @@ -141,6 +143,33 @@ def get_training_type_for_apple_health(workout_type: str): 'cooldown': 'regeneration', 'meditation': 'meditation', '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()) @@ -255,16 +284,54 @@ 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) try: - cur.execute("""INSERT INTO activity_log - (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) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,'apple_health',%s,%s,%s,CURRENT_TIMESTAMP)""", - (str(uuid.uuid4()),pid,date,start,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)) - inserted+=1 - except: skipped+=1 + # 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 + (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) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,'apple_health',%s,%s,%s,CURRENT_TIMESTAMP)""", + (str(uuid.uuid4()),pid,date,start,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)) + inserted+=1 + except Exception as e: + logger.warning(f"Import row failed: {e}") + skipped+=1 return {"inserted":inserted,"skipped":skipped,"message":f"{inserted} Trainings importiert"}