Skip to content

Implemented New Sustainability Score Calculation and Fixed Bugs When Renting Items #73

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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
26 changes: 19 additions & 7 deletions application/backend/secondchance_backend/item/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .serializers import ItemListSerializer, ItemDetailSerializer, RentalListSerializer
from django.shortcuts import get_object_or_404
from useraccount.models import User

from datetime import date, datetime

@api_view(["GET"])
@authentication_classes([])
Expand Down Expand Up @@ -161,11 +161,21 @@ def rent_item(request, pk):
try:
start_date = request.POST.get("start_date", "")
end_date = request.POST.get("end_date", "")
number_of_days = request.POST.get("number_of_days", "")
total_price = request.POST.get("total_price", "")

item = Item.objects.get(pk=pk)

number_of_days = int(request.POST.get("number_of_days", 0))
total_price = float(request.POST.get("total_price", 0))

try:
start_date = datetime.strptime(start_date, "%Y-%m-%d").date()
end_date = datetime.strptime(end_date, "%Y-%m-%d").date()
except ValueError as e:
return JsonResponse({"success": False, "error": "Error parsing dates."}, status=404)


try:
item = Item.objects.get(pk=pk)
except Item.DoesNotExist:
return JsonResponse({"success": False, "error": "Item not found."}, status=404)

Rental.objects.create(
item=item,
start_date=start_date,
Expand All @@ -174,8 +184,10 @@ def rent_item(request, pk):
total_price=total_price,
created_by=request.user,
)



request.user.increment_items_rented()


return JsonResponse({"success": True})
except Exception as e:
Expand Down
62 changes: 62 additions & 0 deletions application/backend/secondchance_backend/item/utils/categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
def get_user_rented_categories(user):
"""
Get the categories of items that a user has rented from

args:
user instance
used to get items rented by user


return: list of categories
rtype: list?
"""
from item.models import Rental
# convert QuerySet to list before returning
return list(
Rental.objects.filter(created_by=user)
.values_list('item__category', flat=True) # returns desired field
)



def get_user_listed_categories(user):
"""
Get the categories of items that a user has listed

args:
user instance
used to get items listed by user


return: list of categories
rtype: list?
"""
from item.models import Item

return list(
Item.objects.filter(seller=user)
.values_list('category', flat=True)
)

def count_items_rented_from_user(user):
"""
Get the number of items that are currently being rented from the given user
Find the rentals that have an item that belongs to the user instance passed in

args:
user instance

return: number of items being rented from the user
rtype: int
"""
from datetime import date
from item.models import Rental, Item

today = date.today()

# count the number of listings belonging to the user that are being rented out
return Rental.objects.filter(
item__seller = user, # listings put up by user
start_date__lte = today, # listed items rented today or sooner
end_date__gte = today # listed items rented today or after
).count()
95 changes: 88 additions & 7 deletions application/backend/secondchance_backend/useraccount/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager
from django.db import models
from django.utils import timezone
from enum import Enum
# from item.utils.categories import get_user_listed_categories, get_user_rented_categories

# this breaks the code
# from item.models import Item
# from item.models import Rental

class CustomUserManager(UserManager):
"""
Expand Down Expand Up @@ -177,6 +182,8 @@ def increment_items_rented(self):
self.items_rented += 1
self.calculate_sustainability_score()
self.save(update_fields=["items_rented", "sustainability_score"])



def calculate_sustainability_score(self):
"""
Expand All @@ -190,19 +197,75 @@ def calculate_sustainability_score(self):
:return: None
:rtype: None
"""
# 30% of total score = days since join date
# 25% items rented by user
# 30% items listed by user
# 15% popularity (items listed by the user currently being rented by other users)

max_normalized_score = 100

# compute how many days out of 2 years the user has been on the platform
# then normalize it
if self.date_joined:
days_on_platform = (timezone.now() - self.date_joined).days
days_on_platform = (timezone.now() - self.date_joined).days
else:
days_on_platform = 0

score = (
(self.items_rented_out * 2.5)
+ (self.items_rented * 1.5)
+ (days_on_platform * 0.1)
max_points_date_joined = 730 # 2 years in days since the user joined the platform
time_score = min((days_on_platform / max_points_date_joined) * 100, 100)

# get the items rented by the user

if self.items_rented or self.items_rented != 0:
from item.utils.categories import get_user_rented_categories

rented_categories = get_user_rented_categories(self)
rented_items_weighted = 0

# sum weights
for category in rented_categories:
rented_items_weighted += CATEGORY_WEIGHTS[category.lower()]

else:
rented_items_weighted = 0

max_rented_score = 40
rented_items_score = min(max((rented_items_weighted / max_rented_score) * 100, 0), max_normalized_score) # compute here

# compute score for items listed by user and how many of them are being rented
if self.items_rented_out:
from item.utils.categories import get_user_listed_categories
from item.utils.categories import count_items_rented_from_user

raw_popularity_score = count_items_rented_from_user(self)

listed_categories = get_user_listed_categories(self)

listed_items_weighted = 0
for category in listed_categories:
listed_items_weighted += CATEGORY_WEIGHTS[category.lower()]

else:
raw_popularity_score = 0
listed_items_weighted = 0

max_listed_score = 50
max_raw_popularity_score = 80

listed_items_score = min(max((listed_items_weighted / max_listed_score) * 100, 0), max_normalized_score) # compute here

popularity_score = min(max(raw_popularity_score / max_raw_popularity_score * 100, 0), max_normalized_score)

total_sustainability_score = (
time_score * 0.3
+ rented_items_score * 0.25
+ listed_items_score * 0.3
+ popularity_score * 0.15
)

max_score = 100
normalized_score = min(max(round(score), 1), max_score)
normalized_score = min(max(round(total_sustainability_score), 0), max_score)

# update user's score in DB
self.sustainability_score = normalized_score

def save(self, *args, **kwargs):
Expand All @@ -215,3 +278,21 @@ def save(self, *args, **kwargs):
"""
self.calculate_sustainability_score()
super().save(*args, **kwargs)

CATEGORY_WEIGHTS = {
"electronics": 2,
"furniture": 1.5,
"clothing": 3,
"books": 3,
"appliances": 2,
"sports": 2.5,
"toys": 2.5,
"tools": 2,
"vehicles": 2,
"party": 2,
"music": 1,
"photography": 1,
"gardening": 1.5,
"office": 1,
"other": 1,
}
43 changes: 36 additions & 7 deletions application/secondchance/app/components/items/RentalSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ const RentalSidebar: React.FC<RentalSidebarProps> = ({ item, userId }) => {
const [dateRange, setDateRange] = useState<Range>(initialDateRange);
const [minDate, setMinDate] = useState<Date>(new Date());
const [reservedDates, setReservedDates] = useState<Date[]>([]);
const [sustainabilityScore, setSustainabilityScore] = useState<number>(0);

const processRental = async () => {
if (userId) {
if (dateRange.startDate && dateRange.endDate) {
const formData = new FormData();
formData.append('location', item.location);
formData.append('condition', item.condition);
formData.append('category', item.category);
// removed fields not present in Rental Model
// formData.append('location', item.location);
// formData.append('condition', item.condition);
// formData.append('category', item.category);
formData.append('start_date', format(dateRange.startDate, 'yyyy-MM-dd'));
formData.append('end_date', format(dateRange.endDate, 'yyyy-MM-dd'));
formData.append('number_of_days', days.toString());
Expand Down Expand Up @@ -97,17 +99,39 @@ const RentalSidebar: React.FC<RentalSidebarProps> = ({ item, userId }) => {
setReservedDates(dates);
};

const fetchSustainabilityScore = async () => {
if (userId) {
try {
const userData = await apiService.get(`/api/auth/users/${userId}/`);
setSustainabilityScore(userData.sustainability_score || 0);
} catch (error) {
console.error('Error fetching sustainability score:', error);
}
}
};

// bug fix: made the second useEffect wait for the sustainabilityScore to change
// instead of executing before it
useEffect(() => {
getRentals();
fetchSustainabilityScore();
}, [userId]);

useEffect(() => {

if (dateRange.startDate && dateRange.endDate) {
const dayCount = differenceInDays(dateRange.endDate, dateRange.startDate);

if (dayCount && item.price_per_day) {
const _fee = ((dayCount * item.price_per_day) / 100) * 5;

const discountModifier = 0.03;
let sustainabilityDiscount = sustainabilityScore * discountModifier;

console.log("SUSTAINABILITY DISCOUNT:", sustainabilityDiscount);

setFee(_fee);
setTotalPrice(dayCount * item.price_per_day + _fee);
setTotalPrice((dayCount * item.price_per_day + _fee) - sustainabilityDiscount);
setDays(dayCount);
} else {
const _fee = (item.price_per_day / 100) * 5;
Expand All @@ -117,7 +141,7 @@ const RentalSidebar: React.FC<RentalSidebarProps> = ({ item, userId }) => {
setDays(1);
}
}
}, [dateRange]);
}, [dateRange, sustainabilityScore], );

return (
<aside className="col-span-2 mt-6 rounded-xl border border-gray-200 bg-white p-6 shadow-xl">
Expand Down Expand Up @@ -149,14 +173,19 @@ const RentalSidebar: React.FC<RentalSidebarProps> = ({ item, userId }) => {

<div className="mb-4 flex items-center justify-between text-gray-700">
<p className="text-md">Service Fee</p>
<p className="text-md">${fee}</p>
<p className="text-md">${fee.toFixed(2)}</p>
</div>

<div className="mb-4 flex items-center justify-between text-gray-700">
<p className="text-md">Sustainability Score Discount</p>
<p className="text-md">${(sustainabilityScore * 0.03).toFixed(2)}</p>
</div>

<hr className="my-4" />

<div className="mt-4 flex items-center justify-between text-lg font-bold text-gray-900">
<p>Total Amount</p>
<p>${totalPrice}</p>
<p>${totalPrice.toFixed(2)}</p>
</div>
</aside>
);
Expand Down