Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/backend/booking analytics #364

Merged
merged 11 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 175 additions & 1 deletion documentation/occupi-docs/pages/api-documentation/analytics-usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ all the users in the office space.
- [Workers peak office hours](#workers-peak-office-hours)
- [Workers arrival departure average](#workers-arival-departure-average)
- [Workers in office rate](#workers-in-office-rate)
- [Top Bookings](#top-bookings)
- [Bookings historical](#bookings-historical)
- [Bookings current](#bookings-current)

## Base URL

Expand Down Expand Up @@ -808,4 +811,175 @@ The workers in office rate endpoint is used to get the in office rate of all the
"error": "Failed to fetch user analytics!",
"status": 500,
}
```
```

### Top Bookings

The top bookings endpoint is used to get the top 3 bookings in the office space.

- **URL**

`/analytics/top-bookings`

- **Method**

`GET`

- **Request Body**

```json
{
"creator": "abcd@gmail", // this is optional
"attendees": ["abcd@gmail", "[email protected]"], // this is optional
"timeFrom": "2021-01-01T00:00:00.000Z", // this is optional and will default to 1970-01-01T00:00:00.000Z
"timeTo": "2021-01-01T00:00:00.000Z", // this is optional and will default to current date
"limit": 50, // this is optional and will default to 50 bookings to select
"page": 1 // this is optional and will default to 1
}
```

- **URL Params**

```
/analytics/top-bookings?creator=abcd@gmail&attendees=abcd@gmail,[email protected]&timeFrom=2021-01-01T00:00:00.000Z&timeTo=2021-01-01T00:00:00.000Z&limit=50&page=1
```

- **Success Response**

- **Code:** 200
- **Content:**
```json
{
"response": "Successfully fetched top bookings!",
"data": [data],
"totalResults": 1,
"totalPages": 1,
"currentPage": 1,
"status": 200,
}
```

- **Error Response**

- **Code:** 500
- **Content:**
```json
{
"error": "Failed to fetch top bookings!",
"status": 500,
}
```

### Bookings historical

The bookings historical endpoint is used to get the historical bookings in the office space.

- **URL**

`/analytics/bookings-historical`

- **Method**

`GET`

- **Request Body**

```json
{
"creator": "abcd@gmail", // this is optional
"attendees": ["abcd@gmail", "[email protected]"], // this is optional
"timeFrom": "2021-01-01T00:00:00.000Z", // this is optional and will default to 1970-01-01T00:00:00.000Z
"timeTo": "2021-01-01T00:00:00.000Z", // this is optional and will default to current date
"limit": 50, // this is optional and will default to 50 bookings to select
"page": 1 // this is optional and will default to 1
}
```

- **URL Params**

```
/analytics/bookings-historical?creator=abcd@gmail&attendees=abcd@gmail,[email protected]&timeFrom=2021-01-01T00:00:00.000Z&timeTo=2021-01-01T00:00:00.000Z&limit=50&page=1
```

- **Success Response**

- **Code:** 200
- **Content:**
```json
{
"response": "Successfully fetched historical bookings!",
"data": [data],
"totalResults": 1,
"totalPages": 1,
"currentPage": 1,
"status": 200,
}
```

- **Error Response**

- **Code:** 500
- **Content:**
```json
{
"error": "Failed to fetch historical bookings!",
"status": 500,
}
```

### Bookings current

The bookings current endpoint is used to get the current bookings in the office space.

- **URL**

`/analytics/bookings-current`

- **Method**

`GET`

- **Request Body**

```json
{
"creator": "abcd@gmail", // this is optional
"attendees": ["abcd@gmail", "[email protected]"], // this is optional
"timeFrom": "2021-01-01T00:00:00.000Z", // this is optional and will default to 1970-01-01T00:00:00.000Z
"timeTo": "2021-01-01T00:00:00.000Z", // this is optional and will default to current date
"limit": 50, // this is optional and will default to 50 bookings to select
"page": 1 // this is optional and will default to 1
}
```

- **URL Params**

```
/analytics/bookings-current?creator=abcd@gmail&attendees=abcd@gmail,[email protected]&timeFrom=2021-01-01T00:00:00.000Z&timeTo=2021-01-01T00:00:00.000Z&limit=50&page=1
```

- **Success Response**

- **Code:** 200
- **Content:**
```json
{
"response": "Successfully fetched current bookings!",
"data": [data],
"totalResults": 1,
"totalPages": 1,
"currentPage": 1,
"status": 200,
}
```

- **Error Response**

- **Code:** 500
- **Content:**
```json
{
"error": "Failed to fetch current bookings!",
"status": 500,
}
```
122 changes: 107 additions & 15 deletions occupi-backend/pkg/analytics/analytics.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package analytics

import (
"fmt"

"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models"
"go.mongodb.org/mongo-driver/bson"
)

func CreateMatchFilter(email string, filter models.OfficeHoursFilterStruct) bson.D {
func CreateOfficeHoursMatchFilter(email string, filter models.AnalyticsFilterStruct) bson.D {
// Create a match filter
matchFilter := bson.D{}

Expand All @@ -31,9 +33,43 @@ func CreateMatchFilter(email string, filter models.OfficeHoursFilterStruct) bson
return matchFilter
}

func CreateBookingMatchFilter(creatorEmail string, attendeesEmail []string, filter models.AnalyticsFilterStruct, dateFilter string) bson.D {
// Create a match filter
matchFilter := bson.D{}

// Conditionally add the email filter if email is not empty
if creatorEmail != "" {
matchFilter = append(matchFilter, bson.E{Key: "creator", Value: bson.D{{Key: "$eq", Value: creatorEmail}}})
}

// Conditionally add the attendees filter if emails is not of length 0
if len(attendeesEmail) > 0 {
fmt.Println(attendeesEmail)
// print len of attendeesEmail
fmt.Println(len(attendeesEmail))
matchFilter = append(matchFilter, bson.E{Key: "emails", Value: bson.D{{Key: "$in", Value: attendeesEmail}}})
}

// Conditionally add the time range filter if provided
timeRangeFilter := bson.D{}
if filter.Filter["timeFrom"] != "" {
timeRangeFilter = append(timeRangeFilter, bson.E{Key: "$gte", Value: filter.Filter["timeFrom"]})
}
if filter.Filter["timeTo"] != "" {
timeRangeFilter = append(timeRangeFilter, bson.E{Key: "$lte", Value: filter.Filter["timeTo"]})
}

// If there are time range filters, append them to the match filter
if len(timeRangeFilter) > 0 && len(filter.Filter) > 0 {
matchFilter = append(matchFilter, bson.E{Key: dateFilter, Value: timeRangeFilter})
}

return matchFilter
}

// GroupOfficeHoursByDay function with total hours calculation
func GroupOfficeHoursByDay(email string, filter models.OfficeHoursFilterStruct) bson.A {
matchFilter := CreateMatchFilter(email, filter)
func GroupOfficeHoursByDay(email string, filter models.AnalyticsFilterStruct) bson.A {
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -91,9 +127,9 @@ func GroupOfficeHoursByDay(email string, filter models.OfficeHoursFilterStruct)
}
}

func AverageOfficeHoursByWeekday(email string, filter models.OfficeHoursFilterStruct) bson.A {
func AverageOfficeHoursByWeekday(email string, filter models.AnalyticsFilterStruct) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -172,9 +208,9 @@ func AverageOfficeHoursByWeekday(email string, filter models.OfficeHoursFilterSt
}
}

func RatioInOutOfficeByWeekday(email string, filter models.OfficeHoursFilterStruct) bson.A {
func RatioInOutOfficeByWeekday(email string, filter models.AnalyticsFilterStruct) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -262,9 +298,9 @@ func RatioInOutOfficeByWeekday(email string, filter models.OfficeHoursFilterStru
}

// BusiestHoursByWeekday function to return the 3 busiest hours per weekday
func BusiestHoursByWeekday(email string, filter models.OfficeHoursFilterStruct) bson.A {
func BusiestHoursByWeekday(email string, filter models.AnalyticsFilterStruct) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -373,9 +409,9 @@ func BusiestHoursByWeekday(email string, filter models.OfficeHoursFilterStruct)
}

// LeastMostInOfficeWorker function to calculate the least or most "in office" worker
func LeastMostInOfficeWorker(email string, filter models.OfficeHoursFilterStruct, sort bool) bson.A {
func LeastMostInOfficeWorker(email string, filter models.AnalyticsFilterStruct, sort bool) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

var sortV int
if sort {
Expand Down Expand Up @@ -506,9 +542,9 @@ func LeastMostInOfficeWorker(email string, filter models.OfficeHoursFilterStruct
}

// AverageArrivalAndDepartureTimesByWeekday function to calculate the average arrival and departure times for each weekday
func AverageArrivalAndDepartureTimesByWeekday(email string, filter models.OfficeHoursFilterStruct) bson.A {
func AverageArrivalAndDepartureTimesByWeekday(email string, filter models.AnalyticsFilterStruct) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -760,9 +796,9 @@ func AverageArrivalAndDepartureTimesByWeekday(email string, filter models.Office
}

// CalculateInOfficeRate function to calculate absenteeism rates
func CalculateInOfficeRate(email string, filter models.OfficeHoursFilterStruct) bson.A {
func CalculateInOfficeRate(email string, filter models.AnalyticsFilterStruct) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateMatchFilter(email, filter)
matchFilter := CreateOfficeHoursMatchFilter(email, filter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
Expand Down Expand Up @@ -1031,3 +1067,59 @@ func CalculateInOfficeRate(email string, filter models.OfficeHoursFilterStruct)
},
}
}

func GetTop3MostBookedRooms(creatorEmail string, attendeeEmails []string, filter models.AnalyticsFilterStruct, dateFilter string) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateBookingMatchFilter(creatorEmail, attendeeEmails, filter, dateFilter)

return bson.A{
// Stage 1: Match filter conditions (email and time range)
bson.D{{Key: "$match", Value: matchFilter}},
// Stage 2: Apply skip for pagination
bson.D{{Key: "$skip", Value: filter.Skip}},
// Stage 3: Apply limit for pagination
bson.D{{Key: "$limit", Value: filter.Limit}},
// Stage 4: Group by the room ID to calculate the total bookings
bson.D{{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$roomId"},
{Key: "roomName", Value: bson.D{{Key: "$first", Value: "$roomName"}}},
{Key: "floorNo", Value: bson.D{{Key: "$first", Value: "$floorNo"}}},
{Key: "creators", Value: bson.D{{Key: "$push", Value: "$creator"}}},
{Key: "emails", Value: bson.D{{Key: "$push", Value: "$emails"}}},
{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
}}},
// Stage 5: Sort by count
bson.D{{Key: "$sort", Value: bson.D{{Key: "count", Value: -1}}}},
// Stage 6: Limit to the top 3 results
bson.D{{Key: "$limit", Value: 3}},
}
}

func AggregateBookings(creatorEmail string, attendeeEmails []string, filter models.AnalyticsFilterStruct, dateFilter string) bson.A {
// Create the match filter using the reusable function
matchFilter := CreateBookingMatchFilter(creatorEmail, attendeeEmails, filter, dateFilter)
return bson.A{
// Stage 1: Match filter conditions (email and time range)
bson.D{{Key: "$match", Value: matchFilter}},
// Stage 2: Apply skip for pagination
bson.D{{Key: "$skip", Value: filter.Skip}},
// Stage 3: Apply limit for pagination
bson.D{{Key: "$limit", Value: filter.Limit}},
// Stage 4: Get all bookings without grouping
bson.D{{Key: "$project", Value: bson.D{
{Key: "_id", Value: 0},
{Key: "occupiID", Value: "$occupiId"},
{Key: "roomName", Value: "$roomName"},
{Key: "roomId", Value: "$roomId"},
{Key: "emails", Value: "$emails"},
{Key: "checkedIn", Value: "$checkedIn"},
{Key: "creators", Value: "$creator"},
{Key: "floorNo", Value: "$floorNo"},
{Key: "date", Value: "$date"},
{Key: "start", Value: "$start"},
{Key: "end", Value: "$end"},
}}},
// Stage 5: Sort by date
bson.D{{Key: "$sort", Value: bson.D{{Key: "date", Value: 1}}}},
}
}
Loading
Loading