# Project export: BeliMatch

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Like food 🍣? No friends ❌? Lonely 🥺??? -> BeliMatch ❤
- Devpost: https://devpost.com/software/belimatch
- GitHub: https://github.com/sgoel97/beli-match
- Demo: https://beli-match.vercel.app/
- Team: 4 GitHub contributor(s) — Samarth Goel (14 commits), Joyce-Yuan (6 commits), Sean Chen (5 commits), Timothy G (4 commits)

## Devpost submission (written by the team)

### Inspiration

Food is a universal language. People bond and share memories over food. As teammates, we bonded over running to Codegen's free boba giveaways and rating the new DoorDash business model of homecooked meals. Since food was able to bring us together, we thought, why isn't there a better way to learn about people's food preferences, and even meet people based off of food preferences. All of us have been sharing our food reviews on apps like Beli, and we wanted to leverage AI technologies and other innovations to make it easier for others to meet based off of their tastes in food.

### What it does

BeliMatch, like DataMatch, helps match you to your most compatible partner through your food preferences and suggests a first date location for both of you to enjoy a great time. After you submit the introduction form, we gather information like your top 25 restaurants on Beli, and we use this to run a matching between what you're looking for, your food preferences, and what others are looking for. At the end of the matching, we send emails to each user with personalized recommendations and their new friends / date matches.

### How we built it

BeliMatch uses React framework with a Firebase backend, and our unique matching algorithm that leverages AI to generate the best matches.

### Challenges we ran into

Creating the pipeline to get the relevant features from a user's Beli or Yelp profile through web scraping and collecting detailed restaurant data was more challenging than anticipated since this was an unexpected technology hurdle we had to overcome. The matching also ended up being more difficult than anticipated due to rules around school matching and preferences that users inputted into the app.

### Accomplishments we're proud of

Being able to actually provide matches that could potentially lead to friendships and more! We went around Treehacks getting users to sign up for our service and many were actually very excited about the idea and filled out our form.

### What we learned

From a technical skills perspective, we learned how to use AI in matching algorithms and use Twilio to send out emails. Additionally, we learned some soft skills when going around pitching our idea and learned how to make people the most excited about it.

### What's next

BeliMatch will run our first batch of matchings at the end of TreeHacks, after receiving overwhelming interest from fellow hackers. Beyond that, we hope that BeliMatch will take off into a platform that helps connect people through our shared universal language -- food.

## README (from the GitHub repository)

# beli-match

Visit us at [https://beli-match.vercel.app](https://beli-match.vercel.app/)
Welcome to BeliMatch, the dating, connections, and restaurant recommendation platform connecting students through the power of food (😉 Otsuka). Built by a team of UC Berkeley and MIT students for TreeHacks 2024, we aim to use technology to bring people closer together and bridge the never-ending gap in the dating market for college students.


## Detected evidence (automated analysis)

Indexed codebase: 18 recognized source files, 37 KB.
- CSS (language) — detected in the code
- Firebase (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (27 of 27)

```
.gitignore
client/.eslintrc.cjs
client/.gitignore
client/bun.lockb
client/firebase-config.js
client/index.html
client/package.json
client/postcss.config.cjs
client/README.md
client/src/components/header.module.css
client/src/components/Heading.jsx
client/src/components/Layout.jsx
client/src/index.css
client/src/main.jsx
client/src/pages/About.jsx
client/src/pages/Home.jsx
client/src/pages/TestBeli.jsx
client/vite.config.js
README.md
server/__init__.py
server/.gitignore
server/scripts/matching.py
server/scripts/populate_beli.py
server/scripts/send_emails.py
top_restaurants.json
user_data.json
webselenium.py
```

### Dependencies

- client/package.json: @mantine/carousel@^7.5.3, @mantine/core@^7.5.3, @mantine/dates@^7.5.3, @mantine/dropzone@^7.5.3, @mantine/form@^7.5.3, @mantine/hooks@^7.5.3, @mantine/modals@^7.5.3, @mantine/nprogress@^7.5.3, @types/react@^18.2.43, @types/react-dom@^18.2.17, @vitejs/plugin-react@^4.2.1, dayjs@^1.11.10, embla-carousel-react@^8.0.0-rc22, eslint@^8.55.0, eslint-plugin-react@^7.33.2, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.5, firebase@^10.8.0, localforage@^1.10.0, match-sorter@^6.3.4, postcss@^8.4.35, postcss-preset-mantine@^1.13.0, postcss-simple-vars@^7.0.1, react@^18.2.0, react-dom@^18.2.0, react-hook-form@^7.50.1, react-router-dom@^6.22.1, sort-by@^1.2.0, vite@^5.0.8

### Recent commits (newest first)

- Update README.md
- Merge branch 'main' of https://github.com/sgoel97/beli-match
- make matchings go into a csv file
- simplify
- minor comments
- matching
- commented out running script
- populate all beli script
- putting in matching files
- test firebase
- about v2
- more meta tags
- add meta image
- remove hamburger
- add photo
- update form
- merge
- add firebase
- Merge branch 'main' of https://github.com/sgoel97/beli-match
- finalize form

## Key source files (fetched from GitHub, selected and truncated for size)

### client/package.json

```
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@mantine/carousel": "^7.5.3",
    "@mantine/core": "^7.5.3",
    "@mantine/dates": "^7.5.3",
    "@mantine/dropzone": "^7.5.3",
    "@mantine/form": "^7.5.3",
    "@mantine/hooks": "^7.5.3",
    "@mantine/modals": "^7.5.3",
    "@mantine/nprogress": "^7.5.3",
    "dayjs": "^1.11.10",
    "embla-carousel-react": "^8.0.0-rc22",
    "firebase": "^10.8.0",
    "localforage": "^1.10.0",
    "match-sorter": "^6.3.4",
    "postcss": "^8.4.35",
    "postcss-preset-mantine": "^1.13.0",
    "postcss-simple-vars": "^7.0.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.50.1",
    "react-router-dom": "^6.22.1",
    "sort-by": "^1.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.43",
    "@types/react-dom": "^18.2.17",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint": "^8.55.0",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "vite": "^5.0.8"
  }
}

```

### client/src/main.jsx

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

import "@mantine/core/styles.css";
import { MantineProvider } from "@mantine/core";

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <MantineProvider
      theme={{
        fontFamily: "Open Sans, sans-serif",
        headings: { fontFamily: "Poppins, sans-serif" },
        primaryColor: "red",
      }}
    >
      <RouterProvider router={router} />
    </MantineProvider>
  </React.StrictMode>
);

```

### webselenium.py

```python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup
import time
import json

import os

# Print the current working directory
current_dir = os.getcwd()

# Set up Chrome options
chrome_options = Options()
chrome_options.add_argument("--headless")  # Run Chrome in headless mode (without GUI)

# Set up the Chrome driver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)

# The URL you want to scrape
url = 'https://app.beliapp.com/lists/will570'

# Navigate to the URL
driver.get(url)

# Optional: wait for JavaScript to render (if needed)
time.sleep(5)  # Adjust sleep time according to your needs

# Get the page source from Selenium
page_source = driver.page_source

# Close the browser
driver.quit()

# Use BeautifulSoup to format the HTML
soup = BeautifulSoup(page_source, 'html.parser')
formatted_html = soup.prettify()

# Parsing part: Find elements using BeautifulSoup
top_restaurants = {}

for i, element in enumerate(soup.find_all('p', {'class': 'text-item-title'})):  # Customize tag and class
    name = element.text.split('.')[1].strip()
    print(name)
    top_restaurants[i] = {'name': name}


for i, element in enumerate(soup.find_all('p', {'class': 'text-item-subtitle'})):  # Customize tag and class
    # even numbers are price and tags
    if (i%2 == 0):
        # dollar, tags, *_ = element.text.split('|')
        array = element.text.split('|')
        # print(array)
        dollar = array[0].strip()
        tags = []
        if len(array) > 1:
            tags = [tag.strip() for tag in array[1].split(",")]
        top_restaurants[i//2]['price'] = dollar.strip()
        top_restaurants[i//2]['tags'] = tags
    else:
        # odd numbers are locations
        location = element.text.strip()
        top_restaurants[i//2]['location'] = location
    # top_tags[i] = element.text

print(top_restaurants)
# write the dictionary as a json file
with open(current_dir + '/top_restaurants.json', 'w') as file:
    json.dump(top_restaurants, file)

# Define the filename for the prettified HTML content
# file_path = current_dir + '/formatted_webpage_source_will.txt'

# Save the formatted HTML to a file
# with open(file_path, 'w', encoding='utf-8') as file:
#     file.write(formatted_html)

# print(f"The formatted HTML has been saved as '{file_path}'.")
```

### client/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### client/firebase-config.js

```javascript
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyCtRVuffLhGrEclezlrDmJ9mLX-qafBoOQ",
  authDomain: "beli-match.firebaseapp.com",
  projectId: "beli-match",
  storageBucket: "beli-match.appspot.com",
  messagingSenderId: "681031852804",
  appId: "1:681031852804:web:5e2a47572749b19a086651",
  measurementId: "G-FS4NG19DTS"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
export default app;
```

### client/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/logo_img.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="image_src" href="https://beli-match.vercel.app/meta.png" />
    <meta property="og:title" content="BeliMatch" />

    <meta
      property="og:description"
      content="Find Your Perfect Plate Partner with BeliMatch – Where Shared Tastes Lead to Lasting Connections!"
    />

    <meta
      property="og:image"
      content="https://beli-match.vercel.app/meta.png"
    />

    <meta name="twitter:title" content="BeliMatch" />
    <meta
      name="twitter:description"
      content="Find Your Perfect Plate Partner with BeliMatch – Where Shared Tastes Lead to Lasting Connections!"
    />
    <meta
      name="twitter:image"
      content="https://beli-match.vercel.app/meta.png"
    />
    <meta
      name="twitter:card"
      content="https://beli-match.vercel.app/meta.png"
    />

    <title>BeliMatch</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### client/src/index.css

```css
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}

```

### server/scripts/send_emails.py

```python
import smtplib, ssl, csv
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart


def read_csv():
    with open('your_file.csv', newline='') as csvfile:
        csvreader = csv.reader(csvfile)
        for row in csvreader:
            print(row)

def make_email(sender_email, receiver_email):
    message = MIMEMultipart("alternative")
    message["Subject"] = "multipart test"
    message["From"] = sender_email
    message["To"] = receiver_email

    # Create the plain-text and HTML version of your message
    text = """\
    Hi,
    How are you?
    Real Python has many great tutorials:
    www.realpython.com"""
    
    html = """\
    <html>
    <body>
        <p>Hi,<br>
        How are you?<br>
        <a href="http://www.realpython.com">Real Python</a> 
        has many great tutorials.
        </p>
    </body>
    </html>
    """

    part1 = MIMEText(text, "plain")
    part2 = MIMEText(html, "html")

    message.attach(part1)
    message.attach(part2)

    return message.as_string()


def send_email():

    port = 465  # For SSL
    # password = input("Type your password and press enter: ")
    password = "logz yylr goea rldv"

    # Create a secure SSL context
    context = ssl.create_default_context()

    with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
        server.login("belimatch90@gmail.com", password)
        # TODO: Send email here
        
        receiver = "sgoel9@berkeley.edu"

        server.sendmail("belimatch90@gmail.com", receiver, make_email("belimatch90@gmail.com", receiver))
        server.sendmail("belimatch90@gmail.com", "timg51237@gmail.com", make_email("belimatch90@gmail.com", "timg51237@gmail.com"))
        print("Email sent!")


send_email()

```

### server/scripts/matching.py

```python
import json
from typing import Dict
import numpy as np
import pandas as pd
from scipy.optimize import linear_sum_assignment

###General workflow: 
# Load data
# Store users in 2D array of compatibilities
# Create love and friendship possiblities (masks of 0s and 1s)
# Create compatibility matrix
# Output pairs (currently using linear_sum_assignment, but taking the log would be better. Still need to figure
# out how to deal with the 0s)

###LOAD DATA ###
# Open the JSON file
with open("top_restaurants.json", 'r') as json_file:
    # Load JSON data into a Python dictionary
    top_restaurants = json.load(json_file)
with open("user_data.json", 'r') as json_file:
    # Load JSON data into a Python dictionary
    user_data: Dict[str, Dict] = json.load(json_file)

### CREATE MATRICES compatibility, love_mask, friend_mask
# assume user_data is a nested dictionary with keys being user emails, 
# and user_data[email] being another dictionary of user responses

#create a matrix compat where compatibilities[i][j] represents the compatibility between userid[i] and userid[j]
#where userid[i] gives the ith email.

userid: list[str]=[]
for email in user_data:
    userid.append(email)

compatibilities=np.zeros((len(userid), len(userid)))
love_mask = np.zeros((len(userid),len(userid)))
friend_mask = np.zeros((len(userid),len(userid)))

## Responses
loveable: set[str]={"Love and Friendship","Love"}
friendable:set[str]={"Love and Friendship","Friendship"}
gendermap: dict[str, int]={"Male":0, "Female":1, "Nonbinary":2}
prefmap: dict[str, set[int]]={
                  "Men": {0},
                  "Women": {1},
                  "Men & women": {0,1},
                  "Nonbinary-people":{2},
                  "Nonbinary & men":{0,2},
                  "Nonbinary & women":{1,2},
                  "People of all genders":{0,1,2},
                  "Only looking for friends":{},
}


### Iterate through users
for i in range(len(userid)):
    user1: Dict=user_data[userid[i]]
    user1love: int=1 if user1["lookingFor"] in loveable else 0
    user1gender=gendermap[user1["gender"]]
    user1pref=prefmap[user1["loveMatch"]]
    for j in range(i):
        #love compatibility:
        user2: Dict=user_data[userid[j]]
        user2love: int=1 if user2["lookingFor"] in loveable else 0
        user2gender=gendermap[user2["gender"]]
        user2pref=prefmap[user2["loveMatch"]]

        if(user1love*user2love==1 and (user2gender in user1pref) and (user1gender in user2pref)):
            love_mask[i,j]=1
            love_mask[j,i]=1

        #friendship:
        if(user1["lookingFor"] in friendable and user2["lookingFor"] in friendable):
            friend_mask[i,j]=1
            friend_mask[j,i]=1
        
        #matching answers for optional questions
        compat=1
        for question in ["buffet", "cheese", "cuisine", "first"]:
            if(user1[question]==user2[question]):
                compat+=1

        #use Beli data 
        topbeli="top_beli_restaurants"
        top1,top2=[],[]
        if(topbeli in user1):
            top1=user1[topbeli]
        if(topbeli in user2):
            top2=user2[topbeli]
        if(len(top1)>0 and len(top2)>0):
            prices1=[]
            tags1=[]
            for restaurant in top1:
                tags1+=restaurant["tags"]
                prices1.append(len(restaurant["price"]))
            #TODO

        #Compatibility score:
        compatibilities[i,j]=compat
        compatibilities[j,i]=compatibilities[i,j]

love_compatibilities=love_mask*compatibilities
friend_compatibilities=friend_mask*compatibilities

# "optimize"
love_rows, love_cols = linear_sum_assignment(love_compatibilities, maximize=True)
friend_rows, friend_cols=linear_sum_assignment(friend_compatibilities,maximize=True)
# print(love_mask)
# print(compatibilities)
# print("Love matches")
# for row, col in zip(love_rows, love_cols):
#     print(f"Matched pair: ({userid[row]}, {userid[col]}) with weight {love_compatibilities[row, col]}")
# print("Friendship matches")
# for row, col in zip(friend_rows, friend_cols):
#     print(f"Matched pair: ({userid[row]}, {userid[col]}) with weight {friend_compatibilities[row, col]}")
emails1=[userid[row] for row in love_rows]
names1=[user_data[email]["firstName"]+" "+user_data[email]["lastName"] for email in emails1]
emails2=[userid[col] for col in love_cols]
names2=[user_data[email]["firstName"]+" "+user_data[email]["lastName"] for email in emails2]
lovers={"Email 1": emails1, "Name 1": names1, "Email 2": emails2, "Name 2": names2}
df=pd.DataFrame(lovers)
df.to_csv("matchings.csv")
```

### server/scripts/populate_beli.py

```python
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup
import time
import json

import os

# Get the current working directory
cwd = os.getcwd()

# Construct the path to the service account key file
service_account_path = os.path.join(cwd, 'scripts', 'firebase-service-key.json')
cred = credentials.Certificate(service_account_path)
firebase_admin.initialize_app(cred)

db = firestore.client()

# Set up Chrome options
chrome_options = Options()
chrome_options.add_argument("--headless")  # Run Chrome in headless mode (without GUI)
chrome_options.add_argument('--no-sandbox')  # Bypass OS security model
chrome_options.add_argument('--disable-dev-shm-usage')  # Overcome limited resource problems

# Set up the Chrome driver
chrome_service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=chrome_service, options=chrome_options)

def get_top_restaurants(beli_username):

    try:
        url = f'https://app.beliapp.com/lists/{beli_username}'

        # Navigate to the URL
        driver.get(url)

        # Optional: wait for JavaScript to render (if needed)
        time.sleep(5)  # Adjust sleep time according to your needs
    except Exception as e:
        print(f"Failed to populate data for {beli_username}: {e}")

    # Get the page source from Selenium
    page_source = driver.page_source

    # Use BeautifulSoup to format the HTML
    soup = BeautifulSoup(page_source, 'html.parser')
    # formatted_html = soup.prettify()

    # Parsing part: Find elements using BeautifulSoup
    top_restaurants = []

    for i, element in enumerate(soup.find_all('p', {'class': 'text-item-title'})):  # Customize tag and class
        name = element.text.split('.')[1].strip()
        # print(name)
        top_restaurants.append({'name': name})

    for i, element in enumerate(soup.find_all('p', {'class': 'text-item-subtitle'})):  # Customize tag and class
        # even numbers are price and tags
        if (i%2 == 0):
            # dollar, tags, *_ = element.text.split('|')
            array = element.text.split('|')
            # print(array)
            dollar = array[0].strip()
            tags = []
            if len(array) > 1:
                tags = [tag.strip() for tag in array[1].split(",")]
            top_restaurants[i//2]['price'] = dollar.strip()
            top_restaurants[i//2]['tags'] = tags
        else:
            # odd numbers are locations
            location = element.text.strip()
            top_restaurants[i//2]['location'] = location

    print(top_restaurants)
    return top_restaurants

def populate_restaurants_from_beli(user_id):
    beli_username = db.collection('users').document(user_id).get().to_dict().get('beli', None)
    beli_restaurants = db.collection('users').document(user_id).get().to_dict().get('top_beli_restaurants', None)
    if beli_username and not beli_restaurants:
        beli_username = beli_username.strip('@')
        top_restaurants = get_top_restaurants(beli_username)
        db.collection('users').document(user_id).update({'top_beli_restaurants': top_restaurants})
    else:
        print(f'User {user_id} does not have a beli username or already has top beli restaurants')

def populate_all_beli_restaurants():
    users_ref = db.collection('users')
    docs = users_ref.stream()
    for doc in docs:
        # populate_restaurants_from_beli(doc.id)
        populate_restaurants_from_beli(doc.id)
        time.sleep(1)

# def populate_all_beli_restaurants():
#     users_ref = db.collection('users')
#     docs = users_ref.stream()
#     for doc in docs:
#         # populate_restaurants_from_beli(doc.id)
#         user_dict = doc.to_dict()
#         beli_username, beli_restaurants = user_dict.get('beli', None), user_dict.get('top_beli_restaurants', None)
#         if beli_username and not beli_restaurants:
#             beli_username = beli_username.strip('@')
#             top_restaurants = get_top_restaurants(beli_username)
#             # update the document with the top restaurants
#             db.collection('users').document(doc.id).update({'top_beli_restaurants': top_restaurants})
#         else:
#             print(f'User {doc.id} does not have a beli username or already has top beli restaurants')


def download_all_data_as_json():
    users_ref = db.collection('users')
    docs = users_ref.stream()
    user_dict = {}
    for doc in docs:
        user_dict[doc.id] = doc.to_dict()
    with open('user_data.json', 'w') as file:
        json.dump(user_dict, file)

# download_all_data_as_json()
# populate_restaurants_from_beli('sgoel9@berkeley.edu')
# populate_all_beli_restaurants()
# Close the browser
driver.quit()
```

[6 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]