Compare commits

...

3 Commits

Author SHA1 Message Date
6282f402db adjusted the codes completely 2025-06-11 13:33:57 +03:00
76f3d6369a test 2025-06-09 15:27:47 +03:00
14b7f4ab6b hot fix of occupancy 2025-06-09 15:24:18 +03:00
4 changed files with 384 additions and 170 deletions

View File

@ -13,81 +13,142 @@ WITH presence_logs AS (
AND p.cat_name = 'hps'
),
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
presence_intervals AS (
raw_absence_intervals AS (
SELECT
space_id,
device_id,
prev_time AS start_time,
event_time AS end_time
FROM presence_logs
WHERE value = 'none'
AND prev_value = 'presence'
AND prev_time IS NOT NULL
WHERE prev_value = 'none' AND prev_time IS NOT NULL
),
-- Split intervals across days
split_intervals AS (
absence_intervals AS (
SELECT
r.space_id,
r.device_id,
gs.day,
GREATEST(r.start_time, gs.day) AS start_time,
LEAST(r.end_time, gs.day + INTERVAL '1 day') AS end_time
FROM raw_absence_intervals r
CROSS JOIN LATERAL (
SELECT generate_series(
date_trunc('day', r.start_time),
date_trunc('day', r.end_time),
INTERVAL '1 day'
) AS day
) gs
WHERE GREATEST(r.start_time, gs.day) < LEAST(r.end_time, gs.day + INTERVAL '1 day')
),
device_counts AS (
SELECT space_id, day, COUNT(DISTINCT device_id) AS device_count
FROM absence_intervals
GROUP BY 1, 2
),
timeline AS (
SELECT
a.space_id,
a.day,
a.device_id,
a.start_time AS ts,
1 AS is_start
FROM absence_intervals a
UNION ALL
SELECT
a.space_id,
a.day,
a.device_id,
a.end_time AS ts,
0 AS is_start
FROM absence_intervals a
),
ordered_events AS (
SELECT
space_id,
generate_series(
date_trunc('day', start_time),
date_trunc('day', end_time),
interval '1 day'
)::date AS event_date,
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
FROM presence_intervals
day,
ts,
is_start,
device_id,
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
OVER (PARTITION BY space_id, day, device_id ORDER BY ts) AS device_active
FROM timeline
),
-- Mark and group overlapping intervals per space per day
ordered_intervals AS (
SELECT
space_id,
event_date,
interval_start,
interval_end,
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
FROM split_intervals
),
grouped_intervals AS (
SELECT *,
SUM(CASE
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
ELSE 0
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
FROM ordered_intervals
),
-- Merge overlapping intervals per group
merged_intervals AS (
SELECT
space_id,
event_date,
MIN(interval_start) AS merged_start,
MAX(interval_end) AS merged_end
FROM grouped_intervals
GROUP BY space_id, event_date, grp
),
-- Sum durations of merged intervals
summed_intervals AS (
device_state_changes AS (
SELECT
space_id,
event_date,
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
FROM merged_intervals
GROUP BY space_id, event_date
),
day,
ts,
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
OVER (PARTITION BY space_id, day ORDER BY ts) AS net_active_devices
FROM (
SELECT DISTINCT space_id, day, ts, is_start
FROM timeline
) t
),
final_data AS (
absence_windows AS (
SELECT
dc.space_id,
dc.day,
ts AS start_time,
LEAD(ts) OVER (PARTITION BY dc.space_id, dc.day ORDER BY ts) AS end_time
FROM device_state_changes dsc
JOIN device_counts dc ON dc.space_id = dsc.space_id AND dc.day = dsc.day
WHERE net_active_devices = 0
),
empty_periods AS (
SELECT
space_id,
day,
EXTRACT(EPOCH FROM (end_time - start_time)) AS unoccupied_seconds
FROM absence_windows
WHERE end_time IS NOT NULL
),
unoccupied_summary AS (
SELECT
space_id,
day,
SUM(unoccupied_seconds) AS total_unoccupied_seconds
FROM empty_periods
GROUP BY space_id, day
),
-- Include device count even for days with 0 unoccupied time
all_days_with_devices AS (
SELECT
space_id,
DATE(event_time) AS day,
COUNT(DISTINCT device_id) AS device_count
FROM presence_logs
GROUP BY 1, 2
),
final_occupancy AS (
SELECT
d.space_id,
d.day,
d.device_count,
COALESCE(u.total_unoccupied_seconds, 0) AS unoccupied_seconds,
86400 - COALESCE(u.total_unoccupied_seconds, 0) AS occupied_seconds
FROM all_days_with_devices d
LEFT JOIN unoccupied_summary u
ON d.space_id = u.space_id AND d.day = u.day
)
, final_data as (
SELECT
space_id,
event_date,
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
FROM summed_intervals
ORDER BY space_id, event_date)
day,
device_count,
occupied_seconds,
ROUND(occupied_seconds / 86400.0 * 100, 2) AS occupancy_percentage
FROM final_occupancy
ORDER BY space_id, day)
INSERT INTO public."space-daily-occupancy-duration" (

View File

@ -10,7 +10,8 @@ presence_logs AS (
l.device_id,
l.event_time,
l.value,
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time,
LAG(l.value) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_value
FROM device d
JOIN "device-status-log" l ON d.uuid = l.device_id
JOIN product p ON p.uuid = d.product_device_uuid
@ -18,77 +19,147 @@ presence_logs AS (
AND p.cat_name = 'hps'
),
presence_intervals AS (
raw_absence_intervals AS (
SELECT
space_id,
device_id,
prev_time AS start_time,
event_time AS end_time
FROM presence_logs
WHERE value = 'none' AND prev_time IS NOT NULL
WHERE prev_value = 'none' AND prev_time IS NOT NULL
),
split_intervals AS (
absence_intervals AS (
SELECT
r.space_id,
r.device_id,
gs.day,
GREATEST(r.start_time, gs.day) AS start_time,
LEAST(r.end_time, gs.day + INTERVAL '1 day') AS end_time
FROM raw_absence_intervals r
CROSS JOIN LATERAL (
SELECT generate_series(
date_trunc('day', r.start_time),
date_trunc('day', r.end_time),
INTERVAL '1 day'
) AS day
) gs
WHERE GREATEST(r.start_time, gs.day) < LEAST(r.end_time, gs.day + INTERVAL '1 day')
),
device_counts AS (
SELECT space_id, day, COUNT(DISTINCT device_id) AS device_count
FROM absence_intervals
GROUP BY 1, 2
),
timeline AS (
SELECT
a.space_id,
a.day,
a.device_id,
a.start_time AS ts,
1 AS is_start
FROM absence_intervals a
UNION ALL
SELECT
a.space_id,
a.day,
a.device_id,
a.end_time AS ts,
0 AS is_start
FROM absence_intervals a
),
ordered_events AS (
SELECT
space_id,
generate_series(
date_trunc('day', start_time),
date_trunc('day', end_time),
interval '1 day'
)::date AS event_date,
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
LEAST(end_time, date_trunc('day', end_time) + INTERVAL '1 day') AS interval_end
FROM presence_intervals
day,
ts,
is_start,
device_id,
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
OVER (PARTITION BY space_id, day, device_id ORDER BY ts) AS device_active
FROM timeline
),
ordered_intervals AS (
SELECT
space_id,
event_date,
interval_start,
interval_end,
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
FROM split_intervals
),
grouped_intervals AS (
SELECT *,
SUM(CASE
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
ELSE 0
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
FROM ordered_intervals
),
merged_intervals AS (
SELECT
space_id,
event_date,
MIN(interval_start) AS merged_start,
MAX(interval_end) AS merged_end
FROM grouped_intervals
GROUP BY space_id, event_date, grp
),
summed_intervals AS (
device_state_changes AS (
SELECT
space_id,
event_date,
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
FROM merged_intervals
GROUP BY space_id, event_date
day,
ts,
SUM(CASE WHEN is_start = 1 THEN 1 ELSE -1 END)
OVER (PARTITION BY space_id, day ORDER BY ts) AS net_active_devices
FROM (
SELECT DISTINCT space_id, day, ts, is_start
FROM timeline
) t
),
final_data AS (
absence_windows AS (
SELECT
s.space_id,
s.event_date,
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
FROM summed_intervals s
JOIN params p
dc.space_id,
dc.day,
ts AS start_time,
LEAD(ts) OVER (PARTITION BY dc.space_id, dc.day ORDER BY ts) AS end_time
FROM device_state_changes dsc
JOIN device_counts dc ON dc.space_id = dsc.space_id AND dc.day = dsc.day
WHERE net_active_devices = 0
),
empty_periods AS (
SELECT
space_id,
day,
EXTRACT(EPOCH FROM (end_time - start_time)) AS unoccupied_seconds
FROM absence_windows
WHERE end_time IS NOT NULL
),
unoccupied_summary AS (
SELECT
space_id,
day,
SUM(unoccupied_seconds) AS total_unoccupied_seconds
FROM empty_periods
GROUP BY space_id, day
),
-- Include device count even for days with 0 unoccupied time
all_days_with_devices AS (
SELECT
space_id,
DATE(event_time) AS day,
COUNT(DISTINCT device_id) AS device_count
FROM presence_logs
GROUP BY 1, 2
),
final_occupancy AS (
SELECT
d.space_id,
d.day,
d.device_count,
COALESCE(u.total_unoccupied_seconds, 0) AS unoccupied_seconds,
86400 - COALESCE(u.total_unoccupied_seconds, 0) AS occupied_seconds
FROM all_days_with_devices d
LEFT JOIN unoccupied_summary u
ON d.space_id = u.space_id AND d.day = u.day
),
final_data as(
SELECT
space_id,
day,
device_count,
occupied_seconds,
ROUND(occupied_seconds / 86400.0 * 100, 2) AS occupancy_percentage
FROM final_occupancy s
JOIN params p
ON p.space_id = s.space_id
AND p.event_date = s.event_date
)
ORDER BY space_id, DAY)
INSERT INTO public."space-daily-occupancy-duration" (
space_uuid,

View File

@ -12,4 +12,8 @@ AND (
P.event_year IS NULL
OR TO_CHAR(psdsd.event_date, 'YYYY') = TO_CHAR(P.event_year, 'YYYY')
)
ORDER BY space_uuid, event_date
ORDER BY space_uuid, event_date

View File

@ -13,78 +13,156 @@ WITH presence_logs AS (
AND p.cat_name = 'hps'
),
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
presence_intervals AS (
-- Intervals when device was in 'absence' (between prev_time and event_time when value='none')
raw_absence_intervals AS (
SELECT
space_id,
device_id,
prev_time AS start_time,
event_time AS end_time
FROM presence_logs
WHERE value = 'none'
AND prev_value = 'presence'
WHERE value <> 'none'
AND prev_value = 'none'
AND prev_time IS NOT NULL
),
-- Split intervals across days
split_intervals AS (
-- Split intervals that span multiple days into day-specific chunks
absence_intervals AS (
SELECT
r.space_id,
r.device_id,
GREATEST(r.start_time, gs.day::timestamp) AS start_time,
LEAST(r.end_time, (gs.day + INTERVAL '1 day' - INTERVAL '1 second')::timestamp) AS end_time
FROM raw_absence_intervals r
CROSS JOIN LATERAL (
SELECT generate_series(
date_trunc('day', r.start_time),
date_trunc('day', r.end_time),
INTERVAL '1 day'
) AS day
) gs
WHERE GREATEST(r.start_time, gs.day::timestamp) < LEAST(r.end_time, (gs.day + INTERVAL '1 day')::timestamp)
),
-- FIXED: Count devices based on presence_logs OR absence_intervals
devices_per_day AS (
SELECT
space_id,
generate_series(
date_trunc('day', start_time),
date_trunc('day', end_time),
interval '1 day'
)::date AS event_date,
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
FROM presence_intervals
day,
COUNT(DISTINCT device_id) AS device_count
FROM (
-- Devices that logged events on that day
SELECT space_id, DATE(event_time) AS day, device_id
FROM presence_logs
UNION
-- Devices that had absence intervals on that day
SELECT space_id, DATE(start_time) AS day, device_id
FROM absence_intervals
) combined
GROUP BY space_id, day
),
-- Mark and group overlapping intervals per space per day
ordered_intervals AS (
SELECT
space_id,
event_date,
interval_start,
interval_end,
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
FROM split_intervals
),
grouped_intervals AS (
SELECT *,
SUM(CASE
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
ELSE 0
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
FROM ordered_intervals
),
-- Merge overlapping intervals per group
merged_intervals AS (
SELECT
space_id,
event_date,
MIN(interval_start) AS merged_start,
MAX(interval_end) AS merged_end
FROM grouped_intervals
GROUP BY space_id, event_date, grp
),
-- Sum durations of merged intervals
summed_intervals AS (
-- For multi-device spaces, find all time periods when ALL devices were absent
multi_device_unoccupied AS (
WITH device_absence_per_day AS (
SELECT
a.space_id,
DATE(a.start_time) AS day,
a.device_id,
a.start_time,
a.end_time,
d.device_count
FROM absence_intervals a
JOIN devices_per_day d ON a.space_id = d.space_id AND DATE(a.start_time) = d.day
WHERE d.device_count > 1
),
-- Generate time slots for each day with multiple devices
time_ranges AS (
SELECT
space_id,
day,
day::timestamp AS range_start,
(day + INTERVAL '1 day')::timestamp AS range_end,
device_count
FROM devices_per_day
WHERE device_count > 1
),
-- Find all time periods when all devices were absent
all_devices_absent AS (
SELECT
t.space_id,
t.day,
t.range_start,
t.range_end,
t.device_count,
-- Find the latest start time of all devices' absence intervals
MAX(a.start_time) OVER (PARTITION BY t.space_id, t.day) AS max_start_time,
-- Find the earliest end time of all devices' absence intervals
MIN(a.end_time) OVER (PARTITION BY t.space_id, t.day) AS min_end_time
FROM time_ranges t
LEFT JOIN device_absence_per_day a ON
t.space_id = a.space_id AND
t.day = a.day
GROUP BY t.space_id, t.day, t.range_start, t.range_end, t.device_count, a.start_time, a.end_time
)
-- Calculate total unoccupied seconds when all devices were absent
SELECT
space_id,
event_date,
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
FROM merged_intervals
GROUP BY space_id, event_date
day,
CASE
WHEN max_start_time IS NULL OR min_end_time IS NULL THEN 0
WHEN max_start_time >= min_end_time THEN 0
ELSE EXTRACT(EPOCH FROM (LEAST(min_end_time, range_end) - GREATEST(max_start_time, range_start)))
END AS unoccupied_seconds
FROM all_devices_absent
GROUP BY space_id, day, range_start, range_end, device_count, max_start_time, min_end_time
HAVING COUNT(*) = device_count -- Only include periods when all devices were absent
)
,
-- Calculate unoccupied time for spaces with single device reporting
single_device_unoccupied AS (
SELECT
a.space_id,
DATE(a.start_time) AS day,
SUM(EXTRACT(EPOCH FROM (a.end_time - a.start_time))) AS unoccupied_seconds
FROM absence_intervals a
JOIN devices_per_day d ON a.space_id = d.space_id AND DATE(a.start_time) = d.day
WHERE d.device_count = 1
GROUP BY a.space_id, DATE(a.start_time)
)
,
-- Combine results from both single and multi-device cases
combined_unoccupied AS (
SELECT space_id, day, unoccupied_seconds FROM single_device_unoccupied
UNION ALL
SELECT space_id, day, unoccupied_seconds FROM multi_device_unoccupied
),
-- Calculate total occupied time per space per day
daily_occupancy AS (
SELECT
space_id,
day,
-- Total seconds in day (86400) minus unoccupied seconds
86400 - COALESCE(SUM(unoccupied_seconds), 0) AS occupied_seconds
FROM combined_unoccupied
GROUP BY space_id, day
)
-- Final output with capped seconds and percentage
-- Final result
SELECT
space_id,
event_date,
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
FROM summed_intervals
ORDER BY space_id, event_date;
day,
occupied_seconds,
-- Also include percentage for convenience
ROUND((occupied_seconds / 86400.0) * 100, 2) AS occupancy_percentage
FROM daily_occupancy
ORDER BY space_id, day;