-- Migration 043: CSV Parser – System-Templates (Issue #21) -- Idempotent: pro Template nur einfügen, wenn noch kein System-Eintrag für module+mapping_name existiert. INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'nutrition', 'FDDB Export (Standard)', 'Standard-Format für FDDB.de CSV-Exporte (Deutsch). Delimiter Semikolon, kJ → kcal Konvertierung.', ARRAY['datum_tag_monat_jahr_stunde_minute', 'fett_g', 'kh_g', 'kj', 'protein_g']::TEXT[], ';', 'utf-8', true, '{ "datum_tag_monat_jahr_stunde_minute": "date", "kj": "kcal", "fett_g": "fat_g", "kh_g": "carbs_g", "protein_g": "protein_g" }'::JSONB, '{ "date": { "type": "date", "format": "dd.mm.yyyy HH:MM", "extract": "date_only" }, "kcal": { "type": "float", "source_unit": "kj", "decimal_separator": "," }, "fat_g": {"type": "float", "decimal_separator": ","}, "carbs_g": {"type": "float", "decimal_separator": ","}, "protein_g": {"type": "float", "decimal_separator": ","} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'nutrition' AND f.mapping_name = 'FDDB Export (Standard)' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'nutrition', 'MyFitnessPal Export', 'Standard CSV export from MyFitnessPal (English)', ARRAY['Carbohydrates (g)', 'Calories', 'Date', 'Fat (g)', 'Protein (g)']::TEXT[], ',', 'utf-8', true, '{ "Date": "date", "Calories": "kcal", "Fat (g)": "fat_g", "Carbohydrates (g)": "carbs_g", "Protein (g)": "protein_g" }'::JSONB, '{ "date": {"type": "date", "format": "yyyy-mm-dd"}, "kcal": {"type": "float", "decimal_separator": "."}, "fat_g": {"type": "float", "decimal_separator": "."}, "carbs_g": {"type": "float", "decimal_separator": "."}, "protein_g": {"type": "float", "decimal_separator": "."} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'nutrition' AND f.mapping_name = 'MyFitnessPal Export' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'nutrition', 'Cronometer Export', 'Cronometer daily nutrition export (English)', ARRAY['Day', 'Energy (kcal)', 'Fat (g)', 'Net Carbs (g)', 'Protein (g)']::TEXT[], ',', 'utf-8', true, '{ "Day": "date", "Energy (kcal)": "kcal", "Fat (g)": "fat_g", "Net Carbs (g)": "carbs_g", "Protein (g)": "protein_g" }'::JSONB, '{ "date": {"type": "date", "format": "yyyy-mm-dd"}, "kcal": {"type": "float", "decimal_separator": "."}, "fat_g": {"type": "float", "decimal_separator": "."}, "carbs_g": {"type": "float", "decimal_separator": "."}, "protein_g": {"type": "float", "decimal_separator": "."} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'nutrition' AND f.mapping_name = 'Cronometer Export' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'activity', 'Apple Health Workout Export (English)', 'Apple Health CSV-Export für Workouts (English). Automatisches Training-Type-Mapping.', ARRAY['Active Energy (kcal)', 'Distance (km)', 'Duration', 'End', 'Heart Rate Average (bpm)', 'Start', 'Workout Type']::TEXT[], ',', 'utf-8', true, '{ "Workout Type": "activity_type", "Start": "start_time", "End": "end_time", "Duration": "duration_min", "Distance (km)": "distance_km", "Active Energy (kcal)": "kcal_active", "Heart Rate Average (bpm)": "hr_avg" }'::JSONB, '{ "start_time": {"type": "datetime", "format": "yyyy-mm-dd HH:MM:SS", "extract": "date_and_time"}, "end_time": {"type": "datetime", "format": "yyyy-mm-dd HH:MM:SS"}, "duration_min": {"type": "duration", "format": "HH:MM:SS", "target_unit": "minutes"}, "distance_km": {"type": "float", "decimal_separator": "."}, "kcal_active": {"type": "float", "decimal_separator": "."}, "hr_avg": {"type": "int"} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'activity' AND f.mapping_name = 'Apple Health Workout Export (English)' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'activity', 'Apple Health Workout Export (Deutsch)', 'Apple Health CSV-Export für Workouts (Deutsch). Automatisches Training-Type-Mapping.', ARRAY['Aktive Energie (kcal)', 'Dauer', 'Durchschnittliche Herzfrequenz (bpm)', 'Ende', 'Start', 'Strecke (km)', 'Trainingsart']::TEXT[], ',', 'utf-8', true, '{ "Trainingsart": "activity_type", "Start": "start_time", "Ende": "end_time", "Dauer": "duration_min", "Strecke (km)": "distance_km", "Aktive Energie (kcal)": "kcal_active", "Durchschnittliche Herzfrequenz (bpm)": "hr_avg" }'::JSONB, '{ "start_time": {"type": "datetime", "format": "yyyy-mm-dd HH:MM:SS", "extract": "date_and_time"}, "end_time": {"type": "datetime", "format": "yyyy-mm-dd HH:MM:SS"}, "duration_min": {"type": "duration", "format": "HH:MM:SS", "target_unit": "minutes"}, "distance_km": {"type": "float", "decimal_separator": ","}, "kcal_active": {"type": "float", "decimal_separator": ","}, "hr_avg": {"type": "int"} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'activity' AND f.mapping_name = 'Apple Health Workout Export (Deutsch)' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'activity', 'Garmin Connect Export', 'Garmin Connect activity CSV export (English)', ARRAY['Activity Type', 'Avg HR', 'Calories', 'Date', 'Distance', 'Duration', 'Time']::TEXT[], ',', 'utf-8', true, '{ "Activity Type": "activity_type", "Date": "date", "Time": "start_time", "Duration": "duration_min", "Distance": "distance_km", "Calories": "kcal_active", "Avg HR": "hr_avg" }'::JSONB, '{ "date": {"type": "date", "format": "yyyy-mm-dd"}, "start_time": {"type": "time", "format": "HH:MM:SS"}, "duration_min": {"type": "duration", "format": "HH:MM:SS", "target_unit": "minutes"}, "distance_km": {"type": "float", "decimal_separator": "."}, "kcal_active": {"type": "float", "decimal_separator": "."}, "hr_avg": {"type": "int"} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'activity' AND f.mapping_name = 'Garmin Connect Export' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'blood_pressure', 'Omron Export (Deutsch)', 'Omron Blutdruckmessgerät CSV-Export (Deutsch)', ARRAY['Datum', 'Diastolisch (mmHg)', 'Puls (bpm)', 'Systolisch (mmHg)', 'Zeit']::TEXT[], ',', 'utf-8', true, '{ "Datum": "measured_date", "Zeit": "measured_time", "Systolisch (mmHg)": "systolic", "Diastolisch (mmHg)": "diastolic", "Puls (bpm)": "pulse" }'::JSONB, '{ "measured_date": {"type": "date", "format": "dd.mm.yyyy"}, "measured_time": {"type": "time", "format": "HH:MM"}, "systolic": {"type": "int"}, "diastolic": {"type": "int"}, "pulse": {"type": "int"} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'blood_pressure' AND f.mapping_name = 'Omron Export (Deutsch)' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'blood_pressure', 'Omron Export (English)', 'Omron blood pressure monitor CSV export (English)', ARRAY['Date', 'Diastolic (mmHg)', 'Pulse (bpm)', 'Systolic (mmHg)', 'Time']::TEXT[], ',', 'utf-8', true, '{ "Date": "measured_date", "Time": "measured_time", "Systolic (mmHg)": "systolic", "Diastolic (mmHg)": "diastolic", "Pulse (bpm)": "pulse" }'::JSONB, '{ "measured_date": {"type": "date", "format": "mm/dd/yyyy"}, "measured_time": {"type": "time", "format": "HH:MM"}, "systolic": {"type": "int"}, "diastolic": {"type": "int"}, "pulse": {"type": "int"} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'blood_pressure' AND f.mapping_name = 'Omron Export (English)' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'weight', 'Apple Health Weight Export', 'Apple Health body mass CSV export', ARRAY['Body Mass (kg)', 'Start']::TEXT[], ',', 'utf-8', true, '{ "Start": "date", "Body Mass (kg)": "weight" }'::JSONB, '{ "date": {"type": "datetime", "format": "yyyy-mm-dd HH:MM:SS", "extract": "date_only"}, "weight": {"type": "float", "decimal_separator": "."} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'weight' AND f.mapping_name = 'Apple Health Weight Export' ); INSERT INTO csv_field_mappings ( profile_id, is_system, module, mapping_name, description, column_signature, delimiter, encoding, has_header, field_mappings, type_conversions ) SELECT NULL, true, 'weight', 'Withings Export', 'Withings smart scale CSV export (weight, body fat, muscle mass)', ARRAY['Body Fat (%)', 'Date', 'Muscle Mass (kg)', 'Weight (kg)']::TEXT[], ',', 'utf-8', true, '{ "Date": "date", "Weight (kg)": "weight" }'::JSONB, '{ "date": {"type": "date", "format": "yyyy-mm-dd"}, "weight": {"type": "float", "decimal_separator": "."} }'::JSONB WHERE NOT EXISTS ( SELECT 1 FROM csv_field_mappings f WHERE f.is_system AND f.profile_id IS NULL AND f.module = 'weight' AND f.mapping_name = 'Withings Export' );