Skip to content

2. Model and Engine

Gabriel Ernesto Mora edited this page Dec 7, 2025 · 10 revisions

Story Map

The Clankers - TripView Story Map

Engine Architecture

The Clankers Engine Architecture

Engine Overview

TripView's data model is mainly composed of the User, Trip, Stop, and Chatt objects, which are present in the front end as serializable data classes and in the backend as database entities. We will go over how these data objects are used throughout the various features in TripView.

theclankers-viewmodels-UML

The User Frontend written with Kotlin and Jetpack Compose manages the client-side experience of TripView. To view details about how TripView works in terms of visual organization and navigation, go to our View UI/UX wiki section. The front end is written using the Model-View-ViewModel (MVVM) architectural pattern, which aims to separate user interface (View) from business logic and data representation (Model) using the ViewModel as an intermediary. Almost every screen has state managed by a specific ViewModel, ensuring object lifetimes and state are consistent within screens but remain within scope.

Our frontend communicates with other parts of the architecture such as the database and LLM by going through the Backend, written with Python and Flask, to manage the server-side of TripView. The backend is structured as a Web REST API, handling requests from the frontend and performing business logic leveraging other backend services before sending responses back to the frontend in JSON. This communication is encapsulated in ApiClient.kt. For more details on the how this communication is handled, view the APIs and Controller wiki section.

While the backend handles much of the business logic, it also uses 4 primary services to facilitate features for TripView users. These include the LLM API for providing agentic travel aid via Gemini, the Google Places (New) API for obtaining detailed information for specific stops of an itinerary, the OSRM Routing API for providing driving route information between a trip's stops, and the Database for maintaining persistent state. Additionally, the frontend directly makes use of the Google Maps SDK to provide navigation and mapping for a trip. The processes and interactions of these services on TripView's data model will be the focus of this wiki section.

Database, Schema, and Data Model

DBMS ER diagram

The backend uses a Postgres database for storing user and trip information. The backend can directly communicate with this database using the psycopg2 package for Python, however we primarily utilize the SQLAlchemy package when interacting with the database to maintain an Object Relational Mapping (ORM) between our data as it is used in the backend and as it is stored in our database. The [database schema](!TODO LINK!) is defined as seen in the above diagram with SQLAlchemy using classes to simplify the declaration of entities & relationships as well as the access, usage, and modification of those objects.

Our data model is centered around [Trip](!TODO LINK!) objects as the main data created and manipulated by users. Trips encode summary information, navigational information, and associated stops and chats. [Stop](!TODO LINK!) objects are individual itinerary items that a trip sequences. Each stop has location, completion, order, and Google Places information. [Chat](!TODO LINK!) objects are messages between a user and the LLM API Gemini Agent, which encode text, sender, timestamp, and role information. A trip must have one or more stops, and a stop must have one or more trips that it belongs to. A trip may have zero or more chats, and a chat must have exactly one trip it belongs to.

TripView users are represented by [User](!TODO LINK!) objects. This object includes basic account information such as first name, last name, email, username, and password, as well as personal information about a user's likes and dislikes with respect to trips. While users and trips are joined by a [PartOf](!TODO LINK!) relationship, trips are defined by this relationship while users can be in a PartOf relationship with many trips. Users are all in a sparse [IsFriends](!TODO LINK!) relationships with one another, where the none relationship state is the default non-relationship. We will discuss this more extensively in the friend management section.

Users

User Authentication

The LoginResult data class is used to encapsulate user authentication.

When the user attempts to log in with a username and password, the [login](!TODO LINK!) function is called and passes the input username and password to the backend. The [input password is then compared with the stored password hash](!TODO LINK!) of the relevant user record using the check_password_hash function from the werkzeug.security module. Successful login then results in the [generation of a JSON Web Token](!TODO LINK!) (JWT) as implemented by the flask_jwt_extended.create_access_token function. The JWT is created, transmitted, and managed by the JWTManager, which is [configured via a server-side secret key](!TODO LINK!). The resulting user id and token are then transmitted to the frontend within a LoginResult to update the [userId and accessToken app states](!TODO LINK!). These states are stored by the client to enable use of features requiring an authorized user, which require an Authorization Header populated by the access token. When the user logs out using the [logout](!TODO LINK!) function, both the userId and accessToken states are set to null.

User signup is not implemented in our MVP, however it is planned that a [signup](!TODO LINK!) function would be called when a user registers with a username, email, password, first name, last name, likes, and dislikes. These would be used by the backend to [generate a password hash](!TODO LINK!) using the generate_password_hash function from the werkzeug.security module and finally [create a user record](!TODO LINK!). The backend would then [generate a JWT token](!TODO LINK!) as described above and return a LoginResult to update the userId and accessToken app states.

User Profile and Preferences

Users are able to configure their profile username, first name, last name, likes, and dislikes. User modifications to these fields are passed to the backend, wherein the [user record is updated for whichever fields were sent](!TODO LINK!). The backend then sends back the updated user data to update the user state on the client side. Likes and dislikes are used to represent user preferences. Preferences are are used by the LLM API to tailor the responses of Gemini service, both for [generating trips](!TODO LINK!) (TODO L337) and [providing landmark context](!TODO LINK!) (TODO L1082) in the form of text inserted into prompts.

IsFriends Relationships

Agentic AI for Trip Planning

LLM API refers to the API built off for our LLM of choice ([gemini-2.5-flash](!TODO LINK!)). The backend is responsible for making requests to and handling responses from the LLM.

Itinerary Generation

The primary function for the LLM is to generate itineraries. To do this, a user fills out a form with their trip details on the frontend when they go to create a new trip. The [submitForm](!TODO LINK!) function passes this information to the backend where the [form data is formatted by the backend for the LLM to use in the model.generate_content function](!TODO LINK!) which hits the [/generateContent endpoint from Gemini](!TODO LINK!), [alongside information the backend has about the user's preferences](!TODO LINK!). The LLM then [generates 3 itinerary choices](!TODO LINK!) into a [predefined JSON schema](!TODO LINK!) for data consistency.

Trip Cost and Transportation Metrics

After a valid JSON object representing the trip itinerary choices is generated, the LLM is called again using the generated itineraries to provide high level trip information to help guide user choice, including total cost estimate, cost breakdown, transportation summary, and transportation breakdown, achieved through a [second chained invocation of the model.generate_content function](!TODO LINK!). Once again this data is returned into a valid JSON object. Finally the backend returns a JSON object that is used in the front end to construct [TripSuggestions](!TODO LINK!) data objects, which encode a proto-trip without its own ID or owner.

Trip Selection and Creation

When one of these suggestions is selected, the [chooseTrip](!TODO LINK!) function is called, sending the trip suggestion to the backend. The backend uses this suggestion to [create a trip object with its own trip_id and owner_id](!TODO LINK!). The stops field is then used to [create stop objects for each proto-stop with it's own stop_id and ties them to the created trip_id](!TODO LINK!). Once each stop is created, [each stop triggers a call of get_place_info](!TODO LINK!), which sends requests to the [Google Places API](!TODO LINK!) to obtain detailed stop information. Finally a [call is made to regenerate_driving_polyline](!TODO LINK!) to create navigation information for the trip using the [OSRM API](!TODO LINK!). These last two APIs will be described in more detail in later subsections. Once all trip information is generated, the trip object is finally sent back to update the front end.

Trip Chatbot

The LLM is also used if the user wants to interactively ask the LLM questions about their trip. To do this the user enters a trip specific chat window facilitated via the ChatViewModel::ChatViewModelFactory ViewModel constructor and enters chats which are forward to the backend using the ApiClient:: in which case the backend formats a prompt with their trip information and question to provide context-informed responses.

Landmark Identification and Context

The LLM also performs landmark identification by taking an image given by the user as well as location information from their phones location sensor. The image of the landmark is saved locally to the user's device and the text returned by the LLM as well as a uuid referring to the stored image is stored in the database for retrieval later.

Trip Modification

remove or add itinerary stops, drag to reorder, edit to add and remove friends from trip, check stops off, archive trip

Stop information using Google Places (New) API

Getting information about stops such as their rating, price range, hours, address, and Google Maps link is achieved by using the Google Places (New) API. Whenever a stop object is created in the database by a network request, the backend request handling queries this API using the stop's latitude and longitude and receives information about that stop.

Driving Navigation with OSRM API

In our skeletal product, the backend returns a polyline containing straight lines between all stops on the route to be visualized on the map. In the MVP, the backend calls the OSRM API to get a polyline consisting of the driving route between all points.

Itinerary Map with Google Maps SDK Integration

When a user views a trip and receives the list of stops for that trip from the LLM/backend, the frontend uses the Google Maps SDK to plot each stop as a point on the map to show to the user.

Clone this wiki locally