diff --git a/README.md b/README.md
index a34b26c..d98e951 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,16 @@
# AdvancedSoftwareEngineering-Group3
-Download Expo Go on the playstore or appstore
+## Run through the Expo
-See instructions within `client/README.md` and `server/README.md` for more information on how to run the project.
\ No newline at end of file
+Download Expo Go on the playstore or appstore then follow the instructions within `client/README.md` and `server/README.md` for more information on how to run the project.
+
+## Package and deployment instructions
+- To package the client, it must first be prebuilt using the command `npx expo prebuild`.
+- Next, the client can be packaged using the command `eas build -p android --profile preview`.
+- This will present a QR code and link, using which you can download and install the app to your device.
+- The process is essentially the same for IOS but for it to work, an Apple Developer Account is required. We did not explore this route as it would have been an additional cost. We did however ensure that all functionality was cross platform.
+
+## Notes
+- Running the server locally will not be possible as you will not be able to connect to the Database without our input.
+- After the demo our servers on Digital Ocean will be taken down as it is a payed service. All functionality will be displayed in the demo.
+- An EAS account is required to packageĀ theĀ app.
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
index e561994..6c761fe 100644
--- a/client/README.md
+++ b/client/README.md
@@ -34,6 +34,8 @@ npx expo start
**Here's an example: `EXPO_PUBLIC_API_URL=http://192.168.13.1:8000`**
+If you are running the server on the Digital Ocean Droplet, the above instructions stand, but the IP address should be that of the droplet: `134.199.188.243`
+
## Linting and Formatting Scripts
This project uses ESLint and Prettier to maintain code quality and consistency. Below are the scripts defined in the `package.json` for linting and formatting:
diff --git a/client/src/DisplayRoute.js b/client/src/DisplayRoute.js
index 60d89f0..c719d75 100644
--- a/client/src/DisplayRoute.js
+++ b/client/src/DisplayRoute.js
@@ -100,7 +100,10 @@ export default function DisplayRouteScreen({ navigation, route }) {
}, [currentInstruction]);
function removeHtmlTags(instruction) {
- return instruction.replace(/<\/?[^>]+(>|$)/g, '');
+ if (instruction) {
+ return instruction.replace(/<\/?[^>]+(>|$)/g, '');
+ }
+ return instruction;
}
// Creating a dictionary of step data for each step in the route
@@ -149,7 +152,7 @@ export default function DisplayRouteScreen({ navigation, route }) {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/check_incidents`);
diff --git a/client/src/FindRoute.js b/client/src/FindRoute.js
index 61c4ca2..173dc82 100644
--- a/client/src/FindRoute.js
+++ b/client/src/FindRoute.js
@@ -67,7 +67,7 @@ export default function FindRouteScreen({ navigation }) {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(
`Sending request to ${baseUrl}/wayfinding/preferences/get_routes`,
diff --git a/client/src/FriendsScreen.js b/client/src/FriendsScreen.js
index e5a9f4b..20a1843 100644
--- a/client/src/FriendsScreen.js
+++ b/client/src/FriendsScreen.js
@@ -97,7 +97,7 @@ export default function FriendsScreen() {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/send_request`);
@@ -133,7 +133,7 @@ export default function FriendsScreen() {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(
`Sending request to ${baseUrl}/check_requests?sender=${senderName}`,
@@ -200,7 +200,7 @@ export default function FriendsScreen() {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/request_response`);
@@ -245,7 +245,7 @@ export default function FriendsScreen() {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/cancel_friend_request`);
@@ -287,7 +287,7 @@ export default function FriendsScreen() {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/remove_friend`);
@@ -373,31 +373,31 @@ export default function FriendsScreen() {
/>
- {/* Pending Friends List */}
-
- Pending Friend Requests
- index.toString()}
- renderItem={({ item }) => (
-
- {item}
- processFriendRequest(item, true)}
- style={styles.acceptButton}
- >
- Accept
-
- processFriendRequest(item, false)}
- style={styles.rejectButton}
- >
- Reject
-
-
- )}
- />
-
+ {/* Pending Friends List */}
+
+ Pending Friend Requests
+ index.toString()}
+ renderItem={({ item }) => (
+
+ {item}
+ processFriendRequest(item, true)}
+ style={styles.acceptButton}
+ >
+ Accept
+
+ processFriendRequest(item, false)}
+ style={styles.rejectButton}
+ >
+ Reject
+
+
+ )}
+ />
+
{/* Current Friends List */}
diff --git a/client/src/Map.js b/client/src/Map.js
index 5225edc..0b5056a 100644
--- a/client/src/Map.js
+++ b/client/src/Map.js
@@ -37,7 +37,7 @@ export default function MapScreen({ navigation }) {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/check_incidents`);
@@ -83,19 +83,19 @@ export default function MapScreen({ navigation }) {
}
};
- const fetchWeather = async () => {
+ const fetchWeather = async (initialLocation) => {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(
- `Sending request to ${baseUrl}/weather?longitude=${location.longitude}&latitude=${location.latitude}`,
+ `Sending request to ${baseUrl}/weather?longitude=${initialLocation.longitude}&latitude=${initialLocation.latitude}`,
);
const response = await fetch(
- `${baseUrl}/weather?longitude=${location.longitude}&latitude=${location.latitude}`,
+ `${baseUrl}/weather?longitude=${initialLocation.longitude}&latitude=${initialLocation.latitude}`,
{
method: 'GET',
headers: {
@@ -123,19 +123,19 @@ export default function MapScreen({ navigation }) {
}
};
- const fetchBikeApi = async () => {
+ const fetchBikeApi = async (initialLocation) => {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(
- `Sending request to ${baseUrl}/BikeStand?longitude=${location.longitude}&latitude=${location.latitude}`,
+ `Sending request to ${baseUrl}/BikeStand?longitude=${initialLocation.longitude}&latitude=${initialLocation.latitude}`,
);
const response = await fetch(
- `${baseUrl}/BikeStand?longitude=${location.longitude}&latitude=${location.latitude}`,
+ `${baseUrl}/BikeStand?longitude=${initialLocation.longitude}&latitude=${initialLocation.latitude}`,
{
method: 'GET',
headers: {
@@ -169,6 +169,11 @@ export default function MapScreen({ navigation }) {
try {
const initialLocation = await getCurrentLocation();
setLocation(initialLocation);
+ // weatherBikePollIncident(initialLocation);
+
+ fetchWeather(initialLocation);
+ fetchBikeApi(initialLocation);
+ pollIncident();
const locationSubscription = await startLocationTracking(setLocation);
@@ -181,19 +186,6 @@ export default function MapScreen({ navigation }) {
fetchLocation();
}, []);
- useEffect(() => {
- const pollOnce = async () => {
- if (location) {
- fetchWeather();
- fetchBikeApi();
- pollIncident();
- }
- };
-
- pollOnce();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [location]); // Empty dependency array ensures this runs only once when the component mounts
-
const renderContent = () => {
if (errorMessage) {
return {errorMessage};
diff --git a/client/src/Preferences.js b/client/src/Preferences.js
index ba3cce1..7822231 100644
--- a/client/src/Preferences.js
+++ b/client/src/Preferences.js
@@ -47,7 +47,7 @@ export default function PreferencesScreen({ navigation }) {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(`Sending request to ${baseUrl}/setPreferences`);
diff --git a/client/src/SelectRoute.js b/client/src/SelectRoute.js
index 59f4ac1..dcddc0a 100644
--- a/client/src/SelectRoute.js
+++ b/client/src/SelectRoute.js
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
-import { View, Text, TouchableOpacity } from 'react-native';
+import { View, Text, TouchableOpacity, Platform } from 'react-native';
import MapView, { Polyline, Marker } from 'react-native-maps';
import {
decodeRoute,
@@ -8,6 +8,7 @@ import {
} from './utils/mapUtils';
import locationCircleIcon from './assets/location-circle.png';
import selectRouteStyles from './components/styles/SelectRoute.styles';
+import { retrieveData } from './caching';
export default function SelectRouteScreen({ navigation, route }) {
const { origin, destination, routeData } = route.params;
@@ -16,6 +17,7 @@ export default function SelectRouteScreen({ navigation, route }) {
const [errorMessage, setErrorMessage] = useState('');
const [polylineCoordinates, setPolylineCoordinates] = useState([]);
const [currentRoute, setCurrentRoute] = useState(routeData.routes[0]);
+ const [scores, setScores] = useState([]);
// Get current location
useEffect(() => {
@@ -54,6 +56,86 @@ export default function SelectRouteScreen({ navigation, route }) {
setPolylineCoordinates(decodedPath);
};
+ const sendSustainabilityScore = async (
+ startRouteFlag,
+ sustainabilityScore,
+ // eslint-disable-next-line consistent-return
+ ) => {
+ try {
+ const baseUrl =
+ Platform.OS === 'web'
+ ? 'http://localhost'
+ : process.env.EXPO_PUBLIC_API_URL;
+ console.log(`Sending request to ${baseUrl}/update_sus_stats`);
+
+ const response = await fetch(`${baseUrl}/update_sus_stats`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ username: await retrieveData('username'),
+ flag: startRouteFlag,
+ modeDistances: sustainabilityScore,
+ }),
+ });
+
+ const data = await response.json();
+ console.log('response (transport score): ', data);
+ return data;
+ } catch (error) {
+ console.log('Error sending sustainability scores and distances:', error);
+ }
+ };
+
+ const getSustainabilityScore = async (index, startRouteFlag) => {
+ const sustainabilityScore = {
+ Bus: 0,
+ Train: 0,
+ WALKING: 0,
+ Tram: 0,
+ DRIVING: 0,
+ BICYCLING: 0,
+ };
+ routeData.routes[index].legs[0].steps.forEach((step) => {
+ let key = '';
+ if (step.travel_mode === 'TRANSIT') {
+ // eslint-disable-next-line prefer-destructuring
+ key = step.html_instructions.trim().split(' ')[0];
+ console.log('key: ', key);
+ } else {
+ key = step.travel_mode;
+ }
+ sustainabilityScore[key] += step.distance.value / 1000;
+ });
+ console.log('sustainability score: ', sustainabilityScore);
+ const transportScore = await sendSustainabilityScore(
+ startRouteFlag,
+ sustainabilityScore,
+ );
+ return transportScore;
+ };
+
+ useEffect(() => {
+ for (let i = 0; i < routeData.routes.length; i += 1) {
+ setScores((prevScores) => [
+ ...prevScores,
+ getSustainabilityScore(i, false),
+ ]);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const startJourney = (index, routeOption) => {
+ getSustainabilityScore(index, true);
+ navigation.navigate('DisplayRouteScreen', {
+ origin,
+ destination,
+ routeData: routeOption,
+ polylineCoordinates,
+ });
+ };
+
return (
{errorMessage && (
@@ -110,17 +192,13 @@ export default function SelectRouteScreen({ navigation, route }) {
Route {index + 1}
Distance: {routeOption.legs[0].distance.text}
Duration: {routeOption.legs[0].duration.text}
+ Sustainability score: {scores[index]}
{/* routeData needs to rename the route variable because that is what react navigator calls its properties */}
- navigation.navigate('DisplayRouteScreen', {
- origin,
- destination,
- routeData: routeOption,
- polylineCoordinates,
- })
- }
+ onPress={() => {
+ startJourney(index, routeOption);
+ }}
style={selectRouteStyles.startButton}
>
Start Journey
diff --git a/client/src/SustainabilityDashboard.js b/client/src/SustainabilityDashboard.js
index 3e8c825..c4ca42f 100644
--- a/client/src/SustainabilityDashboard.js
+++ b/client/src/SustainabilityDashboard.js
@@ -28,6 +28,12 @@ import bronzeBus from './assets/SustainablityDashboard/BusBronze.png';
import goldTrain from './assets/SustainablityDashboard/GoldTrain.png';
import silverTrain from './assets/SustainablityDashboard/SilverTrain.png';
import bronzeTrain from './assets/SustainablityDashboard/BronzeTrain.png';
+import goldWalk from './assets/SustainablityDashboard/walkGold.png';
+import silverWalk from './assets/SustainablityDashboard/walkSilver.png';
+import bronzeWalk from './assets/SustainablityDashboard/walkBronze.png';
+import goldLuas from './assets/SustainablityDashboard/LuasGold.png';
+import silverLuas from './assets/SustainablityDashboard/LuasSilver.png';
+import bronzeLuas from './assets/SustainablityDashboard/LuasBronze.png';
// Define thresholds for medals
const MEDAL_THRESHOLDS = {
@@ -59,6 +65,16 @@ const getMedalImage = (value, vehicleType) => {
silver: silverCar,
bronze: bronzeCar,
},
+ walk: {
+ gold: goldWalk,
+ silver: silverWalk,
+ bronze: bronzeWalk,
+ },
+ luas: {
+ gold: goldLuas,
+ silver: silverLuas,
+ bronze: bronzeLuas,
+ },
// Add default images for other vehicle types
default: {
gold: goldCar,
@@ -95,7 +111,7 @@ const configurePieChartData = (emissionsSavings) => {
// Map emissions savings JSON to PieChart data
return Object.keys(emissionsSavings).map((key) => ({
name: key.charAt(0).toUpperCase() + key.slice(1),
- emissions: emissionsSavings[key],
+ emissions: Math.round(emissionsSavings[key]),
color: colors[key] || '#cccccc', // default color
legendFontColor: '#7F7F7F',
legendFontSize: 15,
@@ -257,37 +273,37 @@ export default function Dashboard() {
type: 'bike',
label: 'Distance traveled by Bike',
value: rawDistances.bike || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.bike / 100) * 100),
},
{
type: 'walk',
label: 'Distance traveled by Walking',
value: rawDistances.walk || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.walk / 100) * 100),
},
{
type: 'bus',
label: 'Distance traveled by Bus',
value: rawDistances.bus || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.bus / 100) * 100),
},
{
type: 'car',
label: 'Distance traveled by Car',
value: rawDistances.car || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.car / 100) * 100),
},
{
type: 'train',
label: 'Distance traveled by Train',
value: rawDistances.train || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.train / 100) * 100),
},
{
type: 'luas',
label: 'Distance traveled by Luas',
value: rawDistances.luas || 0,
- total: 100,
+ total: Math.max(100, Math.ceil(rawDistances.luas / 100) * 100),
},
];
@@ -304,7 +320,7 @@ export default function Dashboard() {
try {
const baseUrl =
Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL;
console.log(
`Sending request to ${baseUrl}/get_sus_stats?sender=${senderName}`,
@@ -490,7 +506,7 @@ export default function Dashboard() {
{item.label}
- {item.ratio}/{item.total}
+ {item.ratio.toFixed(2)}/{item.total}
{
const baseUrl =
customBaseUrl ||
(Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL);
console.log(`Sending request to ${baseUrl}/${url}`);
diff --git a/client/src/utils/incidentReporterUtils.js b/client/src/utils/incidentReporterUtils.js
index d68183a..a3db396 100644
--- a/client/src/utils/incidentReporterUtils.js
+++ b/client/src/utils/incidentReporterUtils.js
@@ -6,7 +6,7 @@ export const postIncident = async (incidentData, customBaseUrl = null) => {
const baseUrl =
customBaseUrl ||
(Platform.OS === 'web'
- ? 'http://localhost:8000'
+ ? 'http://localhost'
: process.env.EXPO_PUBLIC_API_URL);
console.log(`Sending request to ${baseUrl}/report_incident}`);
diff --git a/server/README.md b/server/README.md
index 2a479da..a5ba309 100644
--- a/server/README.md
+++ b/server/README.md
@@ -38,4 +38,7 @@ The `base_api.py` class contains the base API class that all other APIs should i
Add a .env file with this
SUPABASE_URL=https://sawdavbqvabwequzmxpm.supabase.co
-SUPABASE_SERVICE_KEY=(see whatsapp)
\ No newline at end of file
+SUPABASE_SERVICE_KEY=(see whatsapp)
+
+## Notes
+No setup is necessary here when running the app with the server running on the droplet. See `client/README.md`.
\ No newline at end of file
diff --git a/server/src/(legacy) main.py b/server/src/(legacy) main.py
deleted file mode 100644
index a6a4ff2..0000000
--- a/server/src/(legacy) main.py
+++ /dev/null
@@ -1,83 +0,0 @@
-from fastapi import FastAPI, Request
-from fastapi.middleware.cors import CORSMiddleware
-from pydantic import BaseModel
-import logging
-import uvicorn
-import sys
-
-# Configure logging
-logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s - %(levelname)s - %(message)s",
- handlers=[logging.StreamHandler(sys.stdout)],
-)
-logger = logging.getLogger(__name__)
-
-
-class Message(BaseModel):
- text: str
-
-
-class Login(BaseModel):
- username: str
- password: str
-
-
-app = FastAPI()
-
-# Configure CORS with logging
-origins = ["*"] # In production, replace with actual frontend URL
-app.add_middleware(
- CORSMiddleware,
- allow_origins=origins,
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-
-@app.middleware("http")
-async def log_requests(request: Request, call_next):
- logger.info(f"Incoming {request.method} request to {request.url}")
- response = await call_next(request)
- logger.info(f"Returning response with status code: {response.status_code}")
- return response
-
-
-@app.get("/")
-async def root():
- logger.info("Root endpoint accessed")
- return {"message": "Hello World"}
-
-
-@app.post("/echo")
-async def echo_message(message: Message):
- logger.info(f"Received message in echo endpoint: {message.text}")
- try:
- return {"message": f"'{message.text}' sent from server"}
- except Exception as e:
- logger.error(f"Error processing message: {str(e)}")
- raise
-
-
-@app.post("/login")
-async def login_data(login: Login):
- logger.info(
- f"Received message in login endpoint: "
- f"{login.username} {login.password}"
- )
- try:
- return {
- "login details": f"'username={login.username}' "
- f"'password={login.password}'sent from server"
- }
- except Exception as e:
- logger.error(f"Error processing message: {str(e)}")
- raise
-
-
-if __name__ == "__main__":
- logger.info("Starting FastAPI server...")
- uvicorn.run(
- app, host="10.6.124.10", port=8000, log_level="debug"
- ) # changing host to Keith ipv4 address
diff --git a/server/src/bus_api.py b/server/src/bus_api.py
deleted file mode 100644
index fea2abe..0000000
--- a/server/src/bus_api.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Python 3.2"""
-
-import urllib.request
-import json
-
-try:
- url = "https://api.nationaltransport.ie/gtfsr/v2/gtfsr?format=json"
-
- hdr = {
- # Request headers
- "Cache-Control": "no-cache",
- "x-api-key": "704561a2a3964364bf1955be3e3ab53f",
- }
-
- req = urllib.request.Request(url, headers=hdr)
-
- req.get_method = lambda: "GET"
- response = urllib.request.urlopen(req)
- print(response.getcode())
- # print(json.loads(response.read().decode()))
- with open("data.json", "w") as f:
- json.dump(json.loads(response.read().decode()), f)
- # filetest.close()
-except Exception as e:
- print(e)
-####################################
diff --git a/server/src/luas_api.py b/server/src/luas_api.py
deleted file mode 100644
index 01bf48f..0000000
--- a/server/src/luas_api.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import requests
-import xml.etree.ElementTree as ET
-
-
-class luasAPI:
-
- def __init__(self):
- self.poll_interval = 10
-
- def get(self, stop):
- # URL of Open Data
- self.stop = stop
- self.url = f"http://luasforecasts.rpa.ie/xml/get.ashx?action=forecast&stop={self.stop}&encrypt=false" # noqa: E501
-
- # while True:
- # # Fetch the geoJSON data from the URL
- # response = requests.get(url)
-
- # # Check if the request was successful
- # if response.status_code == 200:
- # for item in response.json():
- # print(
- # item["name"],
- # " ",
- # item["available_bikes"],
- # (int(item["last_update"])),
- # )
-
- # else:
- # print("Failed to retrieve data:", response.status_code)
-
- # time.sleep(self.poll_interval)
-
- # fetch the real-time Luas data from the URL
- self.response = requests.get(self.url)
-
- # check if the request was succesful
- if self.response.status_code == 200:
-
- # parse xml content
- root = ET.fromstring(self.response.content)
-
- # initialise dictionaries to store tram info by direction
- tram_info = {"Inbound": [], "Outbound": []}
-
- # iterate through each direction
- for direction in root.findall("direction"):
- direction_name = direction.get("name")
- for tram in direction.findall("tram"):
- due_mins = tram.get("dueMins")
- destination = tram.get("destination")
- tram_info[direction_name].append(
- {"dueMins": due_mins, "destination": destination}
- )
-
- # print the extracted tram information
- print("Inbound Trams:", tram_info["Inbound"])
- print("Outbound Trams:", tram_info["Outbound"], "\n")
-
- else:
- print("Failed to retrieve data:", self.response.status_code)
-
- # time.sleep(self.poll_interval)
-
-
-if __name__ == "__main__":
- luas = luasAPI()
- while True:
- stop = str(
- input(
- "Enter stop code to see live arrivals and departures,"
- ' or "exit" to leave: '
- )
- )
- if stop == "exit":
- break
- luas.get(stop)
diff --git a/server/src/preferences_legacy.py b/server/src/preferences_legacy.py
deleted file mode 100644
index 8718244..0000000
--- a/server/src/preferences_legacy.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from src.Database_class import DataBase
-
-
-class UserPersonalizedSettings:
-
- def __init__(
- self,
- walkspeed=0,
- travelMode={
- "bus": False,
- "car": False,
- "train": False,
- "walk": False,
- "bike": False,
- "tram": False,
- },
- bike=False,
- privateVehicle=False,
- accessibility=None,
- motorways=False,
- tolls=False,
- ):
-
- self.walkingSpeed = walkspeed
- self.travelMode = travelMode
- self.bike = bike
- self.privateVehicle = privateVehicle
- self.accessibility = accessibility
- self.motorways = motorways
- self.tolls = tolls
- self.database = DataBase()
- self.tablename = "personalisedSettings"
-
- def writePersonlisedSettings(self, username):
- # TODO check if user info is in the database
- """Writes personalise components. If no user data used add_entry
- if user exists use update_entry.
-
- Args:
- username (_type_): _description_
- """
- self.database.connect_db()
-
- databaseDict = {
- "Username": username,
- "WalkingSpeed": self.walkingSpeed,
- "TravelMode": self.travelMode,
- "Bike": self.bike,
- "PrivateVehicle": self.privateVehicle,
- "Accessibility": self.accessibility,
- "Motorways": self.motorways,
- "Toll": self.tolls,
- }
-
- self.database.add_entry(self.tablename, databaseDict)
-
- self.database.close_con()
-
- return
diff --git a/server/src/sample_luas_api.py b/server/src/sample_luas_api.py
deleted file mode 100644
index 3cef997..0000000
--- a/server/src/sample_luas_api.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from base_api import BaseAPI
-
-
-class LuasAPI(BaseAPI):
- def __init__(self):
- super().__init__()
-
- def get(self):
- return "LuasAPI"
-
-
-def main():
- x = LuasAPI()
- x.get()
-
-
-if __name__ == "__main__":
- main()
diff --git a/server/src/signup.py b/server/src/signup.py
index e1660c9..e4fa994 100644
--- a/server/src/signup.py
+++ b/server/src/signup.py
@@ -67,9 +67,7 @@ def signup_user(self, username: str, password: str):
db.close_con()
self.preferences.db_initialise_preferences(username)
- self.sustainability.db_initialise_sustainability(
- username
- )
+ self.sustainability.db_initialise_sustainability(username)
return True
diff --git a/server/src/sustainability.py b/server/src/sustainability.py
index 3b6babb..c528034 100644
--- a/server/src/sustainability.py
+++ b/server/src/sustainability.py
@@ -1,6 +1,6 @@
from enum import Enum
import logging
-from fastapi import Query, HTTPException, APIRouter
+from fastapi import Query, HTTPException, APIRouter, Body
from dotenv import load_dotenv
from .Database_class import DataBase
@@ -26,6 +26,7 @@ def __init__(self, api, logger: logging.Logger):
load_dotenv()
# Register Endpoints
self.api_get_sus_stats()
+ self.api_update_sus_stats()
def db_initialise_sustainability(self, username):
db = DataBase()
@@ -102,6 +103,29 @@ async def get_sus_stats(user: str = Query(..., alias="sender")):
"friends_sus_scores": friends_sus_scores,
}
+ def api_update_sus_stats(self):
+ @self.app.post("/update_sus_stats")
+ async def update_sus_stats(
+ username: str = Body(...),
+ flag: bool = Body(...),
+ modeDistances: dict = Body(...),
+ ):
+ journeyData = {
+ "bike": modeDistances["BICYCLING"],
+ "car": modeDistances["DRIVING"],
+ "luas": modeDistances["Tram"],
+ "train": modeDistances["Train"],
+ "bus": modeDistances["Bus"],
+ "walk": modeDistances["WALKING"],
+ }
+ if flag:
+ self.db_update_monthly_distances(username, journeyData)
+ self.logger.info("Monthly distances updated")
+ self.update_user_sus_score(username)
+
+ journeyData.pop("car", None)
+ return self.calc_scores_from_route(username, journeyData)
+
def db_fetch_month_sus_stats(self, user):
table_name = "monthly_distance"
db = DataBase()
@@ -142,6 +166,44 @@ def db_fetch_year_sus_stats(self, user):
print("Year stats not found")
return None
+ def db_update_monthly_distances(self, user, journey_data):
+ table_name = "monthly_distance"
+ db = DataBase()
+ db.connect_db()
+
+ # If user found
+ if db.search_user(table_name, user):
+ self.logger.info("Found user")
+ for transport_mode in journey_data:
+ if transport_mode not in self.vehicle_types:
+ self.logger.error(
+ f"Invalid transport mode: {transport_mode}"
+ )
+ continue
+
+ # Get distance for each transport mode
+ raw_distances = db.return_user_row(table_name, user)
+ self.logger.info(f"Raw distances: {raw_distances}")
+ # Add new distances to the existing ones
+ journey_data[transport_mode] += raw_distances[transport_mode]
+ self.logger.info(
+ f"Updated distances: {journey_data[transport_mode]}"
+ )
+ # Update the database with new distances
+ db.update_entry(
+ table_name,
+ user,
+ transport_mode,
+ journey_data[transport_mode],
+ )
+ db.close_con()
+ return True
+
+ else:
+ db.close_con()
+ print("Monthly distances not found")
+ return False
+
def db_fetch_raw_distances(self, user):
table_name = "monthly_distance"
db = DataBase()
@@ -250,6 +312,57 @@ def calc_scores(self, emissions_difference: float) -> float:
return round(emissions_difference / 1000, 2)
+ def calc_scores_from_route(self, user, journey_data):
+ table_name = "monthly_distance"
+ db = DataBase()
+ db.connect_db()
+
+ calc_emissions_savings = self.calc_emissions_savings(journey_data)
+ transport_score = 0
+
+ # If user found
+ if db.search_user(table_name, user):
+ self.logger.info("Found user")
+ for transport_mode in journey_data:
+ if transport_mode not in self.vehicle_types:
+ self.logger.error(
+ f"Invalid transport mode: {transport_mode}"
+ )
+ continue
+
+ transport_score += self.calc_scores(
+ calc_emissions_savings[transport_mode]
+ )
+ db.close_con()
+ return transport_score
+
+ else:
+ db.close_con()
+ print("Monthly distances not found")
+ return 0
+
+ def update_user_sus_score(self, user: str) -> float:
+ db = DataBase()
+ db.connect_db()
+
+ monthly_data = db.return_user_row("monthly_distance", user)
+ if not monthly_data:
+ self.logger.warning(f"No monthly distance data found for {user}")
+ db.close_con()
+ return 0
+
+ emissions_savings = self.calc_emissions_savings(monthly_data)
+ total_score = 0
+ for mode in emissions_savings:
+ total_score += self.calc_scores(emissions_savings[mode])
+
+ db.update_entry("user_table", user, "sus_score", total_score)
+ self.logger.info(
+ f"Updated sustainability score for {user}: {total_score}"
+ )
+ db.close_con()
+ return total_score
+
def calc_emissions_savings(self, monthly_distances):
emissions_dif = {
"bike": 0,
diff --git a/server/src/wayfinding.py b/server/src/wayfinding.py
index 017b3ca..494acb1 100644
--- a/server/src/wayfinding.py
+++ b/server/src/wayfinding.py
@@ -85,7 +85,7 @@ def get_routes_with_preferences(
"alternatives": str(alternatives).lower(),
"key": GOOGLE_MAPS_API_KEY,
"username": username,
- "avoid": toAvoid
+ "avoid": toAvoid,
}
response = requests.get(url, params=parameters)
diff --git a/server/tests/test_sustainability.py b/server/tests/test_sustainability.py
index df9a0b3..330e5a8 100644
--- a/server/tests/test_sustainability.py
+++ b/server/tests/test_sustainability.py
@@ -176,6 +176,68 @@ def test_emissions_savings(sustainability_instance):
}
+def test_db_update_monthly_distances_success(sustainability_instance):
+ """
+ Test for successful update of monthly distances
+ """
+ with patch("src.sustainability.DataBase") as mock_db_class:
+ mock_db = MagicMock()
+ mock_db.search_user.return_value = True
+ mock_db.return_user_row.return_value = {
+ "username": "test_user",
+ "bike": 10,
+ "car": 20,
+ "luas": 30,
+ "train": 50,
+ "bus": 80,
+ "walk": 20,
+ "total": 210,
+ }
+
+ mock_db_class.return_value = mock_db
+
+ journey_data = {
+ "bike": 5,
+ "car": 15,
+ "luas": 10,
+ "train": 10,
+ "bus": 20,
+ "walk": 5,
+ }
+
+ result = sustainability_instance.db_update_monthly_distances(
+ "test_user", journey_data
+ )
+
+ assert result is True
+ mock_db.search_user.assert_called_once_with(
+ "monthly_distance", "test_user"
+ )
+
+ # Verify that update_entry was called with correct
+ # parameters for each transport mode
+ assert mock_db.update_entry.call_count == 6
+
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "bike", 15
+ )
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "car", 35
+ )
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "luas", 40
+ )
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "train", 60
+ )
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "bus", 100
+ )
+ assert mock_db.update_entry_any_call(
+ "monthly_distance", "test_user", "walk", 25
+ )
+
+
def test_car_calc_emissions(sustainability_instance):
assert sustainability_instance.calc_emissions(7, "car") == 714