Skip to content

Commit fe1a761

Browse files
authored
Merge pull request #59 from Commute-ai/development
merge v0.8.0 to main
2 parents 1795e37 + 52e44a7 commit fe1a761

20 files changed

Lines changed: 1689 additions & 78 deletions

alembic/env.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
# Import your Base and all models
99
from app.db.database import Base
1010
from app.models.user import User # noqa: F401 - Import to register with Base
11+
from app.models.global_preference import GlobalPreference # noqa: F401
12+
from app.models.route_preference import RoutePreference # noqa: F401
1113

1214

1315
# this is the Alembic Config object, which provides

alembic/versions/8f2fc156b3bb_.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
3+
Revision ID: 8f2fc156b3bb
4+
Revises: ecd62863c9d0
5+
Create Date: 2025-11-13 14:11:13.445064
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects import postgresql
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '8f2fc156b3bb'
16+
down_revision: Union[str, Sequence[str], None] = 'ecd62863c9d0'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_table('global_preferences',
25+
sa.Column('id', sa.Integer(), nullable=False),
26+
sa.Column('user_id', sa.Integer(), nullable=False),
27+
sa.Column('prompt', sa.String(), nullable=False),
28+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
29+
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
30+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
31+
sa.PrimaryKeyConstraint('id')
32+
)
33+
op.create_index(op.f('ix_global_preferences_id'), 'global_preferences', ['id'], unique=False)
34+
op.create_index(op.f('ix_global_preferences_user_id'), 'global_preferences', ['user_id'], unique=False)
35+
op.create_table('route_preferences',
36+
sa.Column('id', sa.Integer(), nullable=False),
37+
sa.Column('user_id', sa.Integer(), nullable=False),
38+
sa.Column('prompt', sa.String(), nullable=False),
39+
sa.Column('from_latitude', sa.Float(), nullable=False),
40+
sa.Column('from_longitude', sa.Float(), nullable=False),
41+
sa.Column('to_latitude', sa.Float(), nullable=False),
42+
sa.Column('to_longitude', sa.Float(), nullable=False),
43+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
44+
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
45+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
46+
sa.PrimaryKeyConstraint('id')
47+
)
48+
op.create_index(op.f('ix_route_preferences_id'), 'route_preferences', ['id'], unique=False)
49+
op.create_index(op.f('ix_route_preferences_user_id'), 'route_preferences', ['user_id'], unique=False)
50+
op.drop_index(op.f('ix_preference_id'), table_name='preference')
51+
op.drop_index(op.f('ix_preference_user_id'), table_name='preference')
52+
op.drop_table('preference')
53+
# ### end Alembic commands ###
54+
55+
56+
def downgrade() -> None:
57+
"""Downgrade schema."""
58+
# ### commands auto generated by Alembic - please adjust! ###
59+
op.create_table('preference',
60+
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
61+
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
62+
sa.Column('prompt', sa.VARCHAR(), autoincrement=False, nullable=False),
63+
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
64+
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
65+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('preference_user_id_fkey')),
66+
sa.PrimaryKeyConstraint('id', name=op.f('preference_pkey'))
67+
)
68+
op.create_index(op.f('ix_preference_user_id'), 'preference', ['user_id'], unique=False)
69+
op.create_index(op.f('ix_preference_id'), 'preference', ['id'], unique=False)
70+
op.drop_index(op.f('ix_route_preferences_user_id'), table_name='route_preferences')
71+
op.drop_index(op.f('ix_route_preferences_id'), table_name='route_preferences')
72+
op.drop_table('route_preferences')
73+
op.drop_index(op.f('ix_global_preferences_user_id'), table_name='global_preferences')
74+
op.drop_index(op.f('ix_global_preferences_id'), table_name='global_preferences')
75+
op.drop_table('global_preferences')
76+
# ### end Alembic commands ###

app/api/v1/api.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
from fastapi import APIRouter
22

3-
from app.api.v1.endpoints import auth, health, preferences, routes, users
3+
from app.api.v1.endpoints import (
4+
auth,
5+
health,
6+
preferences,
7+
route_preferences,
8+
routes,
9+
users,
10+
)
411

512
api_router = APIRouter()
613

@@ -10,4 +17,9 @@
1017
api_router.include_router(
1118
preferences.router, prefix="/users/preferences", tags=["preferences"]
1219
)
20+
api_router.include_router(
21+
route_preferences.router,
22+
prefix="/users/route-preferences",
23+
tags=["route-preferences"],
24+
)
1325
api_router.include_router(routes.router, prefix="/routes", tags=["routes"])

app/api/v1/endpoints/preferences.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,40 @@
55

66
from app.db.database import get_db
77
from app.models.user import User
8-
from app.schemas.preference import PreferenceCreate, PreferenceResponse
8+
from app.schemas.global_preference import (
9+
GlobalPreferenceCreate,
10+
GlobalPreferenceResponse,
11+
)
912
from app.services.auth_service import auth_service
10-
from app.services.preference_service import preference_service
13+
from app.services.global_preference_service import global_preference_service
1114

1215
router = APIRouter()
1316

1417

15-
@router.get("", response_model=List[PreferenceResponse])
18+
@router.get("", response_model=List[GlobalPreferenceResponse])
1619
async def get_user_preferences(
1720
current_user: User = Depends(auth_service.get_current_user),
1821
db: Session = Depends(get_db),
1922
) -> Any:
2023
"""
21-
Get all preferences for the authenticated user.
24+
Get all global preferences for the authenticated user.
2225
"""
23-
preferences = preference_service.get_user_preferences(
26+
preferences = global_preference_service.get_user_preferences(
2427
db, int(current_user.id)
2528
)
2629
return preferences
2730

2831

29-
@router.post("", response_model=PreferenceResponse, status_code=201)
32+
@router.post("", response_model=GlobalPreferenceResponse, status_code=201)
3033
async def create_preference(
31-
preference_in: PreferenceCreate,
34+
preference_in: GlobalPreferenceCreate,
3235
current_user: User = Depends(auth_service.get_current_user),
3336
db: Session = Depends(get_db),
3437
) -> Any:
3538
"""
36-
Create a new preference for the authenticated user.
39+
Create a new global preference for the authenticated user.
3740
"""
38-
preference = preference_service.create_preference(
41+
preference = global_preference_service.create_preference(
3942
db, int(current_user.id), preference_in
4043
)
4144
return preference
@@ -48,8 +51,8 @@ async def delete_preference(
4851
db: Session = Depends(get_db),
4952
) -> None:
5053
"""
51-
Delete a preference for the authenticated user.
54+
Delete a global preference for the authenticated user.
5255
"""
53-
preference_service.delete_preference(
56+
global_preference_service.delete_preference(
5457
db, int(current_user.id), preference_id
5558
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from typing import Any, List
2+
3+
from fastapi import APIRouter, Depends
4+
from sqlalchemy.orm import Session
5+
6+
from app.db.database import get_db
7+
from app.models.user import User
8+
from app.schemas.route_preference import (
9+
RoutePreferenceCreate,
10+
RoutePreferenceResponse,
11+
)
12+
from app.services.auth_service import auth_service
13+
from app.services.route_preference_service import route_preference_service
14+
15+
router = APIRouter()
16+
17+
18+
@router.get("", response_model=List[RoutePreferenceResponse])
19+
async def get_user_route_preferences(
20+
current_user: User = Depends(auth_service.get_current_user),
21+
db: Session = Depends(get_db),
22+
) -> Any:
23+
"""
24+
Get all route preferences for the authenticated user.
25+
"""
26+
preferences = route_preference_service.get_user_preferences(
27+
db, int(current_user.id)
28+
)
29+
return preferences
30+
31+
32+
@router.post("", response_model=RoutePreferenceResponse, status_code=201)
33+
async def create_route_preference(
34+
preference_in: RoutePreferenceCreate,
35+
current_user: User = Depends(auth_service.get_current_user),
36+
db: Session = Depends(get_db),
37+
) -> Any:
38+
"""
39+
Create a new route preference for the authenticated user.
40+
"""
41+
preference = route_preference_service.create_preference(
42+
db, int(current_user.id), preference_in
43+
)
44+
return preference
45+
46+
47+
@router.delete("/{preference_id}", status_code=204)
48+
async def delete_route_preference(
49+
preference_id: int,
50+
current_user: User = Depends(auth_service.get_current_user),
51+
db: Session = Depends(get_db),
52+
) -> None:
53+
"""
54+
Delete a route preference for the authenticated user.
55+
"""
56+
route_preference_service.delete_preference(
57+
db, int(current_user.id), preference_id
58+
)

app/api/v1/endpoints/routes.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from app.schemas.routes import RouteSearchRequest, RouteSearchResponse
1919
from app.services.ai_agents_service import ai_agents_service
2020
from app.services.auth_service import auth_service
21-
from app.services.preference_service import preference_service
21+
from app.services.global_preference_service import global_preference_service
22+
from app.services.route_preference_service import route_preference_service
2223
from app.services.routing_service import (
2324
RoutingAPIError,
2425
RoutingDataError,
@@ -76,15 +77,30 @@ async def search_routes(
7677
for pref in request.preferences:
7778
user_preferences.append({"prompt": pref})
7879

79-
# 2. Add stored preferences from authenticated user
80+
# 2. Add stored global preferences from authenticated user
8081
try:
81-
stored_prefs = preference_service.get_user_preferences(
82+
global_prefs = global_preference_service.get_user_preferences(
8283
db, int(current_user.id)
8384
)
84-
for pref in stored_prefs:
85+
for pref in global_prefs:
8586
user_preferences.append({"prompt": pref.prompt})
8687
except Exception as e: # pylint: disable=broad-except
87-
logger.warning("Failed to fetch user preferences: %s", str(e))
88+
logger.warning("Failed to fetch global preferences: %s", str(e))
89+
90+
# 3. Add route-specific preferences that match the coordinates
91+
try:
92+
route_prefs = route_preference_service.get_preferences_by_coordinates(
93+
db,
94+
int(current_user.id),
95+
request.origin.latitude,
96+
request.origin.longitude,
97+
request.destination.latitude,
98+
request.destination.longitude,
99+
)
100+
for pref in route_prefs:
101+
user_preferences.append({"prompt": pref.prompt})
102+
except Exception as e: # pylint: disable=broad-except
103+
logger.warning("Failed to fetch route-specific preferences: %s", str(e))
88104

89105
logger.info(
90106
"Using %d user preferences for route insights", len(user_preferences)

app/core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class Settings(BaseSettings):
1010
PROJECT_NAME: str = "Commute.ai"
1111
PROJECT_DESCRIPTION: str = "AI-powered public transport routing"
12-
VERSION: str = "0.7.0"
12+
VERSION: str = "0.8.0"
1313
API_V1_STR: str = "/api/v1"
1414

1515
# JWT Settings

app/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from app.models.global_preference import GlobalPreference
2+
from app.models.route_preference import RoutePreference
3+
4+
__all__ = ["GlobalPreference", "RoutePreference"]
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from app.db.database import Base
66

77

8-
class Preference(Base):
9-
__tablename__ = "preference"
8+
class GlobalPreference(Base):
9+
__tablename__ = "global_preferences"
1010

1111
id = Column(Integer, primary_key=True, index=True)
1212
user_id = Column(
@@ -16,7 +16,7 @@ class Preference(Base):
1616
created_at = Column(DateTime(timezone=True), server_default=func.now())
1717
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
1818

19-
user = relationship("User", back_populates="preferences")
19+
user = relationship("User", back_populates="global_preferences")
2020

2121
def __init__(self, user_id, prompt):
2222
self.user_id = user_id

app/models/route_preference.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String
2+
from sqlalchemy.orm import relationship
3+
from sqlalchemy.sql import func
4+
5+
from app.db.database import Base
6+
7+
8+
class RoutePreference(Base):
9+
__tablename__ = "route_preferences"
10+
11+
id = Column(Integer, primary_key=True, index=True)
12+
user_id = Column(
13+
Integer, ForeignKey("users.id"), nullable=False, index=True
14+
)
15+
prompt = Column(String, nullable=False)
16+
from_latitude = Column(Float, nullable=False)
17+
from_longitude = Column(Float, nullable=False)
18+
to_latitude = Column(Float, nullable=False)
19+
to_longitude = Column(Float, nullable=False)
20+
created_at = Column(DateTime(timezone=True), server_default=func.now())
21+
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
22+
23+
user = relationship("User", back_populates="route_preferences")
24+
25+
def __init__(
26+
self,
27+
user_id,
28+
prompt,
29+
from_latitude,
30+
from_longitude,
31+
to_latitude,
32+
to_longitude,
33+
):
34+
self.user_id = user_id
35+
self.prompt = prompt
36+
self.from_latitude = from_latitude
37+
self.from_longitude = from_longitude
38+
self.to_latitude = to_latitude
39+
self.to_longitude = to_longitude

0 commit comments

Comments
 (0)