diff --git a/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_insert_all_fact_space_occupancy_count.sql b/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_insert_all_fact_space_occupancy_count.sql index 32a7283..845c9ea 100644 --- a/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_insert_all_fact_space_occupancy_count.sql +++ b/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_insert_all_fact_space_occupancy_count.sql @@ -1,3 +1,4 @@ +-- 1. Load all presence-related logs WITH device_logs AS ( SELECT device.uuid AS device_id, @@ -15,7 +16,7 @@ WITH device_logs AS ( AND "device-status-log".code = 'presence_state' ), --- 1. All 'none' → presence or motion +-- 2. Standard transitions: 'none' → 'motion' or 'presence' presence_transitions AS ( SELECT space_id, @@ -23,10 +24,31 @@ presence_transitions AS ( event_time::date AS event_date, value FROM device_logs - WHERE (value = 'motion' OR value = 'presence') AND prev_value = 'none' + WHERE value IN ('motion', 'presence') AND prev_value = 'none' ), --- 2. Cluster events per space_id within 30s +-- 3. Fallback: days with 'motion' or 'presence' but no 'none' +fallback_daily_presence AS ( + SELECT + space_id, + event_time::date AS event_date, + MIN(event_time) AS event_time, + 'presence'::text AS value + FROM device_logs + WHERE value IN ('motion', 'presence', 'none') + GROUP BY space_id, event_time::date + HAVING BOOL_OR(value = 'motion') OR BOOL_OR(value = 'presence') + AND NOT BOOL_OR(value = 'none') +), + +-- 4. Merge both detection sources +all_presence_events AS ( + SELECT * FROM presence_transitions + UNION ALL + SELECT space_id, event_time, event_date, value FROM fallback_daily_presence +), + +-- 5. Cluster events per space_id within 30 seconds clustered_events AS ( SELECT space_id, @@ -40,11 +62,11 @@ clustered_events AS ( WHEN event_time - LAG(event_time) OVER (PARTITION BY space_id ORDER BY event_time) > INTERVAL '30 seconds' THEN 1 ELSE 0 END AS new_cluster_flag - FROM presence_transitions + FROM all_presence_events ) marked ), --- 3. Determine dominant type (motion vs presence) per cluster +-- 6. Determine dominant type (motion vs presence) per cluster cluster_type AS ( SELECT space_id, @@ -60,7 +82,7 @@ cluster_type AS ( GROUP BY space_id, event_date, cluster_id ), --- 4. Count clusters by dominant type +-- 7. Count clusters by dominant type summary AS ( SELECT space_id, @@ -70,15 +92,16 @@ summary AS ( COUNT(*) AS count_total_presence_detected FROM cluster_type GROUP BY space_id, event_date +), + +-- 8. Prepare final result +final_table AS ( + SELECT * + FROM summary + ORDER BY space_id, event_date ) --- 5. Output -, final_table as ( -SELECT * -FROM summary -ORDER BY space_id, event_date) - - +-- 9. Insert or upsert into the destination table INSERT INTO public."presence-sensor-daily-space-detection" ( space_uuid, event_date, @@ -97,4 +120,4 @@ ON CONFLICT (space_uuid, event_date) DO UPDATE SET count_motion_detected = EXCLUDED.count_motion_detected, count_presence_detected = EXCLUDED.count_presence_detected, - count_total_presence_detected = EXCLUDED.count_total_presence_detected; \ No newline at end of file + count_total_presence_detected = EXCLUDED.count_total_presence_detected; diff --git a/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_update_fact_space_occupancy.sql b/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_update_fact_space_occupancy.sql index cc727c0..09e7612 100644 --- a/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_update_fact_space_occupancy.sql +++ b/libs/common/src/sql/procedures/fact_space_occupancy_count/procedure_update_fact_space_occupancy.sql @@ -4,6 +4,7 @@ WITH params AS ( $2::uuid AS space_id ), +-- 1. Load logs device_logs AS ( SELECT device.uuid AS device_id, @@ -21,7 +22,7 @@ device_logs AS ( AND "device-status-log".code = 'presence_state' ), --- 1. All 'none' → presence or motion +-- 2. Transitions from 'none' → motion/presence presence_transitions AS ( SELECT space_id, @@ -29,10 +30,30 @@ presence_transitions AS ( event_time::date AS event_date, value FROM device_logs - WHERE (value = 'motion' OR value = 'presence') AND prev_value = 'none' + WHERE value IN ('motion', 'presence') AND prev_value = 'none' ), --- 2. Cluster events per space_id within 30s +-- 3. Fallback: days with motion/presence but no 'none' +fallback_daily_presence AS ( + SELECT + space_id, + event_time::date AS event_date, + MIN(event_time) AS event_time, + 'presence'::text AS value + FROM device_logs + GROUP BY space_id, event_time::date + HAVING BOOL_OR(value = 'motion') OR BOOL_OR(value = 'presence') + AND NOT BOOL_OR(value = 'none') +), + +-- 4. Combine standard and fallback detections +all_presence_events AS ( + SELECT * FROM presence_transitions + UNION ALL + SELECT * FROM fallback_daily_presence +), + +-- 5. Cluster detections (within 30s) clustered_events AS ( SELECT space_id, @@ -46,11 +67,11 @@ clustered_events AS ( WHEN event_time - LAG(event_time) OVER (PARTITION BY space_id ORDER BY event_time) > INTERVAL '30 seconds' THEN 1 ELSE 0 END AS new_cluster_flag - FROM presence_transitions + FROM all_presence_events ) marked ), --- 3. Determine dominant type (motion vs presence) per cluster +-- 6. Dominant type per cluster cluster_type AS ( SELECT space_id, @@ -66,7 +87,7 @@ cluster_type AS ( GROUP BY space_id, event_date, cluster_id ), --- 4. Count clusters by dominant type +-- 7. Count presence by type summary AS ( SELECT space_id, @@ -76,22 +97,22 @@ summary AS ( COUNT(*) AS count_total_presence_detected FROM cluster_type GROUP BY space_id, event_date +), + +-- 8. Filter by params and return final table +final_table AS ( + SELECT + summary.space_id, + summary.event_date, + count_motion_detected, + count_presence_detected, + count_total_presence_detected + FROM summary + JOIN params p ON summary.space_id = p.space_id + WHERE p.event_date IS NULL OR summary.event_date = p.event_date ) --- 5. Output -, final_table as ( -SELECT summary.space_id, - summary.event_date, - count_motion_detected, - count_presence_detected, - count_total_presence_detected -FROM summary -JOIN params P ON true -where summary.space_id = P.space_id -and (P.event_date IS NULL or summary.event_date::date = P.event_date) -ORDER BY space_id, event_date) - - +-- 9. Insert or upsert into the table INSERT INTO public."presence-sensor-daily-space-detection" ( space_uuid, event_date, diff --git a/libs/common/src/sql/queries/fact_hourly_space_presence_detected/fact_hourly_space_presence_detected.sql b/libs/common/src/sql/queries/fact_hourly_space_presence_detected/fact_hourly_space_presence_detected.sql index 7d18853..46a46d8 100644 --- a/libs/common/src/sql/queries/fact_hourly_space_presence_detected/fact_hourly_space_presence_detected.sql +++ b/libs/common/src/sql/queries/fact_hourly_space_presence_detected/fact_hourly_space_presence_detected.sql @@ -30,6 +30,19 @@ presence_detection AS ( FROM device_logs ), +fallback_daily_presence AS ( + SELECT + space_id, + event_time::date AS event_date, + 0 AS event_hour, + COUNT(*) > 0 AS has_presence, + BOOL_OR(value = 'none') AS has_none + FROM device_logs + WHERE value IN ('motion', 'presence', 'none') + GROUP BY space_id, event_time::date + HAVING COUNT(*) > 0 AND NOT BOOL_OR(value = 'none') +), + space_level_presence_events AS ( SELECT DISTINCT pd.space_id, @@ -38,6 +51,15 @@ space_level_presence_events AS ( pd.event_time FROM presence_detection pd WHERE presence_started = 1 + + UNION + + SELECT + fdp.space_id, + fdp.event_date, + fdp.event_hour, + NULL::timestamp AS event_time + FROM fallback_daily_presence fdp ), space_level_presence_summary AS ( @@ -77,3 +99,4 @@ LEFT JOIN space_level_presence_summary pds ORDER BY space_id, event_date, event_hour; +