Index: backend/package.json
===================================================================
--- backend/package.json	(revision fc5616f53fd0aa44b9ab596531c777aeb6c71d4a)
+++ backend/package.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -5,5 +5,10 @@
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "db:apply": "node scripts/applyEnhancements.js",
+    "db:seed": "node scripts/seedDemoData.js",
+    "db:proofs": "node scripts/runProof.js",
+    "db:explain": "node scripts/explainQueries.js",
+    "db:reports": "node scripts/runReports.js"
   },
   "keywords": [],
Index: backend/proofs/class_bookings_explain.json
===================================================================
--- backend/proofs/class_bookings_explain.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/class_bookings_explain.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,200 @@
+{
+  "Plan": {
+    "Node Type": "Sort",
+    "Parallel Aware": false,
+    "Async Capable": false,
+    "Startup Cost": 16.35,
+    "Total Cost": 16.36,
+    "Plan Rows": 1,
+    "Plan Width": 20,
+    "Actual Startup Time": 0.232,
+    "Actual Total Time": 0.234,
+    "Actual Rows": 1,
+    "Actual Loops": 1,
+    "Sort Key": [
+      "c.date",
+      "c.class_id"
+    ],
+    "Sort Method": "quicksort",
+    "Sort Space Used": 25,
+    "Sort Space Type": "Memory",
+    "Shared Hit Blocks": 8,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 1,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0,
+    "Plans": [
+      {
+        "Node Type": "Aggregate",
+        "Strategy": "Sorted",
+        "Partial Mode": "Simple",
+        "Parent Relationship": "Outer",
+        "Parallel Aware": false,
+        "Async Capable": false,
+        "Startup Cost": 16.32,
+        "Total Cost": 16.34,
+        "Plan Rows": 1,
+        "Plan Width": 20,
+        "Actual Startup Time": 0.201,
+        "Actual Total Time": 0.202,
+        "Actual Rows": 1,
+        "Actual Loops": 1,
+        "Group Key": [
+          "c.class_id"
+        ],
+        "Shared Hit Blocks": 5,
+        "Shared Read Blocks": 0,
+        "Shared Dirtied Blocks": 1,
+        "Shared Written Blocks": 0,
+        "Local Hit Blocks": 0,
+        "Local Read Blocks": 0,
+        "Local Dirtied Blocks": 0,
+        "Local Written Blocks": 0,
+        "Temp Read Blocks": 0,
+        "Temp Written Blocks": 0,
+        "Plans": [
+          {
+            "Node Type": "Sort",
+            "Parent Relationship": "Outer",
+            "Parallel Aware": false,
+            "Async Capable": false,
+            "Startup Cost": 16.32,
+            "Total Cost": 16.32,
+            "Plan Rows": 2,
+            "Plan Width": 20,
+            "Actual Startup Time": 0.188,
+            "Actual Total Time": 0.189,
+            "Actual Rows": 1,
+            "Actual Loops": 1,
+            "Sort Key": [
+              "c.class_id"
+            ],
+            "Sort Method": "quicksort",
+            "Sort Space Used": 25,
+            "Sort Space Type": "Memory",
+            "Shared Hit Blocks": 5,
+            "Shared Read Blocks": 0,
+            "Shared Dirtied Blocks": 1,
+            "Shared Written Blocks": 0,
+            "Local Hit Blocks": 0,
+            "Local Read Blocks": 0,
+            "Local Dirtied Blocks": 0,
+            "Local Written Blocks": 0,
+            "Temp Read Blocks": 0,
+            "Temp Written Blocks": 0,
+            "Plans": [
+              {
+                "Node Type": "Nested Loop",
+                "Parent Relationship": "Outer",
+                "Parallel Aware": false,
+                "Async Capable": false,
+                "Join Type": "Left",
+                "Startup Cost": 0.26,
+                "Total Cost": 16.31,
+                "Plan Rows": 2,
+                "Plan Width": 20,
+                "Actual Startup Time": 0.161,
+                "Actual Total Time": 0.167,
+                "Actual Rows": 1,
+                "Actual Loops": 1,
+                "Inner Unique": false,
+                "Shared Hit Blocks": 5,
+                "Shared Read Blocks": 0,
+                "Shared Dirtied Blocks": 1,
+                "Shared Written Blocks": 0,
+                "Local Hit Blocks": 0,
+                "Local Read Blocks": 0,
+                "Local Dirtied Blocks": 0,
+                "Local Written Blocks": 0,
+                "Temp Read Blocks": 0,
+                "Temp Written Blocks": 0,
+                "Plans": [
+                  {
+                    "Node Type": "Index Scan",
+                    "Parent Relationship": "Outer",
+                    "Parallel Aware": false,
+                    "Async Capable": false,
+                    "Scan Direction": "Forward",
+                    "Index Name": "idx_class_date_time",
+                    "Relation Name": "Class",
+                    "Alias": "c",
+                    "Startup Cost": 0.13,
+                    "Total Cost": 8.15,
+                    "Plan Rows": 1,
+                    "Plan Width": 12,
+                    "Actual Startup Time": 0.065,
+                    "Actual Total Time": 0.066,
+                    "Actual Rows": 1,
+                    "Actual Loops": 1,
+                    "Index Cond": "(date >= CURRENT_DATE)",
+                    "Rows Removed by Index Recheck": 0,
+                    "Shared Hit Blocks": 2,
+                    "Shared Read Blocks": 0,
+                    "Shared Dirtied Blocks": 0,
+                    "Shared Written Blocks": 0,
+                    "Local Hit Blocks": 0,
+                    "Local Read Blocks": 0,
+                    "Local Dirtied Blocks": 0,
+                    "Local Written Blocks": 0,
+                    "Temp Read Blocks": 0,
+                    "Temp Written Blocks": 0
+                  },
+                  {
+                    "Node Type": "Index Scan",
+                    "Parent Relationship": "Inner",
+                    "Parallel Aware": false,
+                    "Async Capable": false,
+                    "Scan Direction": "Forward",
+                    "Index Name": "idx_ubc_class",
+                    "Relation Name": "User_Booked_Class",
+                    "Alias": "ubc",
+                    "Startup Cost": 0.13,
+                    "Total Cost": 8.15,
+                    "Plan Rows": 1,
+                    "Plan Width": 16,
+                    "Actual Startup Time": 0.092,
+                    "Actual Total Time": 0.095,
+                    "Actual Rows": 1,
+                    "Actual Loops": 1,
+                    "Index Cond": "(class_id = c.class_id)",
+                    "Rows Removed by Index Recheck": 0,
+                    "Shared Hit Blocks": 3,
+                    "Shared Read Blocks": 0,
+                    "Shared Dirtied Blocks": 1,
+                    "Shared Written Blocks": 0,
+                    "Local Hit Blocks": 0,
+                    "Local Read Blocks": 0,
+                    "Local Dirtied Blocks": 0,
+                    "Local Written Blocks": 0,
+                    "Temp Read Blocks": 0,
+                    "Temp Written Blocks": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+  "Planning": {
+    "Shared Hit Blocks": 113,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0
+  },
+  "Planning Time": 0.611,
+  "Triggers": [],
+  "Execution Time": 0.405
+}
Index: backend/proofs/class_bookings_explain_forced.json
===================================================================
--- backend/proofs/class_bookings_explain_forced.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/class_bookings_explain_forced.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,200 @@
+{
+  "Plan": {
+    "Node Type": "Sort",
+    "Parallel Aware": false,
+    "Async Capable": false,
+    "Startup Cost": 16.35,
+    "Total Cost": 16.36,
+    "Plan Rows": 1,
+    "Plan Width": 20,
+    "Actual Startup Time": 0.071,
+    "Actual Total Time": 0.072,
+    "Actual Rows": 1,
+    "Actual Loops": 1,
+    "Sort Key": [
+      "c.date",
+      "c.class_id"
+    ],
+    "Sort Method": "quicksort",
+    "Sort Space Used": 25,
+    "Sort Space Type": "Memory",
+    "Shared Hit Blocks": 4,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0,
+    "Plans": [
+      {
+        "Node Type": "Aggregate",
+        "Strategy": "Sorted",
+        "Partial Mode": "Simple",
+        "Parent Relationship": "Outer",
+        "Parallel Aware": false,
+        "Async Capable": false,
+        "Startup Cost": 16.32,
+        "Total Cost": 16.34,
+        "Plan Rows": 1,
+        "Plan Width": 20,
+        "Actual Startup Time": 0.059,
+        "Actual Total Time": 0.06,
+        "Actual Rows": 1,
+        "Actual Loops": 1,
+        "Group Key": [
+          "c.class_id"
+        ],
+        "Shared Hit Blocks": 4,
+        "Shared Read Blocks": 0,
+        "Shared Dirtied Blocks": 0,
+        "Shared Written Blocks": 0,
+        "Local Hit Blocks": 0,
+        "Local Read Blocks": 0,
+        "Local Dirtied Blocks": 0,
+        "Local Written Blocks": 0,
+        "Temp Read Blocks": 0,
+        "Temp Written Blocks": 0,
+        "Plans": [
+          {
+            "Node Type": "Sort",
+            "Parent Relationship": "Outer",
+            "Parallel Aware": false,
+            "Async Capable": false,
+            "Startup Cost": 16.32,
+            "Total Cost": 16.32,
+            "Plan Rows": 2,
+            "Plan Width": 20,
+            "Actual Startup Time": 0.053,
+            "Actual Total Time": 0.054,
+            "Actual Rows": 1,
+            "Actual Loops": 1,
+            "Sort Key": [
+              "c.class_id"
+            ],
+            "Sort Method": "quicksort",
+            "Sort Space Used": 25,
+            "Sort Space Type": "Memory",
+            "Shared Hit Blocks": 4,
+            "Shared Read Blocks": 0,
+            "Shared Dirtied Blocks": 0,
+            "Shared Written Blocks": 0,
+            "Local Hit Blocks": 0,
+            "Local Read Blocks": 0,
+            "Local Dirtied Blocks": 0,
+            "Local Written Blocks": 0,
+            "Temp Read Blocks": 0,
+            "Temp Written Blocks": 0,
+            "Plans": [
+              {
+                "Node Type": "Nested Loop",
+                "Parent Relationship": "Outer",
+                "Parallel Aware": false,
+                "Async Capable": false,
+                "Join Type": "Left",
+                "Startup Cost": 0.26,
+                "Total Cost": 16.31,
+                "Plan Rows": 2,
+                "Plan Width": 20,
+                "Actual Startup Time": 0.043,
+                "Actual Total Time": 0.045,
+                "Actual Rows": 1,
+                "Actual Loops": 1,
+                "Inner Unique": false,
+                "Shared Hit Blocks": 4,
+                "Shared Read Blocks": 0,
+                "Shared Dirtied Blocks": 0,
+                "Shared Written Blocks": 0,
+                "Local Hit Blocks": 0,
+                "Local Read Blocks": 0,
+                "Local Dirtied Blocks": 0,
+                "Local Written Blocks": 0,
+                "Temp Read Blocks": 0,
+                "Temp Written Blocks": 0,
+                "Plans": [
+                  {
+                    "Node Type": "Index Scan",
+                    "Parent Relationship": "Outer",
+                    "Parallel Aware": false,
+                    "Async Capable": false,
+                    "Scan Direction": "Forward",
+                    "Index Name": "idx_class_date_time",
+                    "Relation Name": "Class",
+                    "Alias": "c",
+                    "Startup Cost": 0.13,
+                    "Total Cost": 8.15,
+                    "Plan Rows": 1,
+                    "Plan Width": 12,
+                    "Actual Startup Time": 0.024,
+                    "Actual Total Time": 0.024,
+                    "Actual Rows": 1,
+                    "Actual Loops": 1,
+                    "Index Cond": "(date >= CURRENT_DATE)",
+                    "Rows Removed by Index Recheck": 0,
+                    "Shared Hit Blocks": 2,
+                    "Shared Read Blocks": 0,
+                    "Shared Dirtied Blocks": 0,
+                    "Shared Written Blocks": 0,
+                    "Local Hit Blocks": 0,
+                    "Local Read Blocks": 0,
+                    "Local Dirtied Blocks": 0,
+                    "Local Written Blocks": 0,
+                    "Temp Read Blocks": 0,
+                    "Temp Written Blocks": 0
+                  },
+                  {
+                    "Node Type": "Index Scan",
+                    "Parent Relationship": "Inner",
+                    "Parallel Aware": false,
+                    "Async Capable": false,
+                    "Scan Direction": "Forward",
+                    "Index Name": "idx_ubc_class",
+                    "Relation Name": "User_Booked_Class",
+                    "Alias": "ubc",
+                    "Startup Cost": 0.13,
+                    "Total Cost": 8.15,
+                    "Plan Rows": 1,
+                    "Plan Width": 16,
+                    "Actual Startup Time": 0.016,
+                    "Actual Total Time": 0.016,
+                    "Actual Rows": 1,
+                    "Actual Loops": 1,
+                    "Index Cond": "(class_id = c.class_id)",
+                    "Rows Removed by Index Recheck": 0,
+                    "Shared Hit Blocks": 2,
+                    "Shared Read Blocks": 0,
+                    "Shared Dirtied Blocks": 0,
+                    "Shared Written Blocks": 0,
+                    "Local Hit Blocks": 0,
+                    "Local Read Blocks": 0,
+                    "Local Dirtied Blocks": 0,
+                    "Local Written Blocks": 0,
+                    "Temp Read Blocks": 0,
+                    "Temp Written Blocks": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+  "Planning": {
+    "Shared Hit Blocks": 3,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0
+  },
+  "Planning Time": 0.319,
+  "Triggers": [],
+  "Execution Time": 0.118
+}
Index: backend/proofs/class_utilization.json
===================================================================
--- backend/proofs/class_utilization.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/class_utilization.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,38 @@
+[
+  {
+    "class_id": "5",
+    "date": "2025-06-09T22:00:00.000Z",
+    "start_time": "08:00:00",
+    "end_time": "09:00:00",
+    "location": "Studio A",
+    "capacity": 20,
+    "booked": "1",
+    "utilization_pct": "5.00",
+    "instructor_id": "5",
+    "daily_rank": "2"
+  },
+  {
+    "class_id": "6",
+    "date": "2025-06-09T22:00:00.000Z",
+    "start_time": "09:30:00",
+    "end_time": "10:30:00",
+    "location": "Studio B",
+    "capacity": 15,
+    "booked": "1",
+    "utilization_pct": "6.67",
+    "instructor_id": "6",
+    "daily_rank": "1"
+  },
+  {
+    "class_id": "7",
+    "date": "2025-09-26T22:00:00.000Z",
+    "start_time": "18:00:00",
+    "end_time": "19:00:00",
+    "location": "Studio A",
+    "capacity": 2,
+    "booked": "1",
+    "utilization_pct": "50.00",
+    "instructor_id": "7",
+    "daily_rank": "1"
+  }
+]
Index: backend/proofs/events_explain.json
===================================================================
--- backend/proofs/events_explain.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/events_explain.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,77 @@
+{
+  "Plan": {
+    "Node Type": "Sort",
+    "Parallel Aware": false,
+    "Async Capable": false,
+    "Startup Cost": 1.04,
+    "Total Cost": 1.04,
+    "Plan Rows": 1,
+    "Plan Width": 488,
+    "Actual Startup Time": 0.063,
+    "Actual Total Time": 0.064,
+    "Actual Rows": 0,
+    "Actual Loops": 1,
+    "Sort Key": [
+      "date",
+      "\"time\""
+    ],
+    "Sort Method": "quicksort",
+    "Sort Space Used": 25,
+    "Sort Space Type": "Memory",
+    "Shared Hit Blocks": 9,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0,
+    "Plans": [
+      {
+        "Node Type": "Seq Scan",
+        "Parent Relationship": "Outer",
+        "Parallel Aware": false,
+        "Async Capable": false,
+        "Relation Name": "Event",
+        "Alias": "Event",
+        "Startup Cost": 0,
+        "Total Cost": 1.03,
+        "Plan Rows": 1,
+        "Plan Width": 488,
+        "Actual Startup Time": 0.015,
+        "Actual Total Time": 0.015,
+        "Actual Rows": 0,
+        "Actual Loops": 1,
+        "Filter": "(date >= CURRENT_DATE)",
+        "Rows Removed by Filter": 2,
+        "Shared Hit Blocks": 1,
+        "Shared Read Blocks": 0,
+        "Shared Dirtied Blocks": 0,
+        "Shared Written Blocks": 0,
+        "Local Hit Blocks": 0,
+        "Local Read Blocks": 0,
+        "Local Dirtied Blocks": 0,
+        "Local Written Blocks": 0,
+        "Temp Read Blocks": 0,
+        "Temp Written Blocks": 0
+      }
+    ]
+  },
+  "Planning": {
+    "Shared Hit Blocks": 164,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0
+  },
+  "Planning Time": 0.742,
+  "Triggers": [],
+  "Execution Time": 0.098
+}
Index: backend/proofs/events_explain_forced.json
===================================================================
--- backend/proofs/events_explain_forced.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/events_explain_forced.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,46 @@
+{
+  "Plan": {
+    "Node Type": "Index Scan",
+    "Parallel Aware": false,
+    "Async Capable": false,
+    "Scan Direction": "Forward",
+    "Index Name": "idx_event_date_time",
+    "Relation Name": "Event",
+    "Alias": "Event",
+    "Startup Cost": 0.13,
+    "Total Cost": 8.15,
+    "Plan Rows": 1,
+    "Plan Width": 488,
+    "Actual Startup Time": 0.1,
+    "Actual Total Time": 0.101,
+    "Actual Rows": 0,
+    "Actual Loops": 1,
+    "Index Cond": "(date >= CURRENT_DATE)",
+    "Rows Removed by Index Recheck": 0,
+    "Shared Hit Blocks": 0,
+    "Shared Read Blocks": 1,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0
+  },
+  "Planning": {
+    "Shared Hit Blocks": 0,
+    "Shared Read Blocks": 0,
+    "Shared Dirtied Blocks": 0,
+    "Shared Written Blocks": 0,
+    "Local Hit Blocks": 0,
+    "Local Read Blocks": 0,
+    "Local Dirtied Blocks": 0,
+    "Local Written Blocks": 0,
+    "Temp Read Blocks": 0,
+    "Temp Written Blocks": 0
+  },
+  "Planning Time": 0.113,
+  "Triggers": [],
+  "Execution Time": 0.14
+}
Index: backend/proofs/top_spenders.json
===================================================================
--- backend/proofs/top_spenders.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/top_spenders.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,56 @@
+[
+  {
+    "user_id": "8",
+    "username": "mikiYoga",
+    "email": "miki@example.com",
+    "spend_packages": "45.00",
+    "spend_merch": "12.00",
+    "total_spend": "57.00",
+    "spend_rank": "1"
+  },
+  {
+    "user_id": "13",
+    "username": "bojan",
+    "email": "bojan@example.com",
+    "spend_packages": "0",
+    "spend_merch": "30.00",
+    "total_spend": "30.00",
+    "spend_rank": "2"
+  },
+  {
+    "user_id": "12",
+    "username": "ana",
+    "email": "ana@example.com",
+    "spend_packages": "25.00",
+    "spend_merch": "0",
+    "total_spend": "25.00",
+    "spend_rank": "3"
+  },
+  {
+    "user_id": "9",
+    "username": "davidG",
+    "email": "david@example.com",
+    "spend_packages": "0",
+    "spend_merch": "0",
+    "total_spend": "0",
+    "spend_rank": "4"
+  },
+  {
+    "user_id": "10",
+    "username": "proba1",
+    "email": "proba1@gmail.com",
+    "spend_packages": "0",
+    "spend_merch": "0",
+    "total_spend": "0",
+    "spend_rank": "4"
+  },
+  {
+    "user_id": "14",
+    "username": "ciro",
+    "email": "ciro@example.com",
+    "spend_packages": "0",
+    "spend_merch": "0",
+    "total_spend": "0",
+    "spend_rank": "4"
+  }
+]
Index: backend/proofs/training_pop_monthly.json
===================================================================
--- backend/proofs/training_pop_monthly.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/training_pop_monthly.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,30 @@
+[
+  {
+    "training_id": "7",
+    "training_name": "Vinyasa",
+    "month": "2025-08-31T22:00:00.000Z",
+    "num_bookings": "1",
+    "rank_in_month": "1"
+  },
+  {
+    "training_id": "8",
+    "training_name": "Yin",
+    "month": "2025-08-31T22:00:00.000Z",
+    "num_bookings": "1",
+    "rank_in_month": "1"
+  },
+  {
+    "training_id": "6",
+    "training_name": "Hatha Basics",
+    "month": "2025-05-31T22:00:00.000Z",
+    "num_bookings": "1",
+    "rank_in_month": "1"
+  },
+  {
+    "training_id": "5",
+    "training_name": "Vinyasa Flow",
+    "month": "2025-05-31T22:00:00.000Z",
+    "num_bookings": "1",
+    "rank_in_month": "1"
+  }
+]
Index: backend/proofs/transactions_proof.json
===================================================================
--- backend/proofs/transactions_proof.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/transactions_proof.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,32 @@
+{
+  "before": {
+    "class_id": "7",
+    "capacity": 2,
+    "seats_available": 1
+  },
+  "after": {
+    "class_id": "7",
+    "capacity": 2,
+    "seats_available": 0
+  },
+  "afterDelete": {
+    "class_id": "7",
+    "capacity": 2,
+    "seats_available": 1
+  },
+  "statuses": [
+    {
+      "user": "ana",
+      "status": "OK"
+    },
+    {
+      "user": "bojan",
+      "status": "CLASS_FULL"
+    },
+    {
+      "user": "ciro",
+      "status": "CLASS_FULL"
+    }
+  ],
+  "errors": []
+}
Index: backend/proofs/views_snapshot.json
===================================================================
--- backend/proofs/views_snapshot.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/proofs/views_snapshot.json	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,125 @@
+{
+  "vw_user_spend": [
+    {
+      "user_id": "8",
+      "username": "mikiYoga",
+      "email": "miki@example.com",
+      "spend_packages": "45.00",
+      "spend_merch": "12.00",
+      "total_spend": "57.00",
+      "cnt_packages": "1",
+      "cnt_merch": "1"
+    },
+    {
+      "user_id": "13",
+      "username": "bojan",
+      "email": "bojan@example.com",
+      "spend_packages": "0",
+      "spend_merch": "30.00",
+      "total_spend": "30.00",
+      "cnt_packages": "0",
+      "cnt_merch": "1"
+    },
+    {
+      "user_id": "12",
+      "username": "ana",
+      "email": "ana@example.com",
+      "spend_packages": "25.00",
+      "spend_merch": "0",
+      "total_spend": "25.00",
+      "cnt_packages": "1",
+      "cnt_merch": "0"
+    },
+    {
+      "user_id": "9",
+      "username": "davidG",
+      "email": "david@example.com",
+      "spend_packages": "0",
+      "spend_merch": "0",
+      "total_spend": "0",
+      "cnt_packages": "0",
+      "cnt_merch": "0"
+    },
+    {
+      "user_id": "10",
+      "username": "proba1",
+      "email": "proba1@gmail.com",
+      "spend_packages": "0",
+      "spend_merch": "0",
+      "total_spend": "0",
+      "cnt_packages": "0",
+      "cnt_merch": "0"
+    },
+    {
+      "user_id": "14",
+      "username": "ciro",
+      "email": "ciro@example.com",
+      "spend_packages": "0",
+      "spend_merch": "0",
+      "total_spend": "0",
+      "cnt_packages": "0",
+      "cnt_merch": "0"
+    }
+  ],
+  "vw_class_utilization": [
+    {
+      "class_id": "7",
+      "date": "2025-09-26T22:00:00.000Z",
+      "start_time": "18:00:00",
+      "end_time": "19:00:00",
+      "location": "Studio A",
+      "capacity": 2,
+      "booked": "1",
+      "utilization_pct": "50.00",
+      "instructor_id": "7"
+    },
+    {
+      "class_id": "6",
+      "date": "2025-06-09T22:00:00.000Z",
+      "start_time": "09:30:00",
+      "end_time": "10:30:00",
+      "location": "Studio B",
+      "capacity": 15,
+      "booked": "1",
+      "utilization_pct": "6.67",
+      "instructor_id": "6"
+    },
+    {
+      "class_id": "5",
+      "date": "2025-06-09T22:00:00.000Z",
+      "start_time": "08:00:00",
+      "end_time": "09:00:00",
+      "location": "Studio A",
+      "capacity": 20,
+      "booked": "1",
+      "utilization_pct": "5.00",
+      "instructor_id": "5"
+    }
+  ],
+  "vw_training_pop_monthly": [
+    {
+      "training_id": "7",
+      "training_name": "Vinyasa",
+      "month": "2025-08-31T22:00:00.000Z",
+      "num_bookings": "1"
+    },
+    {
+      "training_id": "8",
+      "training_name": "Yin",
+      "month": "2025-08-31T22:00:00.000Z",
+      "num_bookings": "1"
+    },
+    {
+      "training_id": "5",
+      "training_name": "Vinyasa Flow",
+      "month": "2025-05-31T22:00:00.000Z",
+      "num_bookings": "1"
+    },
+    {
+      "training_id": "6",
+      "training_name": "Hatha Basics",
+      "month": "2025-05-31T22:00:00.000Z",
+      "num_bookings": "1"
+    }
+  ]
+}
Index: backend/scripts/applyEnhancements.js
===================================================================
--- backend/scripts/applyEnhancements.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/scripts/applyEnhancements.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,21 @@
+const fs = require("fs");
+const path = require("path");
+const pool = require("../db"); //  module.exports = pool
+
+(async () => {
+  const sqlPath = path.join(__dirname, "../sql/blisscore_enhancements.sql");
+  const sql = fs.readFileSync(sqlPath, "utf8");
+
+  const client = await pool.connect();
+  try {
+    console.log("Applying SQL from:", sqlPath);
+    await client.query(sql);
+    console.log(" Enhancements applied (views, triggers, function, indexes).");
+  } catch (e) {
+    console.error(" Error applying enhancements:", e);
+    process.exitCode = 1;
+  } finally {
+    client.release();
+    await pool.end();
+  }
+})();
Index: backend/scripts/explainQueries.js
===================================================================
--- backend/scripts/explainQueries.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/scripts/explainQueries.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,79 @@
+const fs = require("fs");
+const path = require("path");
+const pool = require("../db");
+
+const outDir = path.join(__dirname, "../proofs");
+if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
+
+async function explainToFile(file, sql, forceIndex = false) {
+  const client = await pool.connect();
+  try {
+    if (forceIndex) {
+      await client.query("SET enable_seqscan = off");
+      await client.query("SET enable_bitmapscan = on");
+      await client.query("SET enable_indexscan  = on");
+    }
+    const q = `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${sql}`;
+    const { rows } = await client.query(q);
+    fs.writeFileSync(
+      path.join(outDir, file),
+      JSON.stringify(rows[0]["QUERY PLAN"][0], null, 2)
+    );
+    console.log(`Saved ${file}`);
+  } finally {
+    client.release();
+  }
+}
+
+(async () => {
+  try {
+    // A) Events upcoming ordered by (date,time) — obicni
+    await explainToFile(
+      "events_explain.json",
+      `SELECT * FROM "Event"
+       WHERE date >= CURRENT_DATE
+       ORDER BY date, time`
+    );
+
+    // A2) Forced-index variant
+    await explainToFile(
+      "events_explain_forced.json",
+      `SELECT * FROM "Event"
+       WHERE date >= CURRENT_DATE
+       ORDER BY date, time`,
+      true
+    );
+
+    // B) Bookings per upcoming class — obicni
+    await explainToFile(
+      "class_bookings_explain.json",
+      `SELECT c.class_id, c.date, COUNT(ubc.user_id) AS bookings
+       FROM "Class" c
+       LEFT JOIN "User_Booked_Class" ubc
+         ON ubc.class_id = c.class_id
+       WHERE c.date >= CURRENT_DATE
+       GROUP BY c.class_id, c.date
+       ORDER BY c.date, c.class_id`
+    );
+
+    // B2) Forced-index variant
+    await explainToFile(
+      "class_bookings_explain_forced.json",
+      `SELECT c.class_id, c.date, COUNT(ubc.user_id) AS bookings
+       FROM "Class" c
+       LEFT JOIN "User_Booked_Class" ubc
+         ON ubc.class_id = c.class_id
+       WHERE c.date >= CURRENT_DATE
+       GROUP BY c.class_id, c.date
+       ORDER BY c.date, c.class_id`,
+      true
+    );
+
+    console.log("All EXPLAIN files saved under backend/proofs/.");
+  } catch (e) {
+    console.error("EXPLAIN error:", e);
+    process.exitCode = 1;
+  } finally {
+    await pool.end();
+  }
+})();
Index: backend/scripts/runProof.js
===================================================================
--- backend/scripts/runProof.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/scripts/runProof.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,133 @@
+const fs = require("fs");
+const path = require("path");
+const pool = require("../db");
+
+const outDir = path.join(__dirname, "../proofs");
+if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
+
+async function run() {
+  const client = await pool.connect();
+  const result = {
+    before: null,
+    after: null,
+    afterDelete: null,
+    statuses: [],
+    errors: [],
+  };
+
+  try {
+    console.log("=== PROOF: transactions + triggers ===");
+
+    // pick a class
+    const { rows: classRows } = await client.query(
+      `SELECT class_id, capacity, seats_available FROM "Class" ORDER BY class_id DESC LIMIT 1`
+    );
+    const cls = classRows[0];
+    console.log("Class before:", cls);
+    result.before = cls;
+
+    // get users (sorted by username)
+    const { rows: uRows } = await client.query(
+      `SELECT user_id, username FROM "User" WHERE username IN ('ana','bojan','ciro') ORDER BY username`
+    );
+    const [ana, bojan, ciro] = uRows; // already ordered by username
+    console.log("Users:", uRows);
+
+    await client.query("BEGIN");
+
+    // 2 successful + 1 expected CLASS_FULL
+    let r1 = await client.query(`SELECT book_class($1,$2) AS status`, [
+      ana.user_id,
+      cls.class_id,
+    ]);
+    console.log("book_class(ana):", r1.rows[0].status);
+    result.statuses.push({ user: "ana", status: r1.rows[0].status });
+
+    let r2 = await client.query(`SELECT book_class($1,$2) AS status`, [
+      bojan.user_id,
+      cls.class_id,
+    ]);
+    console.log("book_class(bojan):", r2.rows[0].status);
+    result.statuses.push({ user: "bojan", status: r2.rows[0].status });
+
+    let r3 = await client.query(`SELECT book_class($1,$2) AS status`, [
+      ciro.user_id,
+      cls.class_id,
+    ]);
+    console.log(
+      "book_class(ciro):",
+      r3.rows[0].status,
+      "(expected CLASS_FULL)"
+    );
+    result.statuses.push({ user: "ciro", status: r3.rows[0].status });
+
+    await client.query("COMMIT");
+
+    // after commit
+    let after = await client.query(
+      `SELECT class_id, capacity, seats_available FROM "Class" WHERE class_id=$1`,
+      [cls.class_id]
+    );
+    console.log("Class after bookings:", after.rows[0]);
+    result.after = after.rows[0];
+
+    // delete one booking to show AFTER DELETE increment
+    await client.query(
+      `DELETE FROM "User_Booked_Class" WHERE user_id=$1 AND class_id=$2`,
+      [ana.user_id, cls.class_id]
+    );
+    let afterDel = await client.query(
+      `SELECT class_id, capacity, seats_available FROM "Class" WHERE class_id=$1`,
+      [cls.class_id]
+    );
+    console.log(
+      "Class after delete (seats should increase by 1):",
+      afterDel.rows[0]
+    );
+    result.afterDelete = afterDel.rows[0];
+
+    console.log("Proof finished.");
+
+    // za snapshot
+    const views = {};
+    views.vw_user_spend = (
+      await pool.query(
+        `SELECT * FROM vw_user_spend ORDER BY total_spend DESC NULLS LAST LIMIT 20`
+      )
+    ).rows;
+    views.vw_class_utilization = (
+      await pool.query(
+        `SELECT * FROM vw_class_utilization ORDER BY utilization_pct DESC NULLS LAST LIMIT 20`
+      )
+    ).rows;
+    views.vw_training_pop_monthly = (
+      await pool.query(
+        `SELECT * FROM vw_training_pop_monthly ORDER BY month DESC, num_bookings DESC LIMIT 20`
+      )
+    ).rows;
+
+    fs.writeFileSync(
+      path.join(outDir, "views_snapshot.json"),
+      JSON.stringify(views, null, 2)
+    );
+    console.log("Saved views snapshot -> proofs/views_snapshot.json");
+  } catch (e) {
+    try {
+      await pool.query("ROLLBACK");
+    } catch {}
+    console.error("Proof error:", e);
+    result.errors.push(String(e));
+    process.exitCode = 1;
+  } finally {
+    // write the transaction proof JSON
+    fs.writeFileSync(
+      path.join(outDir, "transactions_proof.json"),
+      JSON.stringify(result, null, 2)
+    );
+    console.log("Saved transaction proof -> proofs/transactions_proof.json");
+    client.release();
+    await pool.end();
+  }
+}
+
+run();
Index: backend/scripts/runReports.js
===================================================================
--- backend/scripts/runReports.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/scripts/runReports.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,58 @@
+const fs = require("fs");
+const path = require("path");
+const pool = require("../db");
+
+async function dump(name, rows) {
+  const dir = path.join(__dirname, "../proofs");
+  if (!fs.existsSync(dir)) fs.mkdirSync(dir);
+  const file = path.join(dir, `${name}.json`);
+  fs.writeFileSync(file, JSON.stringify(rows, null, 2));
+  console.log(`Saved ${name}.json (${rows.length} rows).`);
+}
+
+(async () => {
+  try {
+    // 1) Top spenders (uses vw_user_spend)
+    const topSpendersSql = `
+      SELECT user_id, username, email,
+             spend_packages, spend_merch, total_spend,
+             RANK() OVER (ORDER BY total_spend DESC) AS spend_rank
+      FROM vw_user_spend
+      ORDER BY total_spend DESC, user_id;
+    `;
+    let r1 = await pool.query(topSpendersSql);
+    console.table(r1.rows);
+    await dump("top_spenders", r1.rows);
+
+    // 2) Class utilization + daily rank (uses vw_class_utilization)
+    const utilizationSql = `
+      SELECT *,
+             DENSE_RANK() OVER (PARTITION BY date ORDER BY utilization_pct DESC NULLS LAST) AS daily_rank
+      FROM vw_class_utilization
+      ORDER BY date, start_time, class_id;
+    `;
+    let r2 = await pool.query(utilizationSql);
+    console.table(r2.rows);
+    await dump("class_utilization", r2.rows);
+
+    // 3) Training popularity by month (uses vw_training_pop_monthly)
+    const popSql = `
+      SELECT training_id, training_name, month, num_bookings,
+             RANK() OVER (PARTITION BY month ORDER BY num_bookings DESC NULLS LAST) AS rank_in_month
+      FROM vw_training_pop_monthly
+      ORDER BY month DESC, rank_in_month, training_name;
+    `;
+    let r3 = await pool.query(popSql);
+    console.table(r3.rows);
+    await dump("training_pop_monthly", r3.rows);
+
+    console.log(
+      "Reports generated under backend/proofs/. Paste these into the wiki."
+    );
+  } catch (e) {
+    console.error("Reports error:", e);
+    process.exitCode = 1;
+  } finally {
+    await pool.end();
+  }
+})();
Index: backend/scripts/seedDemoData.js
===================================================================
--- backend/scripts/seedDemoData.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/scripts/seedDemoData.js	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,214 @@
+const pool = require("../db");
+
+async function ensureOne(sql, params = []) {
+  const { rows } = await pool.query(sql, params);
+  return rows[0];
+}
+
+async function run() {
+  const client = await pool.connect();
+  try {
+    console.log("Seeding small demo data...");
+    await client.query("BEGIN");
+
+    // Users
+    const uAna = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "User"(username,email,password_hash,first_name,last_name)
+        SELECT 'ana','ana@example.com','x','Ana','A'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "User" WHERE username='ana'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "User" WHERE username='ana'
+      LIMIT 1;
+    `);
+
+    const uBojan = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "User"(username,email,password_hash,first_name,last_name)
+        SELECT 'bojan','bojan@example.com','x','Bojan','B'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "User" WHERE username='bojan'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "User" WHERE username='bojan'
+      LIMIT 1;
+    `);
+
+    // third user to provoke CLASS_FULL
+    const uCiro = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "User"(username,email,password_hash,first_name,last_name)
+        SELECT 'ciro','ciro@example.com','x','Ciro','C'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "User" WHERE username='ciro'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "User" WHERE username='ciro'
+      LIMIT 1;
+    `);
+
+    // Instructor
+    const instr = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "Instructor"(instructor_email,instructor_password_hash,first_name,last_name,biography)
+        SELECT 'guru@bliss.com','x','Guru','G','Yoga master'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "Instructor" WHERE instructor_email='guru@bliss.com'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Instructor" WHERE instructor_email='guru@bliss.com'
+      LIMIT 1;
+    `);
+
+    // Trainings
+    const tVinyasa = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "Training"(training_name,description,duration,intensity_level)
+        SELECT 'Vinyasa','Flow',60,'Medium'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "Training" WHERE training_name='Vinyasa'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Training" WHERE training_name='Vinyasa'
+      LIMIT 1;
+    `);
+
+    const tYin = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "Training"(training_name,description,duration,intensity_level)
+        SELECT 'Yin','Slow',75,'Low'
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "Training" WHERE training_name='Yin'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Training" WHERE training_name='Yin'
+      LIMIT 1;
+    `);
+
+    // One class tomorrow with capacity 2
+    const cls = await ensureOne(
+      `
+      WITH ins AS (
+        INSERT INTO "Class"(date,start_time,end_time,location,capacity,seats_available,instructor_id)
+        SELECT CURRENT_DATE + 1,'18:00','19:00','Studio A',2,2,$1
+        WHERE NOT EXISTS (
+          SELECT 1 FROM "Class" WHERE date=CURRENT_DATE + 1 AND start_time='18:00' AND location='Studio A'
+        )
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Class" WHERE date=CURRENT_DATE + 1 AND start_time='18:00' AND location='Studio A'
+      LIMIT 1;
+    `,
+      [instr.instructor_id]
+    );
+
+    // Link class to both trainings
+    await client.query(
+      `
+      INSERT INTO "Class_Includes_Training"(class_id,training_id)
+      SELECT $1, $2 WHERE NOT EXISTS (
+        SELECT 1 FROM "Class_Includes_Training" WHERE class_id=$1 AND training_id=$2
+      );
+    `,
+      [cls.class_id, tVinyasa.training_id]
+    );
+
+    await client.query(
+      `
+      INSERT INTO "Class_Includes_Training"(class_id,training_id)
+      SELECT $1, $2 WHERE NOT EXISTS (
+        SELECT 1 FROM "Class_Includes_Training" WHERE class_id=$1 AND training_id=$2
+      );
+    `,
+      [cls.class_id, tYin.training_id]
+    );
+
+    // Packages
+    const pkg5 = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "Package"(package_name,price,num_classes)
+        SELECT '5-Class Pass',50,5
+        WHERE NOT EXISTS (SELECT 1 FROM "Package" WHERE package_name='5-Class Pass')
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Package" WHERE package_name='5-Class Pass'
+      LIMIT 1;
+    `);
+    // Merch
+    const mat = await ensureOne(`
+      WITH ins AS (
+        INSERT INTO "Merch_Items"(item_name,price)
+        SELECT 'Yoga Mat',30
+        WHERE NOT EXISTS (SELECT 1 FROM "Merch_Items" WHERE item_name='Yoga Mat')
+        RETURNING *
+      )
+      SELECT * FROM ins
+      UNION ALL
+      SELECT * FROM "Merch_Items" WHERE item_name='Yoga Mat'
+      LIMIT 1;
+    `);
+
+    // Purchases for reports
+    await client.query(
+      `
+      INSERT INTO "User_Purchased_Package"(user_id,package_id)
+      SELECT $1, $2 WHERE NOT EXISTS (
+        SELECT 1 FROM "User_Purchased_Package" WHERE user_id=$1 AND package_id=$2
+      );
+    `,
+      [uAna.user_id, pkg5.package_id]
+    );
+
+    await client.query(
+      `
+      INSERT INTO "User_Purchased_Merch"(user_id,merch_id)
+      SELECT $1, $2 WHERE NOT EXISTS (
+        SELECT 1 FROM "User_Purchased_Merch" WHERE user_id=$1 AND merch_id=$2
+      );
+    `,
+      [uBojan.user_id, mat.merch_id]
+    );
+
+    await client.query("COMMIT");
+
+    console.log("Seed done. User IDs:", {
+      ana: uAna.user_id,
+      bojan: uBojan.user_id,
+      ciro: uCiro.user_id,
+      classId: cls.class_id,
+    });
+  } catch (e) {
+    await client.query("ROLLBACK");
+    console.error("Seed error:", e);
+    process.exitCode = 1;
+  } finally {
+    client.release();
+    await pool.end();
+  }
+}
+
+run();
Index: backend/sql/blisscore_enhancements.sql
===================================================================
--- backend/sql/blisscore_enhancements.sql	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
+++ backend/sql/blisscore_enhancements.sql	(revision b9095a4fbbe8cfa1deeb628b0d92bfeb1db91073)
@@ -0,0 +1,163 @@
+-- VIEWS (used by advanced reports)
+
+
+-- 1) Потрошувачка по корисник (пакети + мерч)
+CREATE OR REPLACE VIEW vw_user_spend AS
+WITH pkg AS (
+  SELECT u.user_id, SUM(p.price) AS spend_packages, COUNT(*) AS cnt_packages
+  FROM "User_Purchased_Package" upp
+  JOIN "User"    u ON u.user_id = upp.user_id
+  JOIN "Package" p ON p.package_id = upp.package_id
+  GROUP BY u.user_id
+),
+merch AS (
+  SELECT u.user_id, SUM(m.price) AS spend_merch, COUNT(*) AS cnt_merch
+  FROM "User_Purchased_Merch" upm
+  JOIN "User"        u ON u.user_id = upm.user_id
+  JOIN "Merch_Items" m ON m.merch_id = upm.merch_id
+  GROUP BY u.user_id
+)
+SELECT u.user_id, u.username, u.email,
+       COALESCE(pkg.spend_packages,0) AS spend_packages,
+       COALESCE(merch.spend_merch,0)  AS spend_merch,
+       COALESCE(pkg.spend_packages,0) + COALESCE(merch.spend_merch,0) AS total_spend,
+       COALESCE(pkg.cnt_packages,0) AS cnt_packages,
+       COALESCE(merch.cnt_merch,0)  AS cnt_merch
+FROM "User" u
+LEFT JOIN pkg   ON pkg.user_id   = u.user_id
+LEFT JOIN merch ON merch.user_id = u.user_id;
+
+-- 2) Исполнетост на часови
+CREATE OR REPLACE VIEW vw_class_utilization AS
+SELECT
+  c.class_id, c.date, c.start_time, c.end_time, c.location,
+  c.capacity,
+  COUNT(ubc.user_id) AS booked,
+  CASE
+    WHEN c.capacity IS NULL OR c.capacity = 0 THEN NULL
+    ELSE ROUND((COUNT(ubc.user_id)::numeric / c.capacity) * 100, 2)
+  END AS utilization_pct,
+  c.instructor_id
+FROM "Class" c
+LEFT JOIN "User_Booked_Class" ubc ON ubc.class_id = c.class_id
+GROUP BY c.class_id;
+
+-- 3) Популарност на тренинзи по месец
+CREATE OR REPLACE VIEW vw_training_pop_monthly AS
+SELECT
+  t.training_id,
+  t.training_name,
+  date_trunc('month', c.date)::date AS month,
+  COUNT(DISTINCT ubc.user_id) AS num_bookings
+FROM "Training" t
+JOIN "Class_Includes_Training" cit ON cit.training_id = t.training_id
+JOIN "Class" c                     ON c.class_id      = cit.class_id
+LEFT JOIN "User_Booked_Class" ubc  ON ubc.class_id    = c.class_id
+GROUP BY t.training_id, t.training_name, date_trunc('month', c.date)::date;
+
+
+-- TRIGGERS: guard & maintain seats_available
+
+
+-- BEFORE INSERT: block overbooking (and lock Class row)
+CREATE OR REPLACE FUNCTION tg_ubc_before_ins_guard()
+RETURNS trigger LANGUAGE plpgsql AS $$
+DECLARE v_cap INT; v_seats INT; v_now_booked INT;
+BEGIN
+  SELECT capacity, seats_available INTO v_cap, v_seats
+  FROM "Class"
+  WHERE class_id = NEW.class_id
+  FOR UPDATE;
+
+  IF v_cap IS NOT NULL THEN
+    IF v_seats IS NULL THEN
+      SELECT COUNT(*) INTO v_now_booked
+      FROM "User_Booked_Class"
+      WHERE class_id = NEW.class_id
+      FOR UPDATE;
+      v_seats := v_cap - v_now_booked;
+    END IF;
+
+    IF v_seats <= 0 THEN
+      RAISE EXCEPTION 'Class % is full', NEW.class_id
+        USING ERRCODE = '23514'; -- check_violation
+    END IF;
+  END IF;
+
+  RETURN NEW;
+END $$;
+
+-- AFTER INSERT: decrement seats_available
+CREATE OR REPLACE FUNCTION tg_ubc_after_ins_decrement()
+RETURNS trigger LANGUAGE plpgsql AS $$
+BEGIN
+  UPDATE "Class"
+  SET seats_available = CASE
+                          WHEN seats_available IS NULL THEN NULL
+                          ELSE seats_available - 1
+                        END
+  WHERE class_id = NEW.class_id;
+  RETURN NULL;
+END $$;
+
+-- AFTER DELETE: increment seats_available
+CREATE OR REPLACE FUNCTION tg_ubc_after_del_increment()
+RETURNS trigger LANGUAGE plpgsql AS $$
+BEGIN
+  UPDATE "Class"
+  SET seats_available = CASE
+                          WHEN seats_available IS NULL THEN NULL
+                          ELSE seats_available + 1
+                        END
+  WHERE class_id = OLD.class_id;
+  RETURN NULL;
+END $$;
+
+DROP TRIGGER IF EXISTS trg_ubc_before_ins_guard    ON "User_Booked_Class";
+DROP TRIGGER IF EXISTS trg_ubc_after_ins_decrement  ON "User_Booked_Class";
+DROP TRIGGER IF EXISTS trg_ubc_after_del_increment  ON "User_Booked_Class";
+
+CREATE TRIGGER trg_ubc_before_ins_guard
+BEFORE INSERT ON "User_Booked_Class"
+FOR EACH ROW EXECUTE FUNCTION tg_ubc_before_ins_guard();
+
+CREATE TRIGGER trg_ubc_after_ins_decrement
+AFTER INSERT ON "User_Booked_Class"
+FOR EACH ROW EXECUTE FUNCTION tg_ubc_after_ins_decrement();
+
+CREATE TRIGGER trg_ubc_after_del_increment
+AFTER DELETE ON "User_Booked_Class"
+FOR EACH ROW EXECUTE FUNCTION tg_ubc_after_del_increment();
+
+
+-- Function for booking
+
+CREATE OR REPLACE FUNCTION book_class(p_user_id BIGINT, p_class_id BIGINT)
+RETURNS text LANGUAGE plpgsql AS $$
+BEGIN
+  INSERT INTO "User_Booked_Class"(user_id, class_id)
+  VALUES (p_user_id, p_class_id);
+  RETURN 'OK';
+EXCEPTION
+  WHEN unique_violation THEN
+    RETURN 'ALREADY_BOOKED';
+  WHEN foreign_key_violation THEN
+    RETURN 'NO_SUCH_USER_OR_CLASS';
+  WHEN check_violation THEN
+    RETURN 'CLASS_FULL';
+END $$;
+
+
+-- INDEXES 
+
+-- Индекси на bridge табели
+CREATE INDEX IF NOT EXISTS idx_ubc_class    ON "User_Booked_Class"(class_id);
+CREATE INDEX IF NOT EXISTS idx_cht_training ON "Class_Includes_Training"(training_id);
+CREATE INDEX IF NOT EXISTS idx_ue_event     ON "User_Event"(event_id);
+CREATE INDEX IF NOT EXISTS idx_upp_package  ON "User_Purchased_Package"(package_id);
+CREATE INDEX IF NOT EXISTS idx_upm_merch    ON "User_Purchased_Merch"(merch_id);
+CREATE INDEX IF NOT EXISTS idx_pic_class    ON "Package_Includes_Class"(class_id);
+
+-- Индекси за честите листања
+CREATE INDEX IF NOT EXISTS idx_event_date_time ON "Event"(date, time);
+CREATE INDEX IF NOT EXISTS idx_class_date_time ON "Class"(date, start_time);
