Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Palate MVP - Final Release

Latest

Choose a tag to compare

@Funkey08 Funkey08 released this 03 Dec 05:14

Palate MVP - Final Release

Introduction

This document provides a comprehensive overview of Palate, our personalized restaurant recommendation platform. We outline the core user needs that motivated our design, the JTBD and CUJs that shaped our development, and the iterative process through which our MVP evolved. We also describe our product’s architecture and installation instructions.

With only two people enrolled in this course, our development approach was intentionally scoped to deliver a functional, dynamic MVP that demonstrates real user value, establishes a foundation for future features, and reflects the lessons we learned throughout the course.

We hope you enjoy!

Picture1

Quick Links

External Links

1. Product Overview

1.1 TL;DR

Palate is a hyper-personalized restaurant recommendation application that solves the problem of slow, generic selection tools that overwhelm users with popularity-based results. Our insight is that diners make choices based on identity, vibe, and dietary context—not ratings. By using LLM-powered agents to capture users' nuanced personalities and explain recommendations, Palate aims to deliver fast, trustworthy, and transparent suggestions tailored to each user’s real preferences.

1.2 Key Jobs To Be Done (JTBD)

Users approach restaurant discovery with highly varied needs, ranging from simple budget constraints and dietary requirements to more nuanced preferences surrounding ambience and social context. Therefore, we have designed the following Jobs To Be Done to illustrate the diversity of user needs that Palate aims to address.

JTBD 1 - The Budget-Conscious Student with Dietary Restrictions

As a Gen Z newcomer to university, I want to find a cheap sit-down restaurant near campus that offers great gluten-free options so that I stay within my budget and not worry about dietary restrictions.

→ Our Focus: We want our application to intelligently consider price, distance, and dietary accommodations, such that users with financial or health-related constraints can discover suitable options effortlessly.

JTBD 2 - The Consultant Seeking Privacy and a Professional Atmosphere

As a professional business consultant, I want to find a quiet coffee shop with a calm atmosphere and tables spaced far apart, so that I can hold private business discussions with a potential client.

→ Our Focus: Beyond simple metadata matching, we want our app to take into account ambience, vibes, and environmental cues in order to deliver hyper-personalized recommendations tailored to individual contexts.

JTBD 3 – The Group of Friends Planning a Meet-Up

As a group of friends reuniting after 4 years, we want to find a place that balances everyone’s dining preferences so that everyone can focus on reconnecting rather than stressing about food choices (and no one feels awkward telling the others they turned vegan).

→ Our Focus: We want our application to automatically aggregate, balance, and explain groups of preference profiles, thereby accommodating everyone’s preferences, schedules, and dining needs.


Overall, we wanted our application to capture not just functional requirements, but deeper considerations such as ease-of-use, comfort, and dining identity. We believe that capturing these JTBDs will differentiate Palate by moving beyond simple, generic recommendations and toward truly personalized, context-aware decisions.

1.3 Customer User Journeys (CUJs)

Based on these JTBDs, we developed three main CUJs that we aimed to complete throughout this course, as well as three secondary CUJs that we aimed to complete as long-term goals. Our MVP scope for this course centered upon these three main CUJs, as these form the backbone of our architecture and are necessary for the development of future functionalities.

CUJ M1 – Create an Account and Build a Personalized Taste Profile

In Summary

A new user signs up or logs in and completes a short onboarding quiz that enables immediate access to personalized recommendations tied to their identity. After using the app for a period of time, they are invited to complete a second, slightly longer Archetype quiz that captures deeper personality-driven preferences.

Primary Flow

  1. The user taps “Sign Up,” and can choose either Google OAuth or Email.
  2. If using Email: the user enters their email and password → Create Account.
  3. After authentication, the user completes the first Onboarding Quiz, with all questions skippable.
    • Dietary flags (gluten-free, vegetarian, halal, etc.)
    • Preferred cuisines (Chinese, Japanese, etc.)
    • Allergies or dislikes (Peanuts, Fish, etc.)
  4. The app routes to Home → Recommendations.
  5. After scrolling for approximately 5 restaurants, the user has the option to complete the second Onboarding (Archetype) Quiz.
    • The quiz consists of 8 questions, each multiple choice.
    • Upon completing the quiz, the system assigns the user an Archetype (Explorer, Purist, Social Curator, Trend Seeker, Conformist, Aestheticist).
  6. Upon leaving the app and re-entering, the user remains logged in and receives updated recommendations tied to their stored profile.

Description

We selected this as one of our main CUJs as it establishes the personalization backbone of Palate. Once authenticated, the user’s onboarding data and archetype assignment are stored in Firebase and persist across sessions. Separating onboarding into two lightweight stages reduces cognitive load while still enabling meaningful taste modeling.

MVP Status

Fully implemented. Authentication triggers retrieval of user preferences stored on Firebase and enables access to the recommendation flow. Answers to the user Onboarding Quizzes are saved under the user’s “Profile” → “My Palate” → “Food Preferences” and “Archetype”.

CUJ M2 — Receive a Personalized Restaurant List Based on Preferences and Query

In Summary

The user opens the home screen and receives a dynamically generated list of restaurants, complete with several images and rationales that explain why each option fits their profile.

Primary Flow

  1. User lands on "Home," where the "For You" page is immediately visible.
  2. The system fetches a candidate set of restaurants within a default radius (e.g., 3 km).
  3. Palate’s ranking agent evaluates each restaurant using the user’s Archetype/taste vector, restaurant metadata, and internal user signals. List cards are rendered with:
    • Cover image
    • Name, cuisine, price tier
    • Personalized rationale (1–2 lines, e.g., "Matches your gluten-free + casual vibe").
    • Badges (diet supported, "quiet-study," "date-night," etc.)
  4. Photos on the cards are swipeable, allowing the user to browse additional images.

Description

This CUJ represents the core of Palate’s value proposition. When a user enters the app, our search function retrieves a set of nearby restaurants from Firestore or Google Places. Each recommendation is computed dynamically rather than pre-seeded, allowing the user to see why a restaurant aligns with their taste profile. The displayed rationale behind each recommendation supports trust-building and reduces decision friction.

MVP Status

Fully implemented. This CUJ involves the entire backend pipeline, from LLM query interpretation, Google/Firestore retrieval, ranking agent scoring, schema formatting, and frontend rendering. Rationale text is generated from taste vectors and user activity rather than generic ratings.

CUJ M3 — Refine Recommendations Through Filtering Options

The user navigates to the Search page, enters or taps a keyword, and receives a filtered list of restaurants that match their query.

Primary Flow

  1. User taps the “Search” icon on the bottom row to reach the “Discover” page.
  2. Users can tap either preset keywords at the top of the screen or search for keywords using the search bar.
  3. The system returns a brief list of restaurants based on tapped keywords or exact search words.

Description

Search is an important capability of any restaurant platform, and we initially prioritized this as one of our main MVPs for this course because we expected the logic to be technically straightforward to implement. However, we ran into major issues with dataset cleanliness, forcing us to pivot our focus onto the first two main CUJs instead. The details of these challenges are further described in Part 2.2.

MVP Status:

Partially implemented. The front-end user interface for the Discover page has been completed, with placeholders for keyword filter buttons. Searching for simple keywords (e.g., “Pizza”) successfully returns a functional list of restaurants. However, filtering by keywords has been deferred pending dataset normalization (please see Part 2.2).

Secondary CUJs

At the beginning of this course, we defined a set of Secondary CUJs that we considered to be important for our value proposition, but outside the feasible scope of an MVP delivered by a two-person team within a short time frame. Therefore, we defined these CUJs to represent natural extensions of our core product thesis, intended for medium- to long-term development once the foundational personalization flows are validated.

Here, we provide a very brief description of what each Secondary CUJ was intended to accomplish:

CUJ S1 – Create a Group and Get Group-Wide Recommendations

This CUJ introduces the ability to generate consensus-friendly recommendations by blending multiple user profiles, reducing coordination friction, and enabling the “planner” role to solve for entire social groups.

CUJ S2 – Fuzzy Search with Proprietary Dataset

This CUJ plans to integrate a natural-language search layer over our cleaned dataset, allowing users to discover restaurants through semantic prompts (e.g., “good brunch near campus” or “chill ramen place to study”).

CUJ S3 – Calendar Integration

This CUJ focuses on integrating availability, enabling users to align preference-based recommendations with actual scheduling constraints (e.g., selecting venues based on shared availability or upcoming events).

 

2. MVP Development Justification

2.1. Initial Hypothesis

Our initial hypothesis was that restaurant decision-making is driven primarily by personal alignment, not necessarily popularity or price. Our initial research has shown that urban Gen-Z and Millennial diners, particularly with “vibe-driven” social groups, struggle to select restaurants because existing platforms optimize for broad, unspecific popularity signals rather than individualized identity-based preferences.1,2 Therefore, we assumed that if we could model user preferences at the level of “vibe” (e.g., cozy, study-friendly, atmospheric, etc.) and provide transparent recommendations explaining why each option was introduced, users would consider our platform more trustworthy, spend less time searching, and feel greater satisfaction with their final selection.

We also expected that certain users—such as the person who typically decides where their friends eat—would experience a disproportionate amount of decision friction. While group-level recommendation blending was scoped as a future CUJ (CUJ-S1), our MVP focused on validating personalization and explainability at the individual level. For our MVP, we considered a working, individual-level recommendation system to be the foundation necessary to eventually support collective decision-making.

1. Belle Communication. (2025). How social media influencers are reshaping restaurant discovery and choice for Gen Z & Millennials. https://bellecommunication.com/how-social-media-influencers-are-reshaping-restaurant-discovery-and-choice-for-gen-z-millennials/

2. Graves, A. (2024, March 6). The art of personalizing the guest experience with data-driven insights. Bloom Intelligence. https://bloomintelligence.com/blog/personalize-guest-experience/

2.2 Key Learnings and Pivots

Throughout the development of Palate, nearly every core feature was reshaped by user research and testing as well as developmental challenges. Below, we outline the most critical assumptions, the evidence that challenged them, and the resulting product pivots. 

Learning no. 1 – Users do not tolerate cognitive friction during onboarding.

Initial Assumption

We believed that users would be willing to complete ~5 minutes of onboarding with open-ended questions if it meant the system could generate highly accurate, personalized recommendations from the start.

Evidence

Our early onboarding prototypes (Release 1) required manual text entry and abstract, open-ended questions. However, during user research, users consistently described the onboarding process as “mentally tiring” and “too long.” Furthermore, users responded positively to short and explicit personality-style questions, as well as rationale snippets in recommendations rather than numeric scores.

Our Pivot

Because the lengthy onboarding quiz created fiction, we split onboarding into two separate layers:

  1. A short, surface-level intake quiz focused on broad preferences and restrictions, introduced immediately at account creation.
  2. A secondary “Archetype quiz” that users can complete later, once they begin using the product.

Additionally, we made both quizzes optional and much shorter (less than 1-2 minutes each). This change dramatically reduced friction and maintained recommendation quality (as affirmed during our user research in Release 3) without overwhelming first-time users.

Learning no. 2 – Explicit self-reported user signals are insufficient for modelling taste.

Initial Assumption

After completely restructuring our onboarding process, we assumed that the onboarding quizzes alone would provide sufficient long-term personalization.

Evidence

Our onboarding process only captures relatively two-dimensional information, with preferences, restrictions, and dining personality. However, this means that recommendations become static: users’ taste profiles no longer adapt as they interact with the platform, clashing with one of our main value propositions.

Pivot

Therefore, we introduced implicit preference acquisition, logging information that is collected naturally during usage:

  • likes
  • dislikes
  • saves
  • visits

This allows taste vectors to evolve continuously while also allowing users to skip entering explicit data. This change required us to redesign our data schema and agent logic so recommendation embeddings are updated incrementally rather than only at onboarding.

Learning no. 3 – Ensuring data quality can be a long and arduous process, and in some cases, should be prioritized over feature breadth.

Initial Assumption

We assumed that filtering with keywords (“Japanese”, “Chinese”, etc.) would be a relatively straightforward CUJ to implement. Therefore, we listed this as one of our main CUJs to be completed early during our development.

Evidence

During development, we realized that data quality was one of our biggest roadblocks, if not the biggest. As an early-stage venture with no present user base, we had to rely on external services to build our initial dataset.

Initially, we began with a public Kaggle dataset, which offered a foundation for developing our NoSQL structure and Firebase testing. However, many key metadata fields were missing and suffered from fragmentation.

To compensate for this, we integrated Google Places API to further populate our dataset. Google Places API provided richer venue coverage, but tags were inconsistent, multi-hierarchal, and often contradictory.

As a recommendation platform, the proprietary dataset is the core of our architectural promise. Without consistent restaurant descriptors, filtering and keyword search can produce failures or inconsistent results. Therefore, dataset cleaning consumed substantial engineering time, even before embedding or ranking was involved.

Pivot

During development, we redirected much of our efforts toward prioritizing data cleaning, including tag normalization, schema alignment, mapping fields across sources, removing redundancies, and defining canonical categories for “vibe” and cuisine.

While we made significant progress on dataset cleanliness, successfully completing a functional simple search and corresponding UI (CUJ-M3), given the limits of this course, we reframed filtering as a second-order feature. Instead, we introduced placeholders for keyword search, as labelled in our MVP. The remaining keyword filtering functionality of our CUJ-M3 will be completed as we continue to clean up our dataset post-course.

Furthermore, looking ahead, we aim to reduce data heterogeneity within our platform by utilizing verified data sources, such as curated restaurant partnerships and collaborations with trusted food reviewers or influencers. By using human-verified descriptors to authenticate our venue metadata, we aim to build a more standardized dataset that is more reliable for recommendation and filtering features.

Learning no. 4 – Data schema changes can lead to system instability.

Initial Assumption

Initially, we spent a long time experimenting with the user schema representation in our backend dataset. We believed that altering user schema incrementally would be safe and relatively isolated.

Evidence

However, multiple schema iterations resulted in significant bugs, such as:

  • Orphaned or isolated user records
  • Recommendation vectors failing to generate
  • Persistent auth sections were unable to reference missing taste data

Upon debugging, we realized that this issue was not at the UI level, but rather at the database level. Because recommendations depended on stable embeddings, missing schema fields effectively broke many of our CUJ functions.

Pivot

We stabilized the database by:

  • Locking schema versions
  • Resetting the authentication user database
  • Enforcing strict model representations for taste profiles

This intervention was necessary in order to restore consistency across records and eliminate intermittent recommendation failures. Once again, this testifies that many of our issues during development did not necessarily have to do with direct programming, but rather, database handling and integrity.

Learning no. 5 – Infrastructure is very important, even at the MVP stage.

Initial Assumption

We assumed that dev-to-prod migration would be relatively frictionless and could be deferred during development

Evidence

Migrating Firebase to leverage Google free credits required consolidating Android app package identifiers and SHA-1 keys. This produced unexpected authentication failures and login instability, despite no changes to our business logic or UI.

Pivot

We standardized deployment configuration early, consolidated package credentials, and treated authentication as core infrastructure, not simply an administrative detail. This reinforced our realization that reliable access can be foundational to one’s product value: without a clean, working login, users won’t be able to onboard, which means they won't be able to receive personalized recommendations.

2.3 Justification of the Final MVP

Our final MVP was shaped through iterative development and repeated user testing. Rather than attempting to implement every feature scoped early in the semester, we narrowed our solution to the smallest set of capabilities that (1) consistently delivered value to users, (2) validated our product thesis, and, importantly, (3) were achievable by a two-person engineering team within the course timeline.

In this section, we outline why each MVP feature was prioritized over alternatives.

Validated Need no. 1 – Quick, Optional, and Explicit Personalization Onboarding Without User Fatigue

User Problem

First-time users abandon restaurant tools when the personalization process feels demanding, ambiguous, or requires excessive effort before delivering value.

MVP Feature

We implemented a short, 30-second onboarding quiz that captures broad dietary requirements and preferences, paired with a later optional Archetype quiz capturing more nuanced personality-driven preferences.

Why is This in Our MVP?

User testing consistently showed that shorter, more concrete questions led to higher completion rates and greater enjoyment, whereas open-ended text inputs caused cognitive fatigue and early exits (Release 3). By separating onboarding into two layers, we are able to deliver personalization immediately while allowing more streamlined identity modeling after trust has been established.

This design directly supports CUJ-M1 (“Build a Personalized Taste Profile”) and proved to be the most intuitive and enjoyable onboarding experience across our user research (Release 4).

Validated Need no. 2 – An Evolving Taste Profile

User Problem

Diners view their taste as dynamic. They expect recommendation systems to learn from their behavior over time, not remain static based on an initial intake.

MVP Feature

We introduced implicit preference acquisition layered on top of explicit onboarding. The system logs likes, dislikes, saves, and visits, which are reflected on the user’s Profile page and continuously update their taste vector.

Why is This in Our MVP?

A quiz-only model produces static recommendations that are unresponsive to users’ activities. Capturing implicit signals transformed our system from a one-time questionnaire into a continuous feedback loop, allowing recommendations to evolve as users interact with the product.

This fulfills our promise of hyper-personalized outputs (CUJ-M2) and validates the core thesis that identity-based dining preferences emerge over time from interaction, not just initial declarations.

Validated Need no. 3 – Explainable, Trustable, and Location-Specific Recommendations

User Problem

As discovered through our user research, users generally distrust opaque algorithmic outputs (e.g., ratings, star averages, influencer hype) and want recommendations that feel grounded in their identity. Additionally, recommendations must be contextually relevant to a user’s location.

MVP Feature

We introduced explainable recommendations (e.g., “This aligns with the Explorer archetype…”) tied to taste vectors rather than rating-based rankings. Also, our Home and Recommendations pages only show a subset of restaurants within a certain radius. If the user is outside of Toronto, they default to the University of Toronto as their location.

Why is This in Our MVP?

Explainability materially reduced skepticism and increased acceptance during user tests. Rather than asking users to simply “trust the algorithm,” we show why a venue matches their tastes, closing the trust gap that dominates existing platforms.

Also, location-scoping ensures recommendations are actionable. Given that our dataset currently covers Toronto restaurants, defaulting external users to Toronto allows them to explore our app without facing empty results or broken recommendation logic.

This feature is therefore one of the core features of our value proposition, critical for building trust, demonstrating capability, and grounding the MVP in real, usable outputs.

Validated Need no. 4 – Restaurant Filtering and Data Before Features

User Problem

Filtering and search functionality collapse when the underlying restaurant data is fragmented, inconsistent, or mapped across incompatible schemas.

MVP Feature

As mentioned, we made iterative improvements to our proprietary dataset throughout this course. Simple keyword search works (e.g., “Pizza” under “Italian [static]”), but filtering is static, with placeholder tags to indicate work in progress.

Why is This in Our MVP?

Early datasets (Kaggle + Google Places) were highly heterogeneous, requiring significant normalization and schema reconciliation. A broad keyword filter built on inconsistent tags could deliver misleading or broken results, which has the possibility of eroding trust faster than omitting the feature entirely.

Therefore, by choosing to ship only simple search in our MVP, we protect the user experience and focus our development on data integrity, which is foundational to any recommendation platform. The remainder of CUJ-M3 (“Filtering Options”) is deferred to post-MVP once our data quality meets a baseline of reliability.

In Summary

Our final MVP includes only the features that produced demonstrable user value during testing. Each component—from our recommendation logic to our user taste vector structure—was selected based on user research and constraints to development feasibility. This allowed us to deliver a functional recommendation system that aligns with real user needs, while remaining achievable for a two-person development team and the course timeline.

3. Functional and Dynamic MVP

Our MVP is fully dynamic, with the exception of filtering. All remaining main Customer User Journeys (CUJs) described in our Product Overview are implemented through real data flows, live recommendation generation, and persistent user state.

Below, we outline the functional elements of the MVP and their respective dynamic components.

  • 3.1. Users can create an account and authenticate using email/password or Google OAuth, with credentials stored through Firebase Authentication. Authentication ensures that recommendations and user profiles are persistent between sessions.

  • 3.2. New users complete a short onboarding quiz that captures dietary restrictions, cuisine preferences, and allergies or dislikes.

  • 3.3. On the “Home” tab, users can scroll through a dynamically generated list of restaurants relative to their location, with rich image carousels, ratings, and metadata, and buttons for liking, disliking, saving, and visiting the restaurant page on Google Maps.

  • 3.4. Upon scrolling for ~10 restaurants, users may complete the onboarding Archetype quiz for more nuanced recommendations.

  • 3.5. On the “Search” tab (magnifying glass icon), users can search with simple keyword queries (e.g., “Pizza,” “Sushi”). These queries execute against aged restaurant data with real-time filtering of displayed results. However, the filtering buttons at the top of the page are static placeholders. They communicate our intended future CUJ but are disabled due to database heterogeneity and normalization constraints, as mentioned in Section 2.2.

  • 3.6. On the “Group” tab (people icon), we plan to implement our group-level functionality (CUJ-S1) as a long-term goal. This tab is included in the MVP as a framework placeholder for future CUJ-S1 development.

  • 3.7. On the “Recommendations” tab (thumbs-up icon), users receive a live list of recommendations based on their Archetype alignment, user activities, and onboarding preferences. Each recommendation includes a keyword descriptor and a short description of why it’s right for you.

  • 3.8. On the “Profile” tab, users can view settings such as food preferences, allergies, and diets, as well as their implicit signals such as likes, saves, and visits. They are also free to retake the Archetype quiz at any time.

Please see Section 6 for our deployment instructions, or visit our frontend app here.

4. Test Coverage

We have implemented tests across the entire app, covering backend and frontend.

Backend Coverage

We have achieved 65% code coverage across the entire backend. No files were omitted; all modules were included to ensure full validation of our agentic logic, database interactions, and recommendation pipelines.

Click here to visit our backend test CI link.

image

Frontend Coverage

We have achieved 77% code coverage across the frontend. We intentionally omitted FlutterFlow auto-generated widget and model files, as these contain large UI-scaffolded code that does not affect our functional logic, cannot be meaningfully unit tested, and would artificially inflate line counts without affecting the reliability of our app. Therefore, we focused our code coverage to all other testable files in the frontend, including business logic, utility functions, backend integrations, and state management.

Click here to visit our frontend test CI link.

image

5. Demo Recording

Palate_Demo.mp4

Alternatively, please click here for an online upload of our demo recording.

All features shown in this video are dynamic, except for button-based keyword filtering and our Social (group-weighting) page.

In this demo, we feature:

1. Authentication and Sign-Up (Dynamic): Create an account via email.

2. Onboarding (Dynamic): Complete the 3-question intake quiz; responses are written to Firebase and initialize the user's taste profile.

3. Home Feed (Dynamic): Scroll a general, exploratory selection of restaurants relative to the user's location. These results are not hyper-personalized; they are proximity-based starter recommendations designed to help users browse, collect signals, and discover options. Users outside of Toronto default to the University of Toronto campus. Users can interact with the Like, Dislike, Save, or Book buttons. The Book button transfers the user to the restaurant's Google Maps page. Each action implicitly updates the user's taste vector.

4. Archetype Quiz (Dynamic): A pop-up notification prompts the user after scrolling. Upon completing the quiz, the user is assigned a personality Archetype that informs the recommendation logic.

5. Search (Mixed): Simple keyword search is dynamic. The keyword filter buttons are static placeholders (see Part 2.2).

6. Social Tab (Static): A placeholder for group-level recommendation features, which is one of our long-term goals (see Secondary CUJs).

7. Recommendations (Dynamic): A hyper-personalized list with explanatory rationale based on the user's Archetype, onboarding information, and implicit in-app activity.

8. Profile (Dynamic): View the user's defined preferences, dietary flags, and implicit signals, with the option to retake the Archetype quiz.

6. Deployment Documentation

Please visit our Deployment Guide for a complete set of instructions for running Palate locally and deploying to Google Cloud Platform.

7. Updated Architecture Diagram

architecture

Key Integration Points & Links

  • Frontend → API Gateway: The Flutter app calls FastAPI endpoints defined in main.py and route handlers such as agent_places.py.
  • API Gateway → Microservices: Routers use dependency injection from deps.py to access the restaurant search and agent feature services.
  • Agents → Firestore: tools_db.py wraps Firestore access logic in firestore.py so LangChain agents can read/write user, restaurant, and agent data.
  • Search Service → External APIs: single_source.py switches between Google Places API and Firestore based on the request’s source parameter, enriching or retrieving restaurant candidates.
  • Recommendation Agents → Frontend: The ranking and user-analysis agents run inside the Cloud Run recommendation service, score candidate restaurants, and return an ordered, rationale-rich list back through the same FastAPI endpoints. Response schemas are defined in responses.py, request payloads in requests.py, and the Flutter app's API client integration is in frontend/palate/lib/backend/. The ranking agent's output formatting logic is in rank_agent.py.

Frontend App
Backend FastAPI Server

Google Cloud Run (with its managed load balancing) hosts our FastAPI gateway and recommendation microservices.

One-Sentence TL;DR: The frontend sends requests through a cloud load balancer and API gateway to microservices that query external APIs and multi-agent recommendation services running on Cloud Run, which read and write to Firebase Firestore to deliver personalized restaurant results back to the user.