| 1 | from db import get_connection, pick_from_list, print_table
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 | def view_analytics():
|
|---|
| 5 | """UC0007: View Resource Usage Analytics."""
|
|---|
| 6 | while True:
|
|---|
| 7 | print("\n=== Resource Usage Analytics ===\n")
|
|---|
| 8 | options = [
|
|---|
| 9 | "Summary Overview",
|
|---|
| 10 | "Reservations per Resource",
|
|---|
| 11 | "Most Active Users",
|
|---|
| 12 | "Reservation Status Distribution",
|
|---|
| 13 | "Busiest Days of the Week",
|
|---|
| 14 | "Resource Type Utilization",
|
|---|
| 15 | "Monthly Reservation Trends",
|
|---|
| 16 | "Show All Reports",
|
|---|
| 17 | ]
|
|---|
| 18 | idx = pick_from_list(" Choose a report: ", options)
|
|---|
| 19 | if idx is None:
|
|---|
| 20 | return
|
|---|
| 21 |
|
|---|
| 22 | reports = [
|
|---|
| 23 | _summary_overview,
|
|---|
| 24 | _reservations_per_resource,
|
|---|
| 25 | _most_active_users,
|
|---|
| 26 | _status_distribution,
|
|---|
| 27 | _busiest_days,
|
|---|
| 28 | _resource_type_utilization,
|
|---|
| 29 | _monthly_trends,
|
|---|
| 30 | ]
|
|---|
| 31 |
|
|---|
| 32 | if idx == 7: # All reports
|
|---|
| 33 | for report in reports:
|
|---|
| 34 | report()
|
|---|
| 35 | else:
|
|---|
| 36 | reports[idx]()
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 | def _summary_overview():
|
|---|
| 40 | with get_connection() as conn:
|
|---|
| 41 | with conn.cursor() as cur:
|
|---|
| 42 | cur.execute("""
|
|---|
| 43 | SELECT
|
|---|
| 44 | COUNT(*) AS total_reservations,
|
|---|
| 45 | COUNT(*) FILTER (WHERE status = 'approved') AS approved,
|
|---|
| 46 | COUNT(*) FILTER (WHERE status = 'pending') AS pending,
|
|---|
| 47 | COUNT(*) FILTER (WHERE status = 'rejected') AS rejected,
|
|---|
| 48 | COUNT(*) FILTER (WHERE status = 'completed') AS completed,
|
|---|
| 49 | COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled,
|
|---|
| 50 | COUNT(DISTINCT user_id) AS unique_users,
|
|---|
| 51 | COUNT(DISTINCT resource_id) AS unique_resources
|
|---|
| 52 | FROM reservations
|
|---|
| 53 | """)
|
|---|
| 54 | row = cur.fetchone()
|
|---|
| 55 | print("\n --- Summary Overview ---")
|
|---|
| 56 | labels = ["Total", "Approved", "Pending", "Rejected", "Completed", "Cancelled", "Unique Users", "Unique Resources"]
|
|---|
| 57 | for label, val in zip(labels, row):
|
|---|
| 58 | print(f" {label + ':':<20} {val}")
|
|---|
| 59 |
|
|---|
| 60 |
|
|---|
| 61 | def _reservations_per_resource():
|
|---|
| 62 | with get_connection() as conn:
|
|---|
| 63 | with conn.cursor() as cur:
|
|---|
| 64 | cur.execute("""
|
|---|
| 65 | SELECT r.name, rt.type_name,
|
|---|
| 66 | COUNT(res.reservation_id) AS total,
|
|---|
| 67 | COUNT(res.reservation_id) FILTER (WHERE res.status = 'approved') AS approved,
|
|---|
| 68 | COUNT(res.reservation_id) FILTER (WHERE res.status = 'completed') AS completed,
|
|---|
| 69 | COUNT(res.reservation_id) FILTER (WHERE res.status = 'rejected') AS rejected
|
|---|
| 70 | FROM resources r
|
|---|
| 71 | JOIN resource_types rt ON r.type_id = rt.type_id
|
|---|
| 72 | LEFT JOIN reservations res ON r.resource_id = res.resource_id
|
|---|
| 73 | GROUP BY r.resource_id, r.name, rt.type_name
|
|---|
| 74 | HAVING COUNT(res.reservation_id) > 0
|
|---|
| 75 | ORDER BY total DESC
|
|---|
| 76 | """)
|
|---|
| 77 | rows = cur.fetchall()
|
|---|
| 78 | print("\n --- Reservations per Resource ---")
|
|---|
| 79 | print_table(["Resource", "Type", "Total", "Approved", "Completed", "Rejected"], rows)
|
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 | def _most_active_users():
|
|---|
| 83 | with get_connection() as conn:
|
|---|
| 84 | with conn.cursor() as cur:
|
|---|
| 85 | cur.execute("""
|
|---|
| 86 | SELECT u.first_name || ' ' || u.last_name, ut.type_name,
|
|---|
| 87 | COUNT(res.reservation_id) AS total,
|
|---|
| 88 | COUNT(res.reservation_id) FILTER (WHERE res.status IN ('approved', 'completed')) AS successful,
|
|---|
| 89 | COUNT(res.reservation_id) FILTER (WHERE res.status = 'rejected') AS rejected
|
|---|
| 90 | FROM users u
|
|---|
| 91 | JOIN user_types ut ON u.type_id = ut.type_id
|
|---|
| 92 | LEFT JOIN reservations res ON u.user_id = res.user_id
|
|---|
| 93 | GROUP BY u.user_id, u.first_name, u.last_name, ut.type_name
|
|---|
| 94 | HAVING COUNT(res.reservation_id) > 0
|
|---|
| 95 | ORDER BY total DESC
|
|---|
| 96 | """)
|
|---|
| 97 | rows = cur.fetchall()
|
|---|
| 98 | print("\n --- Most Active Users ---")
|
|---|
| 99 | print_table(["User", "Role", "Total", "Successful", "Rejected"], rows)
|
|---|
| 100 |
|
|---|
| 101 |
|
|---|
| 102 | def _status_distribution():
|
|---|
| 103 | with get_connection() as conn:
|
|---|
| 104 | with conn.cursor() as cur:
|
|---|
| 105 | cur.execute("""
|
|---|
| 106 | SELECT status, COUNT(*) AS count,
|
|---|
| 107 | ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) AS percentage
|
|---|
| 108 | FROM reservations
|
|---|
| 109 | GROUP BY status
|
|---|
| 110 | ORDER BY count DESC
|
|---|
| 111 | """)
|
|---|
| 112 | rows = cur.fetchall()
|
|---|
| 113 | print("\n --- Reservation Status Distribution ---")
|
|---|
| 114 | print_table(["Status", "Count", "Percentage (%)"], rows)
|
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 | def _busiest_days():
|
|---|
| 118 | with get_connection() as conn:
|
|---|
| 119 | with conn.cursor() as cur:
|
|---|
| 120 | cur.execute("""
|
|---|
| 121 | SELECT TRIM(TO_CHAR(res.start_time, 'Day')),
|
|---|
| 122 | COUNT(*) AS reservation_count
|
|---|
| 123 | FROM reservations res
|
|---|
| 124 | JOIN resources r ON res.resource_id = r.resource_id
|
|---|
| 125 | JOIN resource_types rt ON r.type_id = rt.type_id
|
|---|
| 126 | WHERE rt.is_physical = TRUE
|
|---|
| 127 | AND res.status IN ('approved', 'completed')
|
|---|
| 128 | GROUP BY TO_CHAR(res.start_time, 'Day'), EXTRACT(ISODOW FROM res.start_time)
|
|---|
| 129 | ORDER BY EXTRACT(ISODOW FROM res.start_time)
|
|---|
| 130 | """)
|
|---|
| 131 | rows = cur.fetchall()
|
|---|
| 132 | print("\n --- Busiest Days of the Week (Physical Resources) ---")
|
|---|
| 133 | print_table(["Day", "Reservations"], rows)
|
|---|
| 134 |
|
|---|
| 135 |
|
|---|
| 136 | def _resource_type_utilization():
|
|---|
| 137 | with get_connection() as conn:
|
|---|
| 138 | with conn.cursor() as cur:
|
|---|
| 139 | cur.execute("""
|
|---|
| 140 | SELECT rt.type_name,
|
|---|
| 141 | CASE WHEN rt.is_physical THEN 'Physical' ELSE 'Digital' END,
|
|---|
| 142 | COUNT(DISTINCT r.resource_id) AS total_resources,
|
|---|
| 143 | COUNT(res.reservation_id) AS total_reservations,
|
|---|
| 144 | ROUND(COUNT(res.reservation_id)::NUMERIC /
|
|---|
| 145 | NULLIF(COUNT(DISTINCT r.resource_id), 0), 1)
|
|---|
| 146 | FROM resource_types rt
|
|---|
| 147 | LEFT JOIN resources r ON rt.type_id = r.type_id
|
|---|
| 148 | LEFT JOIN reservations res ON r.resource_id = res.resource_id
|
|---|
| 149 | GROUP BY rt.type_id, rt.type_name, rt.is_physical
|
|---|
| 150 | ORDER BY total_reservations DESC
|
|---|
| 151 | """)
|
|---|
| 152 | rows = cur.fetchall()
|
|---|
| 153 | print("\n --- Resource Type Utilization ---")
|
|---|
| 154 | print_table(["Type", "Category", "Resources", "Reservations", "Avg/Resource"], rows)
|
|---|
| 155 |
|
|---|
| 156 |
|
|---|
| 157 | def _monthly_trends():
|
|---|
| 158 | with get_connection() as conn:
|
|---|
| 159 | with conn.cursor() as cur:
|
|---|
| 160 | cur.execute("""
|
|---|
| 161 | SELECT TO_CHAR(DATE_TRUNC('month', created_at), 'YYYY-MM'),
|
|---|
| 162 | COUNT(*) AS created,
|
|---|
| 163 | COUNT(*) FILTER (WHERE status IN ('approved', 'completed')) AS successful,
|
|---|
| 164 | COUNT(*) FILTER (WHERE status = 'rejected') AS rejected,
|
|---|
| 165 | COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled
|
|---|
| 166 | FROM reservations
|
|---|
| 167 | GROUP BY DATE_TRUNC('month', created_at)
|
|---|
| 168 | ORDER BY DATE_TRUNC('month', created_at)
|
|---|
| 169 | """)
|
|---|
| 170 | rows = cur.fetchall()
|
|---|
| 171 | print("\n --- Monthly Reservation Trends ---")
|
|---|
| 172 | print_table(["Month", "Created", "Successful", "Rejected", "Cancelled"], rows)
|
|---|