diff --git a/.gitignore b/.gitignore index 58b4b6f2..2bcfed36 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,10 @@ documentation/SRS-Docs/SRS.log documentation/SRS-Docs/SRS.out documentation/SRS-Docs/SRS.synctex.gz documentation/SRS-Docs/SRS.toc -.vscode \ No newline at end of file +.vscode +documentation/occupi-docs/Awards/Data.aux +documentation/occupi-docs/Awards/Data.fdb_latexmk +documentation/occupi-docs/Awards/Data.fls +documentation/occupi-docs/Awards/Data.out +documentation/occupi-docs/Awards/Data.synctex.gz +documentation/occupi-docs/Awards/Data.toc diff --git a/attendance_scaler.pkl b/attendance_scaler.pkl new file mode 100644 index 00000000..11f20c27 Binary files /dev/null and b/attendance_scaler.pkl differ diff --git a/documentation/occupi-docs/Awards/Data Science - Innovation using Data.pdf b/documentation/occupi-docs/Awards/Data Science - Innovation using Data.pdf new file mode 100644 index 00000000..d0395856 Binary files /dev/null and b/documentation/occupi-docs/Awards/Data Science - Innovation using Data.pdf differ diff --git a/documentation/occupi-docs/Awards/Data.tex b/documentation/occupi-docs/Awards/Data.tex new file mode 100644 index 00000000..958409eb --- /dev/null +++ b/documentation/occupi-docs/Awards/Data.tex @@ -0,0 +1,233 @@ +\documentclass[11pt,a4paper]{article} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% PACKAGES % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\usepackage[utf8]{inputenc} +\usepackage{graphicx} % Allows you to insert figures +\usepackage[export]{adjustbox} +\usepackage{booktabs} +\usepackage{amsmath} % Allows you to do equations +\usepackage{helvet} +\usepackage{hyperref} +\renewcommand{\familydefault}{\sfdefault} +\usepackage[a4paper, total={6.5in, 9.5in}]{geometry} % Formats the paper size, orientation, and margins +\linespread{1.1} % about 1.5 spacing in Word +\setlength{\parindent}{0pt} % no paragraph indents +\setlength{\parskip}{1em} % paragraphs separated by one line +\usepackage{listings} +\usepackage{enumitem} +\usepackage{xcolor} +\usepackage{hyperref} +\hypersetup{ + colorlinks=true, + urlcolor=cyan, + linktoc=none, +} +\usepackage{fancyhdr} +\pagestyle{fancy} +\fancyhead[L,C,R]{} +\fancyfoot[L]{Occupi - Office Capacity Predictor} +\fancyfoot[C]{} +\fancyfoot[R]{\textbf{\thepage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0.5pt} + +\definecolor{codegreen}{rgb}{0,0.6,0} +\definecolor{codegray}{rgb}{0.5,0.5,0.5} +\definecolor{codepurple}{rgb}{0.58,0,0.82} +\definecolor{backcolour}{rgb}{0.95,0.95,0.92} + +\lstdefinestyle{mystyle}{ +backgroundcolor=\color{backcolour}, +commentstyle=\color{codegreen}, +keywordstyle=\color{magenta}, +numberstyle=\tiny\color{codegray}, +stringstyle=\color{codepurple}, +basicstyle=\ttfamily\footnotesize, +breakatwhitespace=false, +breaklines=true, +keepspaces=true, +numbers=left, +numbersep=5pt, +showspaces=false, +showstringspaces=false, +showtabs=false, +tabsize=2, +} + +\lstset{style=mystyle} +\def\code#1{\texttt{#1}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% TITLE PAGE & TABLE OF CONTENTS % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{document} +\begin{titlepage} + \centering + \includegraphics[width=0.4\textwidth]{logo-white.png}\par\vspace{1cm} + {\scshape\LARGE Data Science: Innovative use of data \par} + \vspace{1.5cm} + {\huge\bfseries Occupi - Office Capacity Predictor\par} + \vspace{2.5cm} + \begin{tabular}{|c|c|} + \hline + \textbf{Name} & \textbf{Student Number} \\ + \hline + Rethakgetse Manaka & u22491032 \\ + Kamogelo Moeketse & u22623478 \\ + Michael Chinyama & u21546551 \\ + Tinashe Austin & u21564176 \\ + Carey Mokou & u21631532 \\ + \hline + \end{tabular} + \vfill + {\large October 10, 2024\par} +\end{titlepage} + +\tableofcontents +\pagebreak + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% MAIN DOCUMENT CONTENT % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\section*{Introduction} +\addcontentsline{toc}{section}{Introduction} +During the development of Occupi, a strong foundation of data science principles and methodologies was established to ensure that the project would deliver high-impact insights +and predictions. This project was built with the primary goal of handling sequential data efficiently using a mixture of Convolutional Neural Networks (CNN) +and Long Short-Term Memory (LSTM) networks for time series data classification. +Additionally, MongoDB functions and triggers were employed to handle real-time user statistics and data collection. + +The integration of CNN and LSTM models allowed us to capture both spatial and temporal dependencies in the data, +ensuring accurate predictions and classifications. CNNs were used to extract features from the raw time series data, +while LSTMs captured long-term dependencies, making the model particularly effective for time series classification. +This hybrid approach enabled our system to handle large datasets efficiently and provided meaningful predictions in areas such as capacity prediction. + +In addition to the machine learning models, MongoDB functions, aggregation, and triggers were crucial for automating the processing of user data and generating statistics in real time. +This streamlined the data pipeline, ensuring that the latest data was always available for analysis, thus maintaining the accuracy and relevance of the user statistics. + +\pagebreak + +\section*{Data generation and Model Training} +\addcontentsline{toc}{section}{Data generation and Model Training} +The use of Mockaroo and VBA was instrumental in simulating office attendance data that reflected real-world trends under various conditions, such as public holidays, weekends, and peak hours. +This simulated data was essential in generating large, high-quality datasets that were representative of the diverse scenarios that our predictive models would encounter. +This foundation allowed us to begin the initial stages of model training with confidence, knowing that the datasets were comprehensive and varied. + +\begin{figure}[htbp] + \centering + \includegraphics[width=0.9\textwidth]{Mockaroo.png} +\end{figure} + +To handle the sequential nature of this data, we employed a mixture of Convolutional Neural Networks (CNNs) and Long Short-Term Memory (LSTM) networks. +The CNNs were responsible for extracting features from the raw time series data, capturing patterns in attendance fluctuations, +while the LSTMs processed the temporal dependencies, enabling the model to learn from the historical trends and make accurate future predictions. + +\subsection*{Iterative Training and Optimization} +Once the initial models were trained on the simulated data, we rigorously evaluated their performance based on accuracy, precision, recall, +and other relevant metrics. +Our target was to achieve a model accuracy of at least 70\%, +which we identified as the threshold for reliable predictions in real-world applications. +However, the first few iterations of model training showed that while the models captured some patterns, the accuracy was still below our desired threshold. + +To address this, we adopted an iterative approach to model training and optimization. This process involved the following steps: + +\subsubsection*{Hyperparameter Tuning} +We adjusted key hyperparameters such as learning rate, batch size, and the number of epochs. +By experimenting with different combinations of these parameters, we gradually improved the performance of our models. +Grid search and random search techniques were employed to find the best hyperparameter settings for both the CNN and LSTM components of the model. + +\subsubsection*{Data Augmentation and Feature Engineering} +We further enhanced the datasets by introducing additional features that could improve the model's ability to make predictions. These features included: +\begin{itemize} + \item \textbf{Predicted Class}: To easily classify attendance levels. + \item \textbf{Predicted Level}: To capture ranges of attendance, such as low, medium, and high. +\end{itemize} + +\subsubsection*{Regularization Techniques} +To prevent overfitting during training, we applied techniques such as dropout in the LSTM layers, which randomly deactivates certain neurons during training. +This forced the model to learn more generalized patterns and improved its performance on unseen data. +Additionally, L2 regularization was applied to penalize excessively large weights, encouraging the model to focus on the most relevant features. + +\subsection*{Achieving 70\%+ Accuracy} +Through this iterative process of training, evaluation, and optimization, we were able to consistently improve the performance of our models. +After several rounds of refinement, the CNN-LSTM hybrid model achieved an accuracy above 70\%. +This threshold was met by balancing the complexity of the model with its ability to generalize across diverse office attendance scenarios, +ultimately resulting in a model capable of making reliable predictions on real-world data. + +\begin{figure}[htbp] + \centering + \includegraphics[width=0.9\textwidth]{Training.png} +\end{figure} + +\subsection*{Prediction and Reporting} +The model was used to generate predictions for both hourly and daily office attendance. +These predictions proved valuable in producing detailed reports for capacity predictions, allowing stakeholders to make informed decisions about space utilization. +By predicting attendance levels on an hourly and daily basis, the system provided actionable insights into peak times, potential under-utilization, and how attendance varied throughout the week. + +These reports allowed for: +\begin{itemize} + \item \textbf{Hourly Predictions}: Managers could monitor expected attendance levels hour-by-hour, helping them plan resources, staffing, and space requirements effectively. + \item \textbf{Daily Predictions}: Daily predictions provided a broader view of office occupancy trends, allowing for better scheduling, event planning, and overall space management. +\end{itemize} + +The combination of accurate predictions and detailed reporting helped stakeholders +improve decision-making processes related to office capacity management, +ensuring that both resources and spaces were optimally used. The predictions were visualized through graphs and dashboards, +offering a clear and accessible view of expected attendance for any given time period. + +\begin{figure}[htbp] + \centering + \includegraphics[width=0.9\textwidth]{Reports.png} +\end{figure} + +\section*{MongoDB for User Statistics Processing} +\addcontentsline{toc}{section}{MongoDB for User Statistics Processing} + +MongoDB played a critical role in processing user statistics by leveraging its powerful aggregation framework and triggers. +The user statistics data was collected and stored in MongoDB, +allowing us to generate real-time insights regarding work patterns, arrival and departure times, daily hours worked, and peak office hours. + +\subsection*{Data Aggregation} +MongoDB's aggregation pipeline enabled us to efficiently process large volumes of data. Using the pipeline, we could: +\begin{itemize} + \item \textbf{Calculate In-Office Rate}: The \texttt{CalculateInOfficeRate} function used the aggregation pipeline to compute the work ratio for each day of the week. + By grouping the data by weekday and calculating the in-office rate using the difference between entry and exit times, we could measure the attendance ratio. + + \item \textbf{Analyze Arrival and Departure Times}: The \texttt{AverageArrivalAndDepartureTimesByWeekday} function derived the average arrival and departure times using MongoDB’s + aggregation operators like \texttt{\$avg}. The \texttt{\$group} stage was used to calculate averages for each weekday, providing insights into workday start and end times. + + \item \textbf{Identify Peak Office Hours}: The \texttt{BusiestHoursByWeekday} function identified the top 3 busiest hours for each weekday by analyzing the + number of hours spent by users in the office. The data was grouped by weekday and hour to find peak attendance times, allowing managers to optimize resources. +\end{itemize} + +\begin{figure}[htbp] + \centering + \includegraphics[width=0.9\textwidth]{User_Reports.png} +\end{figure} + +\subsection*{Real-Time Triggers} +In addition to the aggregation framework, MongoDB triggers were employed to ensure that user statistics were continuously updated in real-time. +This allowed us to capture live data as it was added to the database, automatically processing new entries without manual intervention. Triggers were used to: +\begin{itemize} + \item \textbf{Update Daily Hours}: When new attendance data was added, triggers automatically calculated and updated the total hours worked for that day. + This ensured that reports were always up-to-date and reflected the latest available data. + + \item \textbf{Real-Time User Statistics}: Triggers calculated the total hours worked by each user based on their check-in and check-out times. + The calculated data was updated in real-time, providing immediate insights into user behavior. +\end{itemize} + +By utilizing MongoDB's real-time processing capabilities and data aggregation pipelines, we were able to generate comprehensive reports and insights for user behavior. +The statistics presented, such as the daily work ratios, arrival and departure times, and peak office hours, +were all derived from the data stored in MongoDB and processed using these advanced features. + +\pagebreak + +\section*{Conclusion} +The integration of CNN and LSTM models, combined with extensive preprocessing, feature engineering, +and real-time data collection using MongoDB functions and triggers, enabled us to build a highly effective system for predicting office attendance. +Through continuous training and model refinement, we successfully achieved the project’s goal of delivering accurate, actionable insights. + +\end{document} diff --git a/documentation/occupi-docs/Awards/Mockaroo.png b/documentation/occupi-docs/Awards/Mockaroo.png new file mode 100644 index 00000000..53dda15b Binary files /dev/null and b/documentation/occupi-docs/Awards/Mockaroo.png differ diff --git a/documentation/occupi-docs/Awards/Reports.png b/documentation/occupi-docs/Awards/Reports.png new file mode 100644 index 00000000..83c69b4c Binary files /dev/null and b/documentation/occupi-docs/Awards/Reports.png differ diff --git a/documentation/occupi-docs/Awards/Training.png b/documentation/occupi-docs/Awards/Training.png new file mode 100644 index 00000000..d595c13b Binary files /dev/null and b/documentation/occupi-docs/Awards/Training.png differ diff --git a/documentation/occupi-docs/Awards/User_Reports.png b/documentation/occupi-docs/Awards/User_Reports.png new file mode 100644 index 00000000..4b1d7f63 Binary files /dev/null and b/documentation/occupi-docs/Awards/User_Reports.png differ diff --git a/documentation/Coding Standards/logo-white.png b/documentation/occupi-docs/Awards/logo-white.png similarity index 100% rename from documentation/Coding Standards/logo-white.png rename to documentation/occupi-docs/Awards/logo-white.png diff --git a/frontend/occupi-web/src/AuthService.ts b/frontend/occupi-web/src/AuthService.ts index 679963fd..9a6d0b93 100644 --- a/frontend/occupi-web/src/AuthService.ts +++ b/frontend/occupi-web/src/AuthService.ts @@ -373,7 +373,7 @@ const AuthService = { const response = await axios.get(`/ping-admin`, { withCredentials: true, }); - return response.data; + return response; } catch (error) { if (axios.isAxiosError(error) && error.response?.data) { throw error.response.data; diff --git a/frontend/occupi-web/src/components/OverviewComponent/OverviewComponent.tsx b/frontend/occupi-web/src/components/OverviewComponent/OverviewComponent.tsx index 12163c1a..f50fa255 100644 --- a/frontend/occupi-web/src/components/OverviewComponent/OverviewComponent.tsx +++ b/frontend/occupi-web/src/components/OverviewComponent/OverviewComponent.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; import { Uptrend, Cal, DownTrend, Bf } from "@assets/index"; import { BarGraph, @@ -20,12 +20,15 @@ const OverviewComponent = () => { useEffect(() => { const fetchTotalBookings = async () => { try { - const response = await fetch('/analytics/top-bookings'); + const response = await fetch("/analytics/top-bookings"); const data = await response.json(); - const total = data.data.reduce((sum: number, booking: { count: number; }) => sum + booking.count, 0); + const total = data.data.reduce( + (sum: number, booking: { count: number }) => sum + booking.count, + 0 + ); setTotalBookings(total); } catch (err) { - console.error('Failed to fetch total bookings:', err); + console.error("Failed to fetch total bookings:", err); } }; @@ -33,7 +36,7 @@ const OverviewComponent = () => { }, []); const handleNavigateToBookingsDashboard = () => { - navigate('/booking-statistics/bookings-dashboard'); + navigate("/booking-statistics/bookings-dashboard"); }; return ( @@ -51,29 +54,27 @@ const OverviewComponent = () => { } /> - } - title="Total bookings" - count={`${totalBookings} bookings`} + icon={Building} + title="Current Attendance" + count={`${counter} people`} trend={{ - icon: , - value: "2.8%", - direction: "up", + icon: , + value: "4.3%", + direction: "down", }} - comparisonText="Up from last period" + comparisonText="Down from yesterday" onClick={handleNavigateToBookingsDashboard} /> - - Prediction Comparison + onClick={handleNavigateToBookingsDashboard}> + Prediction Comparison
@@ -90,21 +91,20 @@ const OverviewComponent = () => { } - title="Total visitations today" - count={`${counter} people`} + icon={Calendar} + title="Total bookings" + count={`${totalBookings} bookings`} trend={{ - icon: , - value: "4.3%", - direction: "down", + icon: , + value: "2.8%", + direction: "up", }} - comparisonText="Down from yesterday" + comparisonText="Up from last period" onClick={handleNavigateToBookingsDashboard} - />
); }; -export default OverviewComponent; \ No newline at end of file +export default OverviewComponent; diff --git a/frontend/occupi-web/src/components/aiDashboard/aiDashCard/AiDashCard.tsx b/frontend/occupi-web/src/components/aiDashboard/aiDashCard/AiDashCard.tsx index c045db38..033a6ef0 100644 --- a/frontend/occupi-web/src/components/aiDashboard/aiDashCard/AiDashCard.tsx +++ b/frontend/occupi-web/src/components/aiDashboard/aiDashCard/AiDashCard.tsx @@ -27,13 +27,13 @@ const AiDashCard: React.FC = ({ }) => { return (
-
+
{icon}

@@ -43,13 +43,13 @@ const AiDashCard: React.FC = ({

= ({

{

) : null; })} + { + results.length === 0 && searchQuery.length > 0 && ( +
+

No results found

+
+ ) + }
diff --git a/frontend/occupi-web/src/components/globalSearch/SearchContent.ts b/frontend/occupi-web/src/components/globalSearch/SearchContent.ts index a8699233..4de267c7 100644 --- a/frontend/occupi-web/src/components/globalSearch/SearchContent.ts +++ b/frontend/occupi-web/src/components/globalSearch/SearchContent.ts @@ -15,5 +15,73 @@ export const pages = [ { id: 15, path: "/employees", title: "Employees", description: "View employee details and employee statistics on an individualised level" }, { id: 16, path: "/booking-statistics/overview", title: "Bookings Overview", description: "View overview statistics on bookings" }, { id: 17, path: "/booking-statistics/bookings-dashboard", title: "Bookings Dashboard", description: "View deeper statistics on bookings" }, - { id: 18, path: "/user-locations", title: "User login locations", description: "Manage users allowed login locations, see their ip addresses for these allowed locations"} + { id: 18, path: "/user-locations", title: "User login locations", description: "Manage users allowed login locations, see their ip addresses for these allowed locations"}, + { id: 19, path: "/", title: "At a glance", description: "View the overview of the system info at a glance" }, + { id: 20, path: "/employees", title: "Admin", description: "Change admin status of employees" }, + { id: 21, path: "/employees", title: "Employee Statistics", description: "View employee statistics" }, + { id: 22, path: "/employees", title: "Site status", description: "View employee on site and offsite status" }, + { id: 23, path: "/employees", title: "Mail employee", description: "Send a personalised email to your employee" }, + { id: 24, path: "/employees", title: "Employee Hours", description: "View users total on site hours" }, + { id: 25, path: "/employees", title: "Employee average arrival and departure", description: "View users average arrival and departure times" }, + { id: 26, path: "/employees", title: "Employee average on site time", description: "View users average on site time" }, + { id: 27, path: "/employees", title: "Employee peak hours time", description: "View users peak hours on site" }, + { id: 28, path: "/employees", title: "Generate report", description: "Generate a report on employee statistics" }, + { id: 29, path: "/employees", title: "Search for employees", description: "View employee check-ins" }, + { id: 30, path: "/booking-statistics/bookings-dashboard", title: "Top 3 bookings", description: "View top 3 bookings in the system" }, + { id: 31, path: "/booking-statistics/bookings-dashboard", title: "Current bookings", description: "View current bookings in the system" }, + { id: 32, path: "/booking-statistics/bookings-dashboard", title: "Booking statistics", description: "View booking statistics" }, + { id: 33, path: "/booking-statistics/bookings-dashboard", title: "Booking history", description: "View historical data on bookings"}, + { id: 34, path: "/worker-dashboard", title: "most active workers", description: "View the most active workers in the system" }, + { id: 35, path: "/worker-dashboard", title: "least active workers", description: "View the least active workers in the system" }, + { id: 36, path: "/worker-dashboard", title: "peak hours", description: "View the peak hours of workers in the system" }, + { id: 37, path: "/worker-dashboard", title: "average on site time", description: "View the average on site time of workers in the system" }, + { id: 38, path: "/worker-dashboard", title: "average arrival and departure", description: "View the average arrival and departure times of workers in the system" }, + { id: 39, path: "/ai-dashboard", title: "office occupancy", description: "View the office occupancy in the system" }, + { id: 40, path: "/ai-dashboard", title: "available desks", description: "View the available desks in the system" }, + { id: 41, path: "/ai-dashboard", title: "bookings", description: "View the bookings in the system" }, + { id: 42, path: "/ai-dashboard", title: "check-ins today", description: "View the check-ins today in the system" }, + { id: 43, path: "/ai-dashboard", title: "predicted capacity levels", description: "View the predicted capacity levels in the system" }, + { id: 44, path: "/ai-dashboard", title: "get reccomendations", description: "Get reccomendations on which days to come into the office" }, + { id: 45, path: "/rooms", title: "add room", description: "Add a room to the system" }, + { id: 46, path: "/rooms", title: "edit room", description: "Edit a room in the system" }, + { id: 47, path: "/rooms", title: "delete room", description: "Delete a room in the system" }, + { id: 48, path: "/rooms", title: "view rooms", description: "View all rooms in the system" }, + { id: 49, path: "/rooms", title: "search rooms", description: "Search for a room in the system" }, + { id: 50, path: "/reports", title: "generate report", description: "Generate a report in the system" }, + { id: 51, path: "/reports", title: "download report", description: "Download a report in the system" }, + { id: 52, path: "/reports", title: "view report", description: "View a report in the system" }, + { id: 53, path: "/reports", title: "search report", description: "Search for a report in the system" }, + { id: 54, path: "/faq", title: "search faq", description: "Search for a faq in the system" }, + { id: 55, path: "/settings/profile", title: "update profile", description: "Update your profile in the system" }, + { id: 56, path: "/settings/profile", title: "change password", description: "Change your password in the system" }, + { id: 57, path: "/settings/profile", title: "update email", description: "Update your email in the system" }, + { id: 58, path: "/settings/profile", title: "update phone number", description: "Update your phone number in the system" }, + { id: 59, path: "/settings/profile", title: "update address", description: "Update your address in the system" }, + { id: 60, path: "/settings/profile", title: "update name", description: "Update your name in the system" }, + { id: 61, path: "/settings/profile", title: "update profile picture", description: "Update your profile picture in the system" }, + { id: 62, path: "/settings/appearance", title: "update theme", description: "Update your theme in the system" }, + { id: 63, path: "/rooms", title: "change room image", description: "Change the image of a room in the system" }, + { id: 64, path: "/user-locations", title: "add location", description: "Add a location to the system" }, + { id: 65, path: "/user-locations", title: "edit location", description: "Edit a location in the system" }, + { id: 66, path: "/user-locations", title: "delete location", description: "Delete a location in the system" }, + { id: 67, path: "/user-locations", title: "blacklist", description: "Blacklist a location in the system" }, + { id: 68, path: "/user-locations", title: "whitelist", description: "Whitelist a location in the system" }, + //{ id: 69, path: "/user-locations", title: "Google maps", description: "View the location on google maps and locate a specific place" }, + { id: 69, path: "/faq", title: "search faq", description: "Search for a faq in the system" }, + { id: 70, path: "/settings/notifications", title: "update notifications", description: "Update your notifications in the system" }, + { id: 71, path: "/settings/security", title: "update security", description: "Update your security in the system" }, + { id: 72, path: "/settings/about", title: "view about", description: "View the about page in the system" }, + { id: 73, path: "/settings/about", title: "view privacy policy", description: "View the privacy policy in the system" }, + { id: 74, path: "/settings/about", title: "view TOS", description: "View the TOS in the system" }, + { id: 75, path: "/settings/about", title: "view user manual", description: "View the user manual in the system" }, + { id: 76, path: "/booking-statistics", title: "generate report", description: "Generate a report in the system" }, + { id: 77, path: "/booking-statistics", title: "download report", description: "Download a report in the system" }, + { id: 78, path: "/booking-statistics", title: "view report", description: "View a report in the system" }, + { id: 79, path: "/booking-statistics", title: "search report", description: "Search for a report in the system" }, + { id: 80, path: "/worker-dashboard", title: "generate report", description: "Generate a report in the system" }, + { id: 81, path: "/worker-dashboard", title: "download report", description: "Download a report in the system" }, + { id: 82, path: "/worker-dashboard", title: "view report", description: "View a report in the system" }, + { id: 83, path: "/worker-dashboard", title: "search report", description: "Search for a report in the system" }, + { id: 84, path: "/employees", title: "generate report", description: "Generate a report in the system" }, + { id: 85, path: "/employees", title: "download report", description: "Download a report in the system" }, ]; \ No newline at end of file diff --git a/frontend/occupi-web/src/components/graphContainer/GraphContainer.test.tsx b/frontend/occupi-web/src/components/graphContainer/GraphContainer.test.tsx index 8851e16f..e43ba23c 100644 --- a/frontend/occupi-web/src/components/graphContainer/GraphContainer.test.tsx +++ b/frontend/occupi-web/src/components/graphContainer/GraphContainer.test.tsx @@ -41,7 +41,7 @@ afterEach(() => { expect(containers[1].className).toContain("card border-2"); expect(containers[1].className).toContain("border-tertiary"); expect(containers[1].className).toContain("rounded-[20px]"); - expect(containers[1].className).toContain("bg-secondary"); + // expect(containers[1].className).toContain("bg-secondary"); // expect(containers[1].className).toContain("shadow-2xl"); }); diff --git a/frontend/occupi-web/src/components/graphContainer/GraphContainer.tsx b/frontend/occupi-web/src/components/graphContainer/GraphContainer.tsx index 1e7d7301..bf26f063 100644 --- a/frontend/occupi-web/src/components/graphContainer/GraphContainer.tsx +++ b/frontend/occupi-web/src/components/graphContainer/GraphContainer.tsx @@ -9,7 +9,7 @@ interface GraphContainerProps { const GraphContainer: React.FC = ({ width = '24.531vw', height = '13.49vw' ,mainComponent}) => { return (
-
{mainComponent} +
{mainComponent}
); diff --git a/frontend/occupi-web/src/components/protectedRoutes/ProtectedRoutes.tsx b/frontend/occupi-web/src/components/protectedRoutes/ProtectedRoutes.tsx index dff9923b..d7a41209 100644 --- a/frontend/occupi-web/src/components/protectedRoutes/ProtectedRoutes.tsx +++ b/frontend/occupi-web/src/components/protectedRoutes/ProtectedRoutes.tsx @@ -16,7 +16,7 @@ const ProtectedRoute = (props: ProtectedRouteProps) => { const checkAuthentication = async () => { if(userDetails === null){ const res = await AuthService.pingAdmin(); - if (res.status === 200 && res.message === "pong -> I am alive and kicking and you are an admin") { + if (res.status === 200) { const userDeets = await AuthService.getUserDetails(); setUserDetails(userDeets); } else { diff --git a/frontend/occupi-web/src/components/sideNavComponent/SideNav.tsx b/frontend/occupi-web/src/components/sideNavComponent/SideNav.tsx index 46934a8e..793ac3ae 100644 --- a/frontend/occupi-web/src/components/sideNavComponent/SideNav.tsx +++ b/frontend/occupi-web/src/components/sideNavComponent/SideNav.tsx @@ -41,7 +41,7 @@ const sidebarcontent = [ }, { icon: Worker, - text: "Worker Dashboard", + text: "Company Statistics", }, { icon: PieChart, @@ -90,7 +90,7 @@ const SideNav = () => { else if (arg === "Reports") navigate("/reports"); else if (arg === "Help") navigate("/faq"); else if (arg === "Booking Statistics") navigate("/booking-statistics/overview"); - else if (arg === "Worker Dashboard") navigate("/worker-dashboard"); + else if (arg === "Company Statistics") navigate("/worker-dashboard"); else if (arg === "Employees") navigate("/employees"); else if (arg === "IP addresses") navigate("/user-locations"); else; @@ -110,7 +110,7 @@ const SideNav = () => { else if (pn.startsWith("/reports")) setSelectedPanel("Reports"); else if (pn.startsWith("/faq")) setSelectedPanel("Help"); else if (pn.startsWith("/booking-statistics")) setSelectedPanel("Booking Statistics"); - else if (pn.startsWith("/worker-dashboard")) setSelectedPanel("Worker Dashboard"); + else if (pn.startsWith("/worker-dashboard")) setSelectedPanel("Company Statistics"); else if (pn.startsWith("/employees")) setSelectedPanel("Employees"); else if (pn.startsWith("/user-locations")) setSelectedPanel("IP addresses"); else setSelectedPanel(""); diff --git a/frontend/occupi-web/src/components/workerStats/LeastActiveEmployeeCard.tsx b/frontend/occupi-web/src/components/workerStats/LeastActiveEmployeeCard.tsx index acbe90c5..93a2033a 100644 --- a/frontend/occupi-web/src/components/workerStats/LeastActiveEmployeeCard.tsx +++ b/frontend/occupi-web/src/components/workerStats/LeastActiveEmployeeCard.tsx @@ -99,7 +99,7 @@ const LeastActiveEmployeeCard = () => {
- Activity Level + Efficiency { { id: "card1", title: "Office Occupancy", - icon: , + icon: , stat: workRatio ? `${(workRatio * 10).toFixed(2)}%` : "Loading...", trend: 3.46, }, { id: "card2", title: "Available Space", - icon: , + icon: , stat: totalMaxCapacity && totalBookings ? `${totalBookings}/${totalMaxCapacity}` : "Loading...", trend: -2.1, @@ -150,14 +150,14 @@ const AiDashboard: React.FC = () => { { id: "card3", title: "Bookings", - icon: , + icon: , stat: currentBookings !== null ? `${currentBookings}` : "Loading...", trend: 8.7, }, { id: "card4", title: "Check-ins Today", - icon: , + icon: , stat: `${counter}`, trend: 3.4, }, @@ -340,21 +340,21 @@ const AiDashboard: React.FC = () => {
) )} -
+
-
+
diff --git a/frontend/occupi-web/src/pages/reports/PDFReport.tsx b/frontend/occupi-web/src/pages/reports/PDFReport.tsx index 40d0706f..9de9e4b9 100644 --- a/frontend/occupi-web/src/pages/reports/PDFReport.tsx +++ b/frontend/occupi-web/src/pages/reports/PDFReport.tsx @@ -12,7 +12,6 @@ import { TopNav } from "@components/index"; import { occupiLogo } from "@assets/index"; import axios from "axios"; -// Define the interface for the data interface CapacityData { date: string; day: string; @@ -32,7 +31,6 @@ interface ResponseItem { Special_Event: number; } -// Sample data const occupancyData = [ { month: "January", occupancy: 60 }, { month: "February", occupancy: 70 }, @@ -48,7 +46,6 @@ const occupancyData = [ { month: "December", occupancy: 80 }, ]; -// Create styles const styles = StyleSheet.create({ page: { flexDirection: "column", @@ -71,8 +68,18 @@ const styles = StyleSheet.create({ textAlign: "right", textTransform: "uppercase", }, + sectionTitle: { + fontSize: 16, + fontWeight: "bold", + marginBottom: 10, + color: "#333333", + paddingBottom: 5, + borderBottom: "1 solid #CCCCCC", + }, section: { - marginVertical: 10, + marginVertical: 15, + padding: 10, + backgroundColor: "#FAFAFA", }, paragraph: { marginVertical: 10, @@ -112,28 +119,27 @@ const styles = StyleSheet.create({ textAlign: "center", color: "grey", }, - chartContainer: { - marginVertical: 20, - width: "100%", - height: 200, - backgroundColor: "#E5E5E5", - justifyContent: "center", - alignItems: "center", - textAlign: "center", + bulletPoint: { + marginLeft: 10, + fontSize: 12, }, }); -// summary data const summaryText = `This report provides an in-depth analysis of the office occupancy trends over highlighting key areas for improvement and optimization based on AI-driven predictions.`; + function BasicDocument() { -// Mock additional data for the report -const [additionalData, setAdditionalData] = useState([ - { category: "Total Floors", value: 0 }, - { category: "Total Meeting Rooms", value: 0 }, - { category: "Average Desk Utilization", value: "0%" }, -]); + const [searchQuery, setSearchQuery] = useState(""); + const [capacityData, setCapacityData] = useState([]); + const [, setLoading] = useState(true); + const [, setError] = useState(null); + const [selectedMonths, setSelectedMonths] = useState([]); + const [additionalData, setAdditionalData] = useState([ + { category: "Total Floors", value: 0 }, + { category: "Total Meeting Rooms", value: 0 }, + { category: "Average Desk Utilization", value: "0%" }, + ]); -useEffect(() => { + useEffect(() => { const fetchData = async () => { try { const [roomsResponse, bookingsResponse] = await Promise.all([ @@ -148,18 +154,10 @@ useEffect(() => { const roomsData = roomsResponse.data.data; const bookingsData = bookingsResponse.data.data; - if (!Array.isArray(roomsData) || !Array.isArray(bookingsData)) { - throw new Error("Data is not in the expected format"); - } - - // Calculate total floors - const totalFloors = new Set(roomsData.map(room => room.floorNo)).size; - - // Calculate total meeting rooms + const totalFloors = new Set(roomsData.map((room: { floorNo: number; }) => room.floorNo)).size; const totalMeetingRooms = roomsData.length; - - const totalOccupancy = bookingsData.reduce((sum, booking) => sum + booking.count, 0); - const totalCapacity = roomsData.reduce((sum, room) => sum + (room.maxOccupancy || 0), 0); + const totalOccupancy = bookingsData.reduce((sum: number, booking: { count: number; }) => sum + booking.count, 0); + const totalCapacity = roomsData.reduce((sum: number, room: { maxOccupancy: number; }) => sum + (room.maxOccupancy || 0), 0); const averageUtilization = totalCapacity > 0 ? (totalOccupancy / totalCapacity) * 100 : 0; setAdditionalData([ @@ -176,26 +174,10 @@ useEffect(() => { fetchData(); }, []); -// Create Document Component - - const [searchQuery, setSearchQuery] = useState(""); - const [capacityData, setCapacityData] = useState([]); - const [, setLoading] = useState(true); - const [, setError] = useState(null); - const [selectedMonths, setSelectedMonths] = useState([]); - - const handleInputChange = (e: { - target: { value: React.SetStateAction }; - }) => { - setSearchQuery(e.target.value); - }; - useEffect(() => { - const fetchData = async () => { + const fetchCapacityData = async () => { try { - const response = await axios.get( - "https://ai.occupi.tech/predict_week" - ); + const response = await axios.get("https://ai.occupi.tech/predict_week"); const formattedData = response.data.map((item: ResponseItem) => ({ date: item.Date, day: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][item.Day_of_Week], @@ -211,18 +193,20 @@ useEffect(() => { } }; - fetchData(); + fetchCapacityData(); }, []); + const handleInputChange = (e: { target: { value: React.SetStateAction }}) => { + setSearchQuery(e.target.value); + }; + const handleMonthSelection = (month: string) => { - setSelectedMonths((prevMonths) => - prevMonths.includes(month) - ? prevMonths.filter((m) => m !== month) - : [...prevMonths, month] + setSelectedMonths(prev => + prev.includes(month) ? prev.filter(m => m !== month) : [...prev, month] ); }; - const filteredOccupancyData = occupancyData.filter((data) => + const filteredOccupancyData = occupancyData.filter(data => selectedMonths.includes(data.month) ); @@ -232,7 +216,7 @@ useEffect(() => { mainComponent={
Reports - + Generate and download reports for Analysis
@@ -241,7 +225,6 @@ useEffect(() => { onChange={handleInputChange} /> - {/* Month Filter */}
{occupancyData.map((data) => (
} diff --git a/hourly_scaler.pkl b/hourly_scaler.pkl new file mode 100644 index 00000000..9834a1a4 Binary files /dev/null and b/hourly_scaler.pkl differ diff --git a/maestro/LSTM(1).py b/maestro/LSTM(1).py index cec6b0b5..6f52a4eb 100644 --- a/maestro/LSTM(1).py +++ b/maestro/LSTM(1).py @@ -215,10 +215,10 @@ def predict_weekly_attendance(month, start_day): plt.title('Predicted Attendance Levels for the Week') plt.show() -# Save the model in the SavedModel format -tf.saved_model.save(model, 'attendance_model/1') -# model.export('C:/Users/retha/Capstone/occupi/models/attendance_model/1') +# # Save the model in the SavedModel format +# tf.saved_model.save(model, 'attendance_model/1') +# # model.export('C:/Users/retha/Capstone/occupi/models/attendance_model/1') -new_model = tf.keras.models.load_model('C:/Users/retha/Capstone/occupi/attendance_model.keras') -new_model.summary() -tf.saved_model.save(new_model, 'serving/') # Save the model in the SavedModel format +# new_model = tf.keras.models.load_model('C:/Users/retha/Capstone/occupi/attendance_model.keras') +# new_model.summary() +# tf.saved_model.save(new_model, 'serving/') # Save the model in the SavedModel format