diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7e87b3..0f8ab0d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,10 @@ #### Dashboards -- fix: charging stats now correctly calculate cost when set to miles (#4983 -@DrMichael) +- fix: charging stats now correctly calculate cost when set to miles (#4983 - @DrMichael) - perf: Optimize Grafana query for trip view to leverage indexes more effectively (#4964 - @jaypark0006) -- feat: add shared buffers size to db info dashboard (#4989 -@swiffer) +- feat: add shared buffers size to db info dashboard (#4989 - @swiffer) +- fix: cast to numeric instead of integer when converting from km to miles to avoid rounding issues in all dashboards (#4986 - @swiffer) #### Translations diff --git a/grafana/dashboards/charging-stats.json b/grafana/dashboards/charging-stats.json index 514c4427..589f0eec 100644 --- a/grafana/dashboards/charging-stats.json +++ b/grafana/dashboards/charging-stats.json @@ -450,7 +450,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "-- Query shared between Charging Stats, Statistics & Trip Dashboards (with minor changes) - ensure to modify in all places when necessary\n\nwith drives_start_event as (\n\n select\n 'drive_start' as event, start_date as date, start_${preferred_range}_range_km as range, start_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ndrives_end_event as (\n\n select\n 'drive_end' as event, end_date as date, end_${preferred_range}_range_km as range, end_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_start_event as (\n\n select\n 'charging_process_start' as event, start_date as date, start_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_end_event as (\n\n select\n 'charging_process_end' as event, end_date as date, end_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\npositions as (\n\n select\n case\n when drive_id is not null and lead(drive_id) over w is not null then 'drive_start'\n else 'something'\n end as event,\n date, ${preferred_range}_battery_range_km as range, p.odometer, p.car_id\n from positions p\n where ideal_battery_range_km is not null and car_id = $car_id and 48 > extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n and (drive_id in (select id from drives where $__timeFilter(start_date)) or drive_id is null and $__timeFilter(date))\n window w as (order by date)\n\n),\n\ncombined as (\n\n select * from drives_start_event\n union all\n select * from drives_end_event\n union all\n select * from charging_processes_start_event\n union all\n select * from charging_processes_end_event\n union all\n select * from positions\n\n),\n\nfinal as (\n\n select\n car_id,\n lead(odometer) over w - odometer as distance,\n case when event != 'drive_start' then greatest(range - lead(range) over w, 0) else range - lead(range) over w end as range_loss\n from combined\n window w as (order by date asc)\n\n),\n\nderived as (\n\n select\n sum(distance) as distance,\n sum(range_loss) * c.efficiency as consumption\n from final\n inner join cars c on car_id = c.id\n group by c.efficiency\n\n),\n\ncharges as (\n\n SELECT\n sum(cost) / sum(charge_energy_added) as cost_per_kwh\n FROM charging_processes\n where car_id = $car_id and $__timeFilter(end_date)\n\n)\n\nselect\n consumption / convert_km(distance::integer, '$length_unit') * 100 * cost_per_kwh as cost_mileage\nfrom derived cross join charges", + "rawSql": "-- Query shared between Charging Stats, Statistics & Trip Dashboards (with minor changes) - ensure to modify in all places when necessary\n\nwith drives_start_event as (\n\n select\n 'drive_start' as event, start_date as date, start_${preferred_range}_range_km as range, start_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ndrives_end_event as (\n\n select\n 'drive_end' as event, end_date as date, end_${preferred_range}_range_km as range, end_km as odometer, car_id\n from drives\n where car_id = $car_id and $__timeFilter(start_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_start_event as (\n\n select\n 'charging_process_start' as event, start_date as date, start_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\ncharging_processes_end_event as (\n\n select\n 'charging_process_end' as event, end_date as date, end_${preferred_range}_range_km as range, p.odometer, cp.car_id\n from charging_processes cp\n inner join positions p on cp.position_id = p.id\n where cp.car_id = $car_id and $__timeFilter(end_date) and 48 <= extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n\n),\n\npositions as (\n\n select\n case\n when drive_id is not null and lead(drive_id) over w is not null then 'drive_start'\n else 'something'\n end as event,\n date, ${preferred_range}_battery_range_km as range, p.odometer, p.car_id\n from positions p\n where ideal_battery_range_km is not null and car_id = $car_id and 48 > extract(hour FROM to_timestamp(${__to:date:seconds}) - to_timestamp(${__from:date:seconds}))\n and (drive_id in (select id from drives where $__timeFilter(start_date)) or drive_id is null and $__timeFilter(date))\n window w as (order by date)\n\n),\n\ncombined as (\n\n select * from drives_start_event\n union all\n select * from drives_end_event\n union all\n select * from charging_processes_start_event\n union all\n select * from charging_processes_end_event\n union all\n select * from positions\n\n),\n\nfinal as (\n\n select\n car_id,\n lead(odometer) over w - odometer as distance,\n case when event != 'drive_start' then greatest(range - lead(range) over w, 0) else range - lead(range) over w end as range_loss\n from combined\n window w as (order by date asc)\n\n),\n\nderived as (\n\n select\n convert_km(sum(distance)::numeric, '$length_unit') as distance,\n sum(range_loss) * c.efficiency as consumption\n from final\n inner join cars c on car_id = c.id\n group by c.efficiency\n\n),\n\ncharges as (\n\n SELECT\n sum(cost) / sum(charge_energy_added) as cost_per_kwh\n FROM charging_processes\n where car_id = $car_id and $__timeFilter(end_date)\n\n)\n\nselect\n consumption / distance * 100 * cost_per_kwh as cost_mileage\nfrom derived cross join charges", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/statistics.json b/grafana/dashboards/statistics.json index 82059b1e..9a0dc815 100644 --- a/grafana/dashboards/statistics.json +++ b/grafana/dashboards/statistics.json @@ -651,7 +651,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH data AS (\nSELECT\n duration_min > 1 AND\n distance > 1 AND\n ( \n start_position.usable_battery_level IS NULL OR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) AS is_sufficiently_precise,\n start_${preferred_range}_range_km - end_${preferred_range}_range_km AS range_diff,\n date_trunc('$period', timezone('UTC', start_date), '$__timezone') as date,\n drives.*\nFROM drives\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id)\nSELECT\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n sum(duration_min)*60 AS sum_duration_h, \n convert_km(max(end_km)::integer - min(start_km)::integer, '$length_unit') AS sum_distance_$length_unit,\n convert_celsius(avg(outside_temp_avg), '$temp_unit') AS avg_outside_temp_$temp_unit,\n count(*) AS cnt,\n case when sum(range_diff) > 0 then sum(distance)/sum(range_diff) else null end AS efficiency\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date", + "rawSql": "WITH data AS (\nSELECT\n duration_min > 1 AND\n distance > 1 AND\n ( \n start_position.usable_battery_level IS NULL OR\n (end_position.battery_level - end_position.usable_battery_level) = 0 \n ) AS is_sufficiently_precise,\n start_${preferred_range}_range_km - end_${preferred_range}_range_km AS range_diff,\n date_trunc('$period', timezone('UTC', start_date), '$__timezone') as date,\n drives.*\nFROM drives\n LEFT JOIN positions start_position ON start_position_id = start_position.id\n LEFT JOIN positions end_position ON end_position_id = end_position.id)\nSELECT\n EXTRACT(EPOCH FROM date)*1000 AS date_from,\n EXTRACT(EPOCH FROM date + interval '1 $period')*1000 AS date_to,\n CASE '$period'\n WHEN 'month' THEN to_char(timezone('$__timezone', date), 'YYYY Month')\n WHEN 'year' THEN to_char(timezone('$__timezone', date), 'YYYY')\n WHEN 'week' THEN 'week ' || to_char(timezone('$__timezone', date), 'WW') || ' starting ' || to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n ELSE to_char(timezone('$__timezone', date), 'YYYY-MM-DD')\n END AS display,\n date,\n sum(duration_min)*60 AS sum_duration_h, \n convert_km(max(end_km)::numeric - min(start_km)::numeric, '$length_unit') AS sum_distance_$length_unit,\n convert_celsius(avg(outside_temp_avg), '$temp_unit') AS avg_outside_temp_$temp_unit,\n count(*) AS cnt,\n case when sum(range_diff) > 0 then sum(distance)/sum(range_diff) else null end AS efficiency\nFROM data WHERE\n car_id = $car_id AND\n $__timeFilter(start_date)\nGROUP BY date", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/timeline.json b/grafana/dashboards/timeline.json index b4160541..0607e2c3 100644 --- a/grafana/dashboards/timeline.json +++ b/grafana/dashboards/timeline.json @@ -525,7 +525,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "-- CTE is used in Parking Query\r\nwith drives_and_charging_processes as (\r\n\r\n select 'Drive' as activity, d.start_date, d.end_date, d.start_position_id, d.end_position_id, d.end_address_id, d.end_geofence_id, d.start_${preferred_range}_range_km, d.end_${preferred_range}_range_km, d.car_id, d.outside_temp_avg from drives d\r\n \r\n union all\r\n \r\n select 'Charging Process' as activity, cp.start_date, cp.end_date, cp.position_id as start_position_id, cp.position_id as end_position_id, cp.address_id as end_address_id, cp.geofence_id as end_geofence_id, cp.start_${preferred_range}_range_km, cp.end_${preferred_range}_range_km, cp.car_id, cp.outside_temp_avg from charging_processes cp\r\n\r\n)\r\n\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🚗 Driving' AS \"Action\",\r\n drives.duration_min AS \"Duration\",\r\n CASE WHEN start_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(start_geofence_id, '/edit')\r\n END AS start_path,\r\n CASE WHEN end_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(end_geofence_id, '/edit')\r\n END AS end_path,\r\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n convert_km(end_km::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n convert_km(distance::NUMERIC, '$length_unit') AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n (end_${preferred_range}_range_km - start_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n TP2.battery_level AS \"SoC\",\r\n TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/zm7wN6Zgz/drive-details?from=', ROUND(EXTRACT(EPOCH FROM start_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date))*1000, '&var-car_id=', drives.car_id, '&var-drive_id=', drives.id) AS slotlink\r\nFROM drives\r\n INNER JOIN cars AS car ON drives.car_id = car.id\r\n INNER JOIN positions AS TP1 on drives.start_position_id = TP1.id\r\n INNER JOIN positions AS TP2 on drives.end_position_id = TP2.id\r\n INNER JOIN addresses start_address ON start_address_id = start_address.id\r\n INNER JOIN addresses end_address ON end_address_id = end_address.id\r\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\r\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\r\nWHERE \r\n $__timeFilter(drives.start_date)\r\n AND drives.car_id = $car_id\r\n AND '🚗 Driving' in ($action_filter)\r\n AND\r\n (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%' or\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city))::TEXT ILIKE'%$text_filter%')\r\n\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🔋 Charging' AS \"Action\",\r\n charging_processes.duration_min AS \"Duration\",\r\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', address.latitude, '&lng=', address.longitude)\r\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n charging_processes.charge_energy_added AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit, \r\n end_battery_level AS \"SoC\",\r\n end_battery_level - start_battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/BHhxFeZRz/charge-details?from=', ROUND(EXTRACT(EPOCH FROM start_date)-10)*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date)+10)*1000, '&var-car_id=', charging_processes.car_id, '&var-charging_process_id=', charging_processes.id) AS slotlink\r\nFROM charging_processes\r\n INNER JOIN positions AS position ON position_id = position.id\r\n INNER JOIN addresses AS address ON address_id = address.id\r\n LEFT JOIN geofences AS geofence ON geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(charging_processes.start_date)\r\n AND charging_processes.charge_energy_added > 0\r\n AND charging_processes.car_id = $car_id\r\n AND '🔋 Charging' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nUNION\r\nSELECT\r\n d.end_date AS \"Start\",\r\n LEAD(d.start_date) over w AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM d.end_date)) * 1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000 AS end_date_ts,\r\n '🅿️ Parking' AS \"Action\",\r\n EXTRACT(EPOCH FROM LEAD(d.start_date) over w - d.end_date)/60 AS \"Duration\",\r\n CASE WHEN d.end_geofence_id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\r\n WHEN d.end_geofence_id IS NOT NULL THEN CONCAT(d.end_geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(end_position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(LEAD(d.start_${preferred_range}_range_km) over w::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n ((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km(((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n LEAD(start_position.battery_level) over w AS \"SoC\",\r\n LEAD(start_position.battery_level) over w - end_position.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/FkUpJpQZk/trip?from=', ROUND(EXTRACT(EPOCH FROM d.end_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000, '&var-car_id=', d.car_id) AS slotlink\r\nFROM drives_and_charging_processes AS d\r\n INNER JOIN cars AS car ON d.car_id = car.id\r\n INNER JOIN positions AS start_position on d.start_position_id = start_position.id\r\n INNER JOIN positions AS end_position on d.end_position_id = end_position.id\r\n INNER JOIN addresses AS address ON d.end_address_id = address.id\r\n LEFT JOIN geofences AS geofence ON d.end_geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(d.end_date)\r\n AND d.car_id=$car_id\r\n AND '🅿️ Parking' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nWINDOW w as (ORDER BY d.start_date ASC)\r\n\r\nUNION\r\nSELECT\r\n\tT1.end_date +(1 * interval '1 second') AS \"Start\", -- added 1 sec to get it after the corresponding Parking row\r\n\tT2.start_date AS \"End\",\r\n\tROUND(EXTRACT(EPOCH FROM T1.end_date)) * 1000 - 1 AS start_date_ts,\r\n\tROUND(EXTRACT(EPOCH FROM T2.start_date)) * 1000 - 1 AS end_date_ts,\r\n\t'❓ Missing' AS \"Action\",\r\n\t-- EXTRACT(EPOCH FROM T2.start_date - T1.end_date)/60 AS \"Duration\",\r\n\tNULL AS \"Duration\",\r\n\tCASE WHEN T1.end_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n\t\tWHEN T1.end_geofence_id IS NOT NULL THEN CONCAT(T1.end_geofence_id, '/edit')\r\n\tEND AS start_path,\r\n\tCASE WHEN T2.start_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n\t\tWHEN T2.start_geofence_id IS NOT NULL THEN CONCAT(T2.start_geofence_id, '/edit')\r\n\tEND AS end_path,\r\n\tCOALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n\tCOALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n\tconvert_km(TP2.odometer::INTEGER, '$length_unit') AS odometer_$length_unit,\r\n\tconvert_km((TP2.odometer - TP1.odometer)::INTEGER, '$length_unit') AS distance_$length_unit,\r\n convert_km(T2.end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n\t((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km) * car.efficiency AS \"kWh\",\r\n\tconvert_km(((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km)::INTEGER, '$length_unit') AS range_diff_$length_unit,\r\n\tNULL AS \"SoC\",\r\n\tNULL AS \"SoC Diff\",\r\n\tNULL AS outside_temp_avg_$temp_unit,\r\n\tNULL AS slotlink\r\n\t-- TP2.battery_level AS \"SoC\",\r\n\t-- TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n\t-- (T1.outside_temp_avg+T2.outside_temp_avg)/2 AS outside_temp_avg_$temp_unit\r\nFROM drives AS T1\r\n INNER JOIN cars AS car ON T1.car_id = car.id\r\n\tINNER JOIN (SELECT d.*, LAG(id) OVER (ORDER BY id ASC) AS previous_id FROM drives d WHERE d.car_id = $car_id) AS T2 ON T1.id = T2.previous_id\r\n\tINNER JOIN positions AS TP1 ON T1.end_position_id = TP1.id\r\n\tINNER JOIN positions AS TP2 ON T2.start_position_id = TP2.id\r\n\tINNER JOIN addresses AS start_address ON T1.end_address_id = start_address.id\r\n\tINNER JOIN addresses AS end_address ON T2.start_address_id = end_address.id\r\n\tLEFT JOIN geofences AS start_geofence ON T1.end_geofence_id = start_geofence.id\r\n\tLEFT JOIN geofences AS end_geofence ON T2.start_geofence_id = end_geofence.id\r\nWHERE\r\n\t$__timeFilter(T1.end_date)\r\n\tAND TP2.odometer - TP1.odometer > 0.5\r\n AND T1.end_address_id <> T2.start_address_id AND ((COALESCE(T1.end_geofence_id, 0) <> COALESCE(T2.start_geofence_id, 0)) OR (T1.end_geofence_id IS NULL AND T2.start_geofence_id IS NULL))\r\n AND '❓ Missing' in ($action_filter)\r\n\tAND (\r\n\t (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%') or\r\n\t (COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)))::TEXT ILIKE'%$text_filter%')\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts, \r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts, \r\n '💾 Updating' AS \"Action\",\r\n\tEXTRACT(EPOCH FROM end_date - start_date)/60 AS \"Duration\",\r\n NULL AS start_path,\r\n NULL AS end_path,\r\n version AS \"Start Address\",\r\n '' AS \"End Address\",\r\n NULL AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n NULL AS end_range_$length_unit,\r\n NULL AS \"kWh\",\r\n NULL AS range_diff_$length_unit,\r\n NULL AS \"SoC\",\r\n NULL AS \"SoC Diff\",\r\n NULL AS outside_temp_avg_$temp_unit,\r\n CONCAT('https://www.notateslaapp.com/software-updates/version/', split_part(version, ' ', 1), '/release-notes') AS slotlink\r\nFROM updates\r\nWHERE \r\n $__timeFilter(start_date)\r\n AND car_id = $car_id \r\n AND '💾 Updating' in ($action_filter)\r\n AND version::TEXT ILIKE'%$text_filter%'\r\n\r\nORDER BY \"Start\" DESC;", + "rawSql": "-- CTE is used in Parking Query\r\nwith drives_and_charging_processes as (\r\n\r\n select 'Drive' as activity, d.start_date, d.end_date, d.start_position_id, d.end_position_id, d.end_address_id, d.end_geofence_id, d.start_${preferred_range}_range_km, d.end_${preferred_range}_range_km, d.car_id, d.outside_temp_avg from drives d\r\n \r\n union all\r\n \r\n select 'Charging Process' as activity, cp.start_date, cp.end_date, cp.position_id as start_position_id, cp.position_id as end_position_id, cp.address_id as end_address_id, cp.geofence_id as end_geofence_id, cp.start_${preferred_range}_range_km, cp.end_${preferred_range}_range_km, cp.car_id, cp.outside_temp_avg from charging_processes cp\r\n\r\n)\r\n\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🚗 Driving' AS \"Action\",\r\n drives.duration_min AS \"Duration\",\r\n CASE WHEN start_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(start_geofence_id, '/edit')\r\n END AS start_path,\r\n CASE WHEN end_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n WHEN start_geofence_id IS NOT NULL THEN CONCAT(end_geofence_id, '/edit')\r\n END AS end_path,\r\n COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n convert_km(end_km::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n convert_km(distance::NUMERIC, '$length_unit') AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n (end_${preferred_range}_range_km - start_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n TP2.battery_level AS \"SoC\",\r\n TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/zm7wN6Zgz/drive-details?from=', ROUND(EXTRACT(EPOCH FROM start_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date))*1000, '&var-car_id=', drives.car_id, '&var-drive_id=', drives.id) AS slotlink\r\nFROM drives\r\n INNER JOIN cars AS car ON drives.car_id = car.id\r\n INNER JOIN positions AS TP1 on drives.start_position_id = TP1.id\r\n INNER JOIN positions AS TP2 on drives.end_position_id = TP2.id\r\n INNER JOIN addresses start_address ON start_address_id = start_address.id\r\n INNER JOIN addresses end_address ON end_address_id = end_address.id\r\n LEFT JOIN geofences start_geofence ON start_geofence_id = start_geofence.id\r\n LEFT JOIN geofences end_geofence ON end_geofence_id = end_geofence.id\r\nWHERE \r\n $__timeFilter(drives.start_date)\r\n AND drives.car_id = $car_id\r\n AND '🚗 Driving' in ($action_filter)\r\n AND\r\n (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%' or\r\n COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city))::TEXT ILIKE'%$text_filter%')\r\n\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts,\r\n '🔋 Charging' AS \"Action\",\r\n charging_processes.duration_min AS \"Duration\",\r\n CASE WHEN geofence_id IS NULL THEN CONCAT('new?lat=', address.latitude, '&lng=', address.longitude)\r\n WHEN geofence_id IS NOT NULL THEN CONCAT(geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n charging_processes.charge_energy_added AS \"kWh\",\r\n convert_km((end_${preferred_range}_range_km - start_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit, \r\n end_battery_level AS \"SoC\",\r\n end_battery_level - start_battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/BHhxFeZRz/charge-details?from=', ROUND(EXTRACT(EPOCH FROM start_date)-10)*1000, '&to=', ROUND(EXTRACT(EPOCH FROM end_date)+10)*1000, '&var-car_id=', charging_processes.car_id, '&var-charging_process_id=', charging_processes.id) AS slotlink\r\nFROM charging_processes\r\n INNER JOIN positions AS position ON position_id = position.id\r\n INNER JOIN addresses AS address ON address_id = address.id\r\n LEFT JOIN geofences AS geofence ON geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(charging_processes.start_date)\r\n AND charging_processes.charge_energy_added > 0\r\n AND charging_processes.car_id = $car_id\r\n AND '🔋 Charging' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nUNION\r\nSELECT\r\n d.end_date AS \"Start\",\r\n LEAD(d.start_date) over w AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM d.end_date)) * 1000 AS start_date_ts,\r\n ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000 AS end_date_ts,\r\n '🅿️ Parking' AS \"Action\",\r\n EXTRACT(EPOCH FROM LEAD(d.start_date) over w - d.end_date)/60 AS \"Duration\",\r\n CASE WHEN d.end_geofence_id IS NULL THEN CONCAT('new?lat=', end_position.latitude, '&lng=', end_position.longitude)\r\n WHEN d.end_geofence_id IS NOT NULL THEN CONCAT(d.end_geofence_id, '/edit')\r\n END AS start_path,\r\n NULL AS end_path,\r\n COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city)) AS \"Start Address\",\r\n '' AS \"End Address\",\r\n convert_km(end_position.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n convert_km(LEAD(d.start_${preferred_range}_range_km) over w::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n ((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km) * car.efficiency AS \"kWh\",\r\n convert_km(((LEAD(d.start_${preferred_range}_range_km) over w + (LEAD(start_position.odometer) over w - end_position.odometer)) - d.end_${preferred_range}_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n LEAD(start_position.battery_level) over w AS \"SoC\",\r\n LEAD(start_position.battery_level) over w - end_position.battery_level AS \"SoC Diff\",\r\n convert_celsius(outside_temp_avg, '$temp_unit') AS outside_temp_avg_$temp_unit,\r\n CONCAT('d/FkUpJpQZk/trip?from=', ROUND(EXTRACT(EPOCH FROM d.end_date))*1000, '&to=', ROUND(EXTRACT(EPOCH FROM LEAD(d.start_date) over w))*1000, '&var-car_id=', d.car_id) AS slotlink\r\nFROM drives_and_charging_processes AS d\r\n INNER JOIN cars AS car ON d.car_id = car.id\r\n INNER JOIN positions AS start_position on d.start_position_id = start_position.id\r\n INNER JOIN positions AS end_position on d.end_position_id = end_position.id\r\n INNER JOIN addresses AS address ON d.end_address_id = address.id\r\n LEFT JOIN geofences AS geofence ON d.end_geofence_id = geofence.id\r\nWHERE\r\n $__timeFilter(d.end_date)\r\n AND d.car_id=$car_id\r\n AND '🅿️ Parking' in ($action_filter)\r\n AND COALESCE(geofence.name, CONCAT_WS(', ', COALESCE(address.name, nullif(CONCAT_WS(' ', address.road, address.house_number), '')), address.city))::TEXT ILIKE'%$text_filter%'\r\nWINDOW w as (ORDER BY d.start_date ASC)\r\n\r\nUNION\r\nSELECT\r\n\tT1.end_date +(1 * interval '1 second') AS \"Start\", -- added 1 sec to get it after the corresponding Parking row\r\n\tT2.start_date AS \"End\",\r\n\tROUND(EXTRACT(EPOCH FROM T1.end_date)) * 1000 - 1 AS start_date_ts,\r\n\tROUND(EXTRACT(EPOCH FROM T2.start_date)) * 1000 - 1 AS end_date_ts,\r\n\t'❓ Missing' AS \"Action\",\r\n\t-- EXTRACT(EPOCH FROM T2.start_date - T1.end_date)/60 AS \"Duration\",\r\n\tNULL AS \"Duration\",\r\n\tCASE WHEN T1.end_geofence_id IS NULL THEN CONCAT('new?lat=', TP1.latitude, '&lng=', TP1.longitude)\r\n\t\tWHEN T1.end_geofence_id IS NOT NULL THEN CONCAT(T1.end_geofence_id, '/edit')\r\n\tEND AS start_path,\r\n\tCASE WHEN T2.start_geofence_id IS NULL THEN CONCAT('new?lat=', TP2.latitude, '&lng=', TP2.longitude)\r\n\t\tWHEN T2.start_geofence_id IS NOT NULL THEN CONCAT(T2.start_geofence_id, '/edit')\r\n\tEND AS end_path,\r\n\tCOALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city)) AS \"Start Address\",\r\n\tCOALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)) AS \"End Address\",\r\n\tconvert_km(TP2.odometer::NUMERIC, '$length_unit') AS odometer_$length_unit,\r\n\tconvert_km((TP2.odometer - TP1.odometer)::NUMERIC, '$length_unit') AS distance_$length_unit,\r\n convert_km(T2.end_${preferred_range}_range_km::NUMERIC, '$length_unit') AS end_range_$length_unit,\r\n\t((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km) * car.efficiency AS \"kWh\",\r\n\tconvert_km(((TP2.${preferred_range}_battery_range_km + (TP2.odometer - TP1.odometer)) - TP1.${preferred_range}_battery_range_km)::NUMERIC, '$length_unit') AS range_diff_$length_unit,\r\n\tNULL AS \"SoC\",\r\n\tNULL AS \"SoC Diff\",\r\n\tNULL AS outside_temp_avg_$temp_unit,\r\n\tNULL AS slotlink\r\n\t-- TP2.battery_level AS \"SoC\",\r\n\t-- TP2.battery_level-TP1.battery_level AS \"SoC Diff\",\r\n\t-- (T1.outside_temp_avg+T2.outside_temp_avg)/2 AS outside_temp_avg_$temp_unit\r\nFROM drives AS T1\r\n INNER JOIN cars AS car ON T1.car_id = car.id\r\n\tINNER JOIN (SELECT d.*, LAG(id) OVER (ORDER BY id ASC) AS previous_id FROM drives d WHERE d.car_id = $car_id) AS T2 ON T1.id = T2.previous_id\r\n\tINNER JOIN positions AS TP1 ON T1.end_position_id = TP1.id\r\n\tINNER JOIN positions AS TP2 ON T2.start_position_id = TP2.id\r\n\tINNER JOIN addresses AS start_address ON T1.end_address_id = start_address.id\r\n\tINNER JOIN addresses AS end_address ON T2.start_address_id = end_address.id\r\n\tLEFT JOIN geofences AS start_geofence ON T1.end_geofence_id = start_geofence.id\r\n\tLEFT JOIN geofences AS end_geofence ON T2.start_geofence_id = end_geofence.id\r\nWHERE\r\n\t$__timeFilter(T1.end_date)\r\n\tAND TP2.odometer - TP1.odometer > 0.5\r\n AND T1.end_address_id <> T2.start_address_id AND ((COALESCE(T1.end_geofence_id, 0) <> COALESCE(T2.start_geofence_id, 0)) OR (T1.end_geofence_id IS NULL AND T2.start_geofence_id IS NULL))\r\n AND '❓ Missing' in ($action_filter)\r\n\tAND (\r\n\t (COALESCE(start_geofence.name, CONCAT_WS(', ', COALESCE(start_address.name, nullif(CONCAT_WS(' ', start_address.road, start_address.house_number), '')), start_address.city))::TEXT ILIKE'%$text_filter%') or\r\n\t (COALESCE(end_geofence.name, CONCAT_WS(', ', COALESCE(end_address.name, nullif(CONCAT_WS(' ', end_address.road, end_address.house_number), '')), end_address.city)))::TEXT ILIKE'%$text_filter%')\r\nUNION\r\nSELECT\r\n start_date AS \"Start\",\r\n end_date AS \"End\",\r\n ROUND(EXTRACT(EPOCH FROM start_date))*1000 AS start_date_ts, \r\n ROUND(EXTRACT(EPOCH FROM end_date))*1000 AS end_date_ts, \r\n '💾 Updating' AS \"Action\",\r\n\tEXTRACT(EPOCH FROM end_date - start_date)/60 AS \"Duration\",\r\n NULL AS start_path,\r\n NULL AS end_path,\r\n version AS \"Start Address\",\r\n '' AS \"End Address\",\r\n NULL AS odometer_$length_unit,\r\n NULL AS distance_$length_unit,\r\n NULL AS end_range_$length_unit,\r\n NULL AS \"kWh\",\r\n NULL AS range_diff_$length_unit,\r\n NULL AS \"SoC\",\r\n NULL AS \"SoC Diff\",\r\n NULL AS outside_temp_avg_$temp_unit,\r\n CONCAT('https://www.notateslaapp.com/software-updates/version/', split_part(version, ' ', 1), '/release-notes') AS slotlink\r\nFROM updates\r\nWHERE \r\n $__timeFilter(start_date)\r\n AND car_id = $car_id \r\n AND '💾 Updating' in ($action_filter)\r\n AND version::TEXT ILIKE'%$text_filter%'\r\n\r\nORDER BY \"Start\" DESC;", "refId": "A", "sql": { "columns": [