= Advanced Reports = == 1. Resource Utilization and Demand Analysis == Quarterly report showing each resource's utilization rate, peak usage hours, busiest day, approval rate, and demand ranking. Helps administrators identify underutilized resources that could be repurposed and overdemanded resources that may need capacity expansion or alternatives. === Solution SQL === {{{ SET search_path TO project; WITH quarter_bounds AS ( SELECT DATE '2026-01-01' AS q_start, DATE '2026-03-31' AS q_end ), quarter_days AS ( SELECT d::DATE AS day, EXTRACT(ISODOW FROM d)::INT AS dow FROM quarter_bounds qb, generate_series(qb.q_start, qb.q_end, '1 day'::INTERVAL) AS d ), resource_availability AS ( SELECT r.resource_id, SUM(CASE WHEN qd.dow <= 5 OR r.available_weekends THEN EXTRACT(EPOCH FROM (r.available_to - r.available_from)) / 3600.0 ELSE 0 END) AS total_available_hours FROM resources r CROSS JOIN quarter_days qd GROUP BY r.resource_id ), reservation_stats AS ( SELECT rv.resource_id, COUNT(*) AS total_reservations, COUNT(*) FILTER (WHERE rv.status IN ('approved', 'completed')) AS approved_count, COUNT(*) FILTER (WHERE rv.status = 'rejected') AS rejected_count, COUNT(*) FILTER (WHERE rv.status = 'cancelled') AS cancelled_count, ROUND(SUM(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0) FILTER (WHERE rv.status IN ('approved', 'completed')), 1) AS reserved_hours FROM reservations rv, quarter_bounds qb WHERE rv.start_time >= qb.q_start AND rv.start_time < qb.q_end + INTERVAL '1 day' GROUP BY rv.resource_id ), popular_day AS ( SELECT DISTINCT ON (rv.resource_id) rv.resource_id, TRIM(TO_CHAR(rv.start_time, 'Day')) AS busiest_day FROM reservations rv, quarter_bounds qb WHERE rv.start_time >= qb.q_start AND rv.start_time < qb.q_end + INTERVAL '1 day' AND rv.status IN ('approved', 'completed') GROUP BY rv.resource_id, TRIM(TO_CHAR(rv.start_time, 'Day')) ORDER BY rv.resource_id, COUNT(*) DESC ), peak_hour AS ( SELECT DISTINCT ON (rv.resource_id) rv.resource_id, EXTRACT(HOUR FROM rv.start_time)::INT AS peak_hour FROM reservations rv, quarter_bounds qb WHERE rv.start_time >= qb.q_start AND rv.start_time < qb.q_end + INTERVAL '1 day' AND rv.status IN ('approved', 'completed') GROUP BY rv.resource_id, EXTRACT(HOUR FROM rv.start_time) ORDER BY rv.resource_id, COUNT(*) DESC ) SELECT r.name AS resource_name, rt.type_name AS resource_type, COALESCE(l.building || ' ' || l.room, 'Digital') AS location, COALESCE(rs.total_reservations, 0) AS total_reservations, COALESCE(rs.approved_count, 0) AS approved, COALESCE(rs.rejected_count, 0) AS rejected, COALESCE(rs.cancelled_count, 0) AS cancelled, COALESCE(ROUND(rs.approved_count::NUMERIC / NULLIF(rs.total_reservations, 0) * 100, 1), 0) AS approval_rate_pct, COALESCE(rs.reserved_hours, 0) AS reserved_hours, ROUND(ra.total_available_hours, 0) AS available_hours, COALESCE(ROUND(rs.reserved_hours / NULLIF(ra.total_available_hours, 0) * 100, 2), 0) AS utilization_pct, COALESCE(pd.busiest_day, '-') AS busiest_day, COALESCE(ph.peak_hour || ':00', '-') AS peak_hour, RANK() OVER ( ORDER BY COALESCE(rs.reserved_hours, 0) / NULLIF(ra.total_available_hours, 0) DESC NULLS LAST ) AS demand_rank FROM resources r JOIN resource_types rt ON r.type_id = rt.type_id LEFT JOIN locations l ON r.location_id = l.location_id JOIN resource_availability ra ON r.resource_id = ra.resource_id LEFT JOIN reservation_stats rs ON r.resource_id = rs.resource_id LEFT JOIN popular_day pd ON r.resource_id = pd.resource_id LEFT JOIN peak_hour ph ON r.resource_id = ph.resource_id ORDER BY utilization_pct DESC; }}} === Solution Relational Algebra === {{{ QuarterDays <- π day, dow ( σ day >= '2026-01-01' ∧ day <= '2026-03-31' (CalendarDays) ) ResourceAvailability <- γ resource_id; total_available_hours := SUM(daily_hours) ( π resource_id, (CASE dow ≤ 5 ∨ available_weekends THEN hours ELSE 0) → daily_hours ( resources × QuarterDays ) ) ReservationStats <- γ resource_id; total_reservations := COUNT(*), approved_count := COUNT(σ status ∈ {'approved','completed'}), rejected_count := COUNT(σ status = 'rejected'), cancelled_count := COUNT(σ status = 'cancelled'), reserved_hours := SUM(duration_hours WHERE status ∈ {'approved','completed'}) ( σ start_time >= '2026-01-01' ∧ start_time < '2026-04-01' ( π resource_id, status, (end_time − start_time)/3600 → duration_hours (reservations) ) ) PopularDay <- γ resource_id; busiest_day := DAY_NAME(start_time) HAVING MAX(COUNT(*)) ( σ status ∈ {'approved','completed'} ∧ start_time ∈ Quarter (reservations) ) PeakHour <- γ resource_id; peak_hour := HOUR(start_time) HAVING MAX(COUNT(*)) ( σ status ∈ {'approved','completed'} ∧ start_time ∈ Quarter (reservations) ) Result <- π resource_name, resource_type, location, total_reservations, approved_count, rejected_count, cancelled_count, approval_rate_pct, reserved_hours, available_hours, utilization_pct, busiest_day, peak_hour, demand_rank ( ( ( ( (resources ⨝ resource_types) ⟕ locations ) ⨝ ResourceAvailability ) ⟕ ReservationStats ) ⟕ PopularDay ⟕ PeakHour ) }}} == 2. Monthly Reservation Trends with Cumulative Statistics == Monthly time-series report showing reservation counts, approval rates, month-over-month growth, cumulative totals, and the top 3 most demanded resources per month. Useful for identifying seasonal patterns and forecasting future demand at the semester or academic year level. === Solution SQL === {{{ SET search_path TO project; WITH monthly_overview AS ( SELECT DATE_TRUNC('month', rv.start_time) AS month, COUNT(*) AS total_reservations, COUNT(*) FILTER (WHERE rv.status IN ('approved', 'completed')) AS approved, COUNT(*) FILTER (WHERE rv.status = 'rejected') AS rejected, COUNT(*) FILTER (WHERE rv.status = 'cancelled') AS cancelled, COUNT(*) FILTER (WHERE rv.status = 'pending') AS pending, COUNT(DISTINCT rv.user_id) AS unique_users, COUNT(DISTINCT rv.resource_id) AS unique_resources, ROUND(AVG(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0), 1) AS avg_duration_hours, ROUND(SUM(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0) FILTER (WHERE rv.status IN ('approved', 'completed')), 1) AS total_approved_hours FROM reservations rv GROUP BY DATE_TRUNC('month', rv.start_time) ), monthly_trends AS ( SELECT TO_CHAR(month, 'YYYY-MM') AS month_label, month, total_reservations, approved, rejected, cancelled, pending, unique_users, unique_resources, avg_duration_hours, total_approved_hours, SUM(total_reservations) OVER (ORDER BY month) AS cumulative_total, ROUND(approved::NUMERIC / NULLIF(total_reservations, 0) * 100, 1) AS approval_rate, CASE WHEN LAG(total_reservations) OVER (ORDER BY month) IS NULL THEN NULL ELSE ROUND( (total_reservations - LAG(total_reservations) OVER (ORDER BY month))::NUMERIC / LAG(total_reservations) OVER (ORDER BY month) * 100, 1 ) END AS mom_change_pct FROM monthly_overview ), resource_demand_ranked AS ( SELECT TO_CHAR(DATE_TRUNC('month', rv.start_time), 'YYYY-MM') AS month_label, r.name AS resource_name, COUNT(*) AS demand_count, ROW_NUMBER() OVER ( PARTITION BY DATE_TRUNC('month', rv.start_time) ORDER BY COUNT(*) DESC ) AS rank FROM reservations rv JOIN resources r ON rv.resource_id = r.resource_id GROUP BY DATE_TRUNC('month', rv.start_time), r.name ), top_resources AS ( SELECT month_label, STRING_AGG(resource_name || ' (' || demand_count || ')', ', ' ORDER BY rank) AS top_3_resources FROM resource_demand_ranked WHERE rank <= 3 GROUP BY month_label ) SELECT mt.month_label, mt.total_reservations, mt.approved, mt.rejected, mt.cancelled, mt.pending, mt.approval_rate AS approval_rate_pct, COALESCE(mt.mom_change_pct || '%', 'N/A') AS month_over_month, mt.cumulative_total, mt.unique_users, mt.unique_resources, mt.avg_duration_hours, mt.total_approved_hours, COALESCE(tr.top_3_resources, '-') AS top_demanded_resources FROM monthly_trends mt LEFT JOIN top_resources tr ON mt.month_label = tr.month_label ORDER BY mt.month_label; }}} === Solution Relational Algebra === {{{ MonthlyOverview <- γ month := TRUNC_MONTH(start_time); total_reservations := COUNT(*), approved := COUNT(σ status ∈ {'approved','completed'}), rejected := COUNT(σ status = 'rejected'), cancelled := COUNT(σ status = 'cancelled'), pending := COUNT(σ status = 'pending'), unique_users := COUNT_DISTINCT(user_id), unique_resources := COUNT_DISTINCT(resource_id), avg_duration_hours := AVG(duration), total_approved_hours := SUM(duration WHERE status ∈ {'approved','completed'}) ( π start_time, status, user_id, resource_id, (end_time − start_time)/3600 → duration (reservations) ) MonthlyTrends <- π month_label, total_reservations, approved, rejected, cancelled, pending, approval_rate, cumulative_total, mom_change_pct ( ω ORDER BY month; cumulative_total := SUM(total_reservations), mom_change_pct := (total_reservations − LAG(total_reservations)) / LAG(total_reservations) * 100 ( MonthlyOverview ) ) ResourceDemandRanked <- ω PARTITION BY month ORDER BY demand_count DESC; rank := ROW_NUMBER() ( γ month := TRUNC_MONTH(start_time), resource_name := r.name; demand_count := COUNT(*) ( reservations rv ⨝ (rv.resource_id = r.resource_id) resources r ) ) TopResources <- γ month_label; top_3 := CONCAT(resource_name, demand_count) ( σ rank ≤ 3 (ResourceDemandRanked) ) Result <- π month_label, total_reservations, approved, rejected, cancelled, pending, approval_rate, mom_change_pct, cumulative_total, top_3 ( MonthlyTrends ⟕ (month_label) TopResources ) }}} == 3. User Activity and Behavior Analysis == Per-user report showing total reservations, approval and cancellation rates, most-used resource, total hours consumed, and activity ranking. Helps identify the most active users, detect problematic patterns (high cancellation rates), and understand how different user types (students vs. teaching staff) use the system. === Solution SQL === {{{ SET search_path TO project; WITH user_stats AS ( SELECT rv.user_id, COUNT(*) AS total_reservations, COUNT(*) FILTER (WHERE rv.status IN ('approved', 'completed')) AS approved, COUNT(*) FILTER (WHERE rv.status = 'rejected') AS rejected, COUNT(*) FILTER (WHERE rv.status = 'cancelled') AS cancelled, ROUND(COUNT(*) FILTER (WHERE rv.status = 'cancelled')::NUMERIC / NULLIF(COUNT(*), 0) * 100, 1) AS cancellation_rate, ROUND(AVG(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0), 1) AS avg_duration_hours, ROUND(SUM(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0) FILTER (WHERE rv.status IN ('approved', 'completed')), 1) AS total_hours_used, COUNT(DISTINCT rv.resource_id) AS distinct_resources_used FROM reservations rv GROUP BY rv.user_id ), favorite_resource AS ( SELECT DISTINCT ON (rv.user_id) rv.user_id, r.name AS favorite_resource, COUNT(*) AS use_count FROM reservations rv JOIN resources r ON rv.resource_id = r.resource_id WHERE rv.status IN ('approved', 'completed') GROUP BY rv.user_id, r.name ORDER BY rv.user_id, COUNT(*) DESC ) SELECT u.first_name || ' ' || u.last_name AS user_name, ut.type_name AS user_type, COALESCE(us.total_reservations, 0) AS total_reservations, COALESCE(us.approved, 0) AS approved, COALESCE(us.rejected, 0) AS rejected, COALESCE(us.cancelled, 0) AS cancelled, COALESCE(us.cancellation_rate, 0) AS cancellation_rate_pct, COALESCE(us.avg_duration_hours, 0) AS avg_duration_hours, COALESCE(us.total_hours_used, 0) AS total_hours_used, COALESCE(us.distinct_resources_used, 0) AS distinct_resources, COALESCE(fr.favorite_resource, '-') AS most_used_resource, COALESCE(fr.use_count, 0) AS most_used_count, RANK() OVER (ORDER BY COALESCE(us.total_reservations, 0) DESC) AS activity_rank FROM users u JOIN user_types ut ON u.type_id = ut.type_id LEFT JOIN user_stats us ON u.user_id = us.user_id LEFT JOIN favorite_resource fr ON u.user_id = fr.user_id ORDER BY activity_rank; }}} === Solution Relational Algebra === {{{ UserStats <- γ user_id; total_reservations := COUNT(*), approved := COUNT(σ status ∈ {'approved','completed'}), rejected := COUNT(σ status = 'rejected'), cancelled := COUNT(σ status = 'cancelled'), cancellation_rate := COUNT(σ status='cancelled') / COUNT(*) * 100, avg_duration_hours := AVG(duration), total_hours_used := SUM(duration WHERE status ∈ {'approved','completed'}), distinct_resources := COUNT_DISTINCT(resource_id) ( π user_id, status, resource_id, (end_time − start_time)/3600 → duration (reservations) ) FavoriteResource <- γ user_id; favorite_resource := r.name HAVING MAX(use_count) ( γ user_id, r.name; use_count := COUNT(*) ( σ status ∈ {'approved','completed'} ( reservations rv ⨝ (rv.resource_id = r.resource_id) resources r ) ) ) Result <- π user_name, user_type, total_reservations, approved, rejected, cancelled, cancellation_rate, avg_duration_hours, total_hours_used, distinct_resources, favorite_resource, activity_rank ( ( (users u ⨝ (u.type_id = ut.type_id) user_types ut) ⟕ (u.user_id = us.user_id) UserStats us ) ⟕ (u.user_id = fr.user_id) FavoriteResource fr ) }}} == 4. Administrator Approval Workload and Bottleneck Analysis == Shows each administrator's approval workload, approval/rejection rates, workload share percentage, and system-wide pending reservation wait times. Helps management identify if approval responsibilities are evenly distributed and whether pending reservations are being handled in a timely manner. === Solution SQL === {{{ SET search_path TO project; WITH admin_stats AS ( SELECT rv.approved_by AS admin_id, COUNT(*) AS total_reviewed, COUNT(*) FILTER (WHERE rv.status IN ('approved', 'completed')) AS approved_count, COUNT(*) FILTER (WHERE rv.status = 'rejected') AS rejected_count, ROUND(COUNT(*) FILTER (WHERE rv.status IN ('approved', 'completed'))::NUMERIC / NULLIF(COUNT(*), 0) * 100, 1) AS approval_rate, COUNT(DISTINCT rv.resource_id) AS distinct_resources_handled, COUNT(DISTINCT rv.user_id) AS distinct_users_served FROM reservations rv WHERE rv.approved_by IS NOT NULL GROUP BY rv.approved_by ), pending_stats AS ( SELECT COUNT(*) AS total_pending, ROUND(AVG(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - rv.created_at)) / 86400.0), 1) AS avg_wait_days, MAX(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - rv.created_at)) / 86400.0)::INT AS max_wait_days FROM reservations rv WHERE rv.status = 'pending' ), workload_share AS ( SELECT admin_id, total_reviewed, ROUND(total_reviewed::NUMERIC / SUM(total_reviewed) OVER () * 100, 1) AS workload_share_pct FROM admin_stats ) SELECT u.first_name || ' ' || u.last_name AS administrator, COALESCE(ast.total_reviewed, 0) AS total_reviewed, COALESCE(ast.approved_count, 0) AS approved, COALESCE(ast.rejected_count, 0) AS rejected, COALESCE(ast.approval_rate, 0) AS approval_rate_pct, COALESCE(ws.workload_share_pct, 0) AS workload_share_pct, COALESCE(ast.distinct_resources_handled, 0) AS resources_handled, COALESCE(ast.distinct_users_served, 0) AS users_served, ps.total_pending AS system_pending_count, ps.avg_wait_days AS pending_avg_wait_days, ps.max_wait_days AS pending_max_wait_days FROM users u JOIN user_types ut ON u.type_id = ut.type_id LEFT JOIN admin_stats ast ON u.user_id = ast.admin_id LEFT JOIN workload_share ws ON u.user_id = ws.admin_id CROSS JOIN pending_stats ps WHERE ut.type_name = 'Administrator' ORDER BY total_reviewed DESC; }}} === Solution Relational Algebra === {{{ AdminStats <- γ admin_id := approved_by; total_reviewed := COUNT(*), approved_count := COUNT(σ status ∈ {'approved','completed'}), rejected_count := COUNT(σ status = 'rejected'), approval_rate := COUNT(σ status ∈ {'approved','completed'}) / COUNT(*) * 100, distinct_resources := COUNT_DISTINCT(resource_id), distinct_users := COUNT_DISTINCT(user_id) ( σ approved_by IS NOT NULL (reservations) ) PendingStats <- γ total_pending := COUNT(*), avg_wait_days := AVG((NOW() − created_at) / 86400), max_wait_days := MAX((NOW() − created_at) / 86400) ( σ status = 'pending' (reservations) ) WorkloadShare <- π admin_id, total_reviewed, total_reviewed / SUM(total_reviewed) * 100 → workload_share_pct ( AdminStats ) Result <- π administrator, total_reviewed, approved, rejected, approval_rate, workload_share_pct, resources_handled, users_served, total_pending, avg_wait_days, max_wait_days ( ( ( σ type_name = 'Administrator' (users u ⨝ user_types ut) ⟕ (u.user_id = ast.admin_id) AdminStats ast ) ⟕ (u.user_id = ws.admin_id) WorkloadShare ws ) × PendingStats ps ) }}} == 5. Resource Recommendation Based on Similar Users == Collaborative filtering report: given a target user, finds other users with similar reservation patterns (shared resources), then recommends resources those similar users have used that the target user has not tried yet. Useful for suggesting relevant resources to users and improving resource discovery. === Solution SQL === {{{ SET search_path TO project; WITH target_user AS ( SELECT 5 AS user_id -- parameterize per user ), target_resources AS ( SELECT DISTINCT rv.resource_id FROM reservations rv, target_user tu WHERE rv.user_id = tu.user_id AND rv.status IN ('approved', 'completed') ), similar_users AS ( SELECT rv.user_id AS similar_user_id, COUNT(DISTINCT rv.resource_id) AS shared_resources, RANK() OVER (ORDER BY COUNT(DISTINCT rv.resource_id) DESC) AS similarity_rank FROM reservations rv JOIN target_resources tr ON rv.resource_id = tr.resource_id CROSS JOIN target_user tu WHERE rv.user_id != tu.user_id AND rv.status IN ('approved', 'completed') GROUP BY rv.user_id ), recommended_resources AS ( SELECT rv.resource_id, r.name AS resource_name, rt.type_name AS resource_type, COALESCE(l.building || ' ' || l.room, 'Digital') AS location, COUNT(DISTINCT rv.user_id) AS recommended_by_count, ROUND(AVG(EXTRACT(EPOCH FROM (rv.end_time - rv.start_time)) / 3600.0), 1) AS avg_usage_hours FROM reservations rv JOIN similar_users su ON rv.user_id = su.similar_user_id JOIN resources r ON rv.resource_id = r.resource_id JOIN resource_types rt ON r.type_id = rt.type_id LEFT JOIN locations l ON r.location_id = l.location_id WHERE rv.status IN ('approved', 'completed') AND su.similarity_rank <= 5 AND rv.resource_id NOT IN (SELECT resource_id FROM target_resources) GROUP BY rv.resource_id, r.name, rt.type_name, l.building, l.room ) SELECT u.first_name || ' ' || u.last_name AS target_user, rr.resource_name, rr.resource_type, rr.location, rr.recommended_by_count AS similar_users_use_it, rr.avg_usage_hours, RANK() OVER (ORDER BY rr.recommended_by_count DESC, rr.avg_usage_hours DESC) AS recommendation_rank FROM recommended_resources rr CROSS JOIN target_user tu JOIN users u ON tu.user_id = u.user_id ORDER BY recommendation_rank; }}} === Solution Relational Algebra === {{{ TargetUser <- {(user_id: 5)} TargetResources <- π resource_id ( σ status ∈ {'approved','completed'} ( reservations ⨝ (user_id) TargetUser ) ) SimilarUsers <- ω ORDER BY shared_resources DESC; similarity_rank := RANK() ( γ similar_user_id := rv.user_id; shared_resources := COUNT_DISTINCT(rv.resource_id) ( σ rv.user_id ≠ tu.user_id ∧ rv.status ∈ {'approved','completed'} ( (reservations rv ⨝ (rv.resource_id = tr.resource_id) TargetResources tr) × TargetUser tu ) ) ) RecommendedResources <- γ resource_id, resource_name, resource_type, location; recommended_by := COUNT_DISTINCT(user_id), avg_usage_hours := AVG(duration) ( π rv.resource_id, r.name → resource_name, rt.type_name → resource_type, l.building || l.room → location, rv.user_id, (rv.end_time − rv.start_time)/3600 → duration ( σ rv.status ∈ {'approved','completed'} ∧ su.similarity_rank ≤ 5 ( ( ( reservations rv ⨝ (rv.user_id = su.similar_user_id) σ similarity_rank ≤ 5 (SimilarUsers su) ) ⨝ (rv.resource_id = r.resource_id) resources r ⨝ (r.type_id = rt.type_id) resource_types rt ⟕ (r.location_id = l.location_id) locations l ) − (π * (reservations rv ⨝ TargetResources)) ) ) ) Result <- π target_user, resource_name, resource_type, location, recommended_by, avg_usage_hours, recommendation_rank ( ω ORDER BY recommended_by DESC, avg_usage_hours DESC; recommendation_rank := RANK() ( RecommendedResources × (TargetUser ⨝ users) ) ) }}}