Skip to content

Commit 070c155

Browse files
committed
implement clustering, recommendation system, improve algorithm
1 parent ef9e3fe commit 070c155

27 files changed

+1518
-300
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
.venv
3+
src/fuel_forecast
4+
csv

.idea/hackathon.iml

+11-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

alembic/versions/481d2b9416cd_add_users_table.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def upgrade() -> None:
2424
sa.Column('id', sa.Integer(), nullable=False),
2525
sa.Column('email', sa.String(), nullable=True),
2626
sa.Column('promotions', sa.Boolean(), nullable=True),
27+
sa.Column('guest', sa.Boolean(), nullable=False),
2728
sa.Column('password', sa.String(), nullable=True),
2829
sa.PrimaryKeyConstraint('id'),
2930
sa.UniqueConstraint('email')

alembic/versions/4ba0c3464647_add_fuel_table.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""add fuel table
22
33
Revision ID: 4ba0c3464647
4-
Revises: e4222f79264a
4+
Revises: ca72df19df31
55
Create Date: 2024-02-25 07:53:03.178722
66
77
"""
@@ -13,7 +13,7 @@
1313

1414
# revision identifiers, used by Alembic.
1515
revision: str = '4ba0c3464647'
16-
down_revision: Union[str, None] = 'e4222f79264a'
16+
down_revision: Union[str, None] = 'ca72df19df31'
1717
branch_labels: Union[str, Sequence[str], None] = None
1818
depends_on: Union[str, Sequence[str], None] = None
1919

alembic/versions/c3104d14e561_add_cars_and_logs_table.py

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def upgrade() -> None:
3232
sa.Column('tax', sa.Integer(), nullable=True),
3333
sa.Column('mpg', sa.Float(), nullable=True),
3434
sa.Column('engine_size', sa.Float(), nullable=True),
35+
sa.Column('satisfaction', sa.Float(), nullable=True),
36+
sa.Column('class_prediction', sa.Integer(), nullable=True),
3537
sa.PrimaryKeyConstraint('id')
3638
)
3739
op.create_table('logs',

alembic/versions/e4222f79264a_add_satisfaction_field.py

-30
This file was deleted.

poetry.lock

+932-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ passlib = "^1.7.4"
1616
pyjwt = "^2.8.0"
1717
bcrypt = "^4.1.2"
1818
python-jose = "^3.3.0"
19+
pandas = "^2.2.1"
20+
prophet = "^1.1.5"
21+
scikit-learn = "^1.4.1.post1"
22+
jwt = "^1.3.1"
1923

2024

2125
[build-system]

src/auth/dependencies.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
from typing import Annotated
22

33
from fastapi import Depends
4-
from sqlalchemy.ext.asyncio import AsyncSession
54

65
from src.auth.service import get_current_user
7-
from src.database import get_db_session
86
from src.models import UserModel
97

108
# DBSessionDep = Annotated[AsyncSession, Depends(get_db_session)]

src/auth/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from sqlalchemy import Integer, Column, Boolean, String, Float, ForeignKey, DateTime
1+
from sqlalchemy import Integer, Column, ForeignKey, DateTime
22
from sqlalchemy.orm import relationship
33

44
from src.database import Base

src/auth/router.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from fastapi import APIRouter
22

3-
from src.auth import service
43
from src.auth.dependencies import CurrentUserDep
5-
from src.auth.schemas import RegisterData, UserSchemeDetailed, LoginData, Token
4+
from src.auth.schemas import RegisterData, LoginData, Token, UserScheme
65
from src.auth.exceptions import UserAlreadyExists
6+
from src import config
7+
from src.auth import service
78
from src.database import DBSessionDep
89
from src.exceptions import InvalidCredentials
910

@@ -44,12 +45,23 @@ async def register(
4445
return Token(access_token=jwt_token, token_type="Bearer")
4546

4647

47-
4848
@router.get(
4949
"/me",
50-
response_model=UserSchemeDetailed
50+
response_model=UserScheme
5151
)
5252
async def get_me(
5353
user: CurrentUserDep
5454
):
55-
return UserSchemeDetailed.from_orm(user)
55+
return UserScheme.from_orm(user)
56+
57+
58+
@router.post(
59+
"/guest",
60+
response_model=Token
61+
)
62+
async def get_me(
63+
db_session: DBSessionDep
64+
):
65+
user_model = await service.create_guest_user(db_session)
66+
jwt_token = service.create_access_token(user_model.id, expiry_time=config.GUEST_JWT_EXPIRY_TIME)
67+
return Token(access_token=jwt_token, token_type="Bearer")

src/auth/schemas.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional
2+
13
from pydantic import BaseModel
24

35

@@ -19,19 +21,22 @@ class LoginData(BaseModel):
1921

2022
class UserScheme(BaseModel):
2123
id: int
22-
email: str
23-
promotions: bool
24+
email: Optional[str]
25+
promotions: Optional[bool]
26+
guest: Optional[bool]
2427

2528
class Config:
2629
from_attributes = True
2730

2831

2932
class UserSchemeDetailed(BaseModel):
3033
id: int
31-
email: str
32-
promotions: bool
33-
_password: str
34+
email: Optional[str]
35+
promotions: Optional[bool]
36+
guest: Optional[bool]
37+
password: Optional[str]
3438

3539
class Config:
3640
from_attributes = True
37-
underscore_attrs_are_private = True
41+
# underscore_attrs_are_private = True
42+
# todo make sure its ok

src/auth/service.py

+17-15
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
from datetime import datetime, timedelta
2-
from typing import Optional
32

43
import jwt
54
from fastapi import Depends
65
from fastapi.security import OAuth2PasswordBearer
76
from sqlalchemy import select
87
from sqlalchemy.ext.asyncio import AsyncSession
9-
from sqlalchemy.orm import Session
108
from jose import JWTError
11-
from src.auth import models, schemas
9+
from src import config
1210
from src.auth.exceptions import UserDoesntExist, InvalidPassword
13-
from src.auth.schemas import RegisterData, LoginData
11+
from src.auth.schemas import RegisterData
1412
from src.database import get_db_session
1513
from src.exceptions import InvalidCredentials
1614
from src.models import UserModel
17-
from src import config
1815
from passlib.context import CryptContext
1916
from typing import Annotated
2017

@@ -23,7 +20,6 @@
2320
scheme_name="JWT"
2421
)
2522

26-
2723
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
2824

2925

@@ -61,32 +57,38 @@ async def get_user_by_email(db_session: AsyncSession, user_email: str) -> UserMo
6157
return user
6258

6359

64-
def create_access_token(user_id: int) -> str:
60+
def create_access_token(user_id: int, expiry_time: int = config.JWT_EXPIRY_TIME) -> str:
6561
to_encode = {"sub": user_id}
66-
expire = datetime.utcnow() + timedelta(minutes=config.JWT_EXPIRY_TIME)
62+
expire = datetime.utcnow() + timedelta(minutes=expiry_time)
6763
to_encode.update({"exp": expire})
6864
encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=config.JWT_ALGORITHM)
6965
return encoded_jwt
7066

7167

72-
async def get_current_user(token: Annotated[str, Depends(oauth2_bearer)], db_session: AsyncSession = Depends(get_db_session), fatal: bool = False) -> UserModel:
68+
async def get_current_user(token: Annotated[str, Depends(oauth2_bearer)],
69+
db_session: AsyncSession = Depends(get_db_session), fatal: bool = False) -> UserModel:
7370
try:
7471
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[config.JWT_ALGORITHM])
7572
user_id = int(payload.get("sub")) # todo add exception for this
7673
if user_id is None:
7774
raise InvalidCredentials()
7875
except JWTError:
7976
raise InvalidCredentials()
80-
user = await get_user_by_id(db_session, user_id)
81-
# todo prob dont need that expcetion cuz get_user_by_id will cause exception if it needs to
82-
if user is None:
83-
raise InvalidCredentials()
84-
return user
77+
return await get_user_by_id(db_session, user_id)
8578

8679

8780
async def create_user(db_session: AsyncSession, register_data: RegisterData) -> UserModel:
8881
db_user = UserModel(email=register_data.email, password=pwd_context.hash(register_data.password),
89-
promotions=register_data.promotions)
82+
guest=False, promotions=register_data.promotions)
83+
db_session.add(db_user)
84+
await db_session.commit()
85+
await db_session.refresh(db_user)
86+
return db_user
87+
88+
89+
async def create_guest_user(db_session: AsyncSession) -> UserModel:
90+
db_user = UserModel(email=None, password=None,
91+
guest=True, promotions=False)
9092
db_session.add(db_user)
9193
await db_session.commit()
9294
await db_session.refresh(db_user)

src/cars/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class CarModel(Base):
2121
mpg = Column(Float)
2222
engine_size = Column(Float)
2323
satisfaction = Column(Float)
24+
class_prediction = Column(Integer)
2425

2526
@property
2627
def maintenance(self):

src/cars/router.py

+22-15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
from fastapi import APIRouter, Depends, HTTPException
1+
from fastapi import APIRouter
22

3-
from src.auth import service
4-
from src.auth.dependencies import CurrentUserDep
5-
from src.auth.schemas import RegisterData, UserSchemeDetailed, LoginData, Token
6-
from src.auth.exceptions import UserAlreadyExists
73
from src.cars.schemas import CarsFiltersSchema
8-
from src.cars.service import get_cars_makes, get_cars_models, get_cars
9-
from src.database import get_db_session, DBSessionDep
10-
from src.exceptions import InvalidCredentials, TokenParseError
11-
from src.models import UserModel
4+
from src.cars.service import get_cars_makes, get_cars_models, get_cars, add_log, get_recommended_car
5+
from src.auth.dependencies import CurrentUserDep
6+
from src.database import DBSessionDep
127

138
router = APIRouter()
149

@@ -25,9 +20,21 @@ async def get_models(car_make: str, db_session: DBSessionDep):
2520

2621

2722
@router.post("/find")
28-
async def find_cars(filters: CarsFiltersSchema,
29-
db_session: DBSessionDep
30-
):
31-
car = await get_cars(db_session, filters)
32-
# await add_log()
33-
return car
23+
async def find_cars(
24+
user: CurrentUserDep,
25+
filters: CarsFiltersSchema,
26+
db_session: DBSessionDep
27+
):
28+
cars = await get_cars(db_session, filters)
29+
30+
for car in cars:
31+
await add_log(db_session, user, car.CarModel)
32+
return cars
33+
34+
35+
@router.get("/recommended")
36+
async def recommended_car(
37+
user: CurrentUserDep,
38+
db_session: DBSessionDep
39+
):
40+
return await get_recommended_car(db_session, user)

src/cars/schemas.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional, List
22

3-
from pydantic import BaseModel, Field
3+
from pydantic import BaseModel
44

55

66
class CarsFiltersSchema(BaseModel):

0 commit comments

Comments
 (0)