# Project export: Prep.AI

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: Cal Hacks 11.0
- Tagline: Land your next Product Manager role – because we know you’re ready, no matter the economy. With Prep.AI, you'll walk into your next interview ready to ace it. Secure your future in product now!
- Devpost: https://devpost.com/software/prep-ai
- GitHub: https://github.com/anagha1999/AInterview
- Team: 1 GitHub contributor(s) — Anagha Ananth (1 commits)

## Devpost submission (written by the team)

### Inspiration

In today’s competitive job market, it's alarming to see MBA graduates from top schools struggling to secure employment, while many immigrants find themselves overlooked despite their qualifications. For these candidates, landing a job can often hinge on a single interview—the make-or-break opportunity that can determine their future. Breaking into Product Management can feel like a formidable challenge without the right guidance. The role demands a deep, holistic understanding of business, collaboration across cross-functional teams, and a strong obsession on delivering an exceptional customer experience. There’s no one-size-fits-all formula to measure a candidate’s potential as a product manager, making the path even more intimidating for those without prior experience. Inspired by this gap, we’re driven to provide a solution—one where candidates can upload their resume and the job description of their desired role, and in return, receive a tailored interview experience that helps them effectively prepare, sharpen their skills, and build confidence for their actual interview.

### What it does

Our product allows customers of all backgrounds to interview for PM roles and receive feedback on their interview. We also perform an analysis on the interview transcripts and give feedback on the interview on a scale of 1 to 10.

### How we built it

We used Reflex for both the front-end and back-end web development, enabling us to build a fully functional web application entirely in Python without needing to handle separate frameworks or languages for the front-end. We built a Full stack app that uses vapi ai to record and transcribe interviews. We further exploit the capabilities of vapi to generate questions and follow ups to ask the user using gpt 4.0 model We also use the gpt4.0 model and prompt engineer the vapi app to analyse the transcribed interviews and rate the users feedback based on our defined rubric.

### Challenges we ran into

Zeroing in on a voice agent was very difficult as there were a lot of options and each model had their own pros and cons. Prompt engineering to get the right feedback from the voice agent was hard to perform.

### Accomplishments we're proud of

We were able to build an AI voice agent that can conduct product manager interviews, capable of providing target company based interviews. Gained exposure to voice agents and explored the prompt generation to enhance the accuracy of the ai model.

### What we learned

We learnt about the extensive capabilities of vapi and voice agents in general. We learnt how to use AI agents to take interviews and give feedback to customers. Learnt the python reflex framework and used it develop the FE & BE.

### What's next

We plan on analysing the performance of the candidate in the interview and assigning a schedule that he/she can follow to improve his interview score. Introduce a progress tracker that updates iteratively based on the mock interviews taken by the candidates. Use Prompt engineering to further refine the evaluation rubric iteratively to generate more accurate and authentic reports. Take in the resume and job description of the candidate into the knowledge base creating context for each user that interacts with the AI voice agent, and tailor interview experiences.

## README (from the GitHub repository)

# Prep.AI

## Inspiration
Breaking into Product Management can feel like a formidable challenge without the right guidance. The role demands a deep, holistic understanding of business, collaboration across cross-functional teams, and a strong obsession on delivering an exceptional customer experience. There’s no one-size-fits-all formula to measure a candidate’s potential as a product manager, making the path even more intimidating for those without prior experience. Inspired by this gap, we’re driven to provide a solution—one where candidates can upload their resume and the job description of their desired role, and in return, receive a tailored interview experience that helps them effectively prepare, sharpen their skills, and build confidence for their actual interview.

## What it does
Our product allows customers of all backgrounds to interview for PM roles and receive feedback on their interview. We also perform an analysis on the interview transcipts and give feedback on the interview on a scale of 1 to 10.

## Detected evidence (automated analysis)

Indexed codebase: 24 recognized source files, 45 KB.
- HTML (language) — detected in the code
- Python (language) — detected in the code
- TypeScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (31 of 31)

```
.DS_Store
aiinterview-app/.gitignore
aiinterview-app/aiinterview_app/__init__.py
aiinterview-app/aiinterview_app/aiinterview_app.py
aiinterview-app/aiinterview_app/backend/__init__.py
aiinterview-app/aiinterview_app/backend/backend.py
aiinterview-app/aiinterview_app/components/__init__.py
aiinterview-app/aiinterview_app/components/form_field.py
aiinterview-app/aiinterview_app/components/interviewRecorder.py
aiinterview-app/aiinterview_app/components/report.py
aiinterview-app/aiinterview_app/components/stats_cards.py
aiinterview-app/aiinterview_app/components/status_badges.py
aiinterview-app/aiinterview_app/components/videocam.py
aiinterview-app/aiinterview_app/components/webcam.py
aiinterview-app/aiinterview_app/views/__init__.py
aiinterview-app/aiinterview_app/views/navbar.py
aiinterview-app/alembic.ini
aiinterview-app/alembic/env.py
aiinterview-app/alembic/README
aiinterview-app/alembic/script.py.mako
aiinterview-app/alembic/versions/277cad49d2b0_.py
aiinterview-app/alembic/versions/333b61328fdd_.py
aiinterview-app/alembic/versions/7aaec6b87d88_.py
aiinterview-app/alembic/versions/e565fdc23e6c_something_changed.py
aiinterview-app/README.md
aiinterview-app/requirements.txt
aiinterview-app/rxconfig.py
README.md
vapi-app/DG-key
vapi-app/interviewer-api.py
vapi-app/templates/index.html
```

### Dependencies

- aiinterview-app/requirements.txt: psycopg2-binary, reflex@>=0.5.3

### Recent commits (newest first)

- git ignore update
- minor changes
- Vidcam update
- add README
- add README
- add README
- combine interview and front page
- changes with knowledge base
- score page
- Update VAPI credentials in videocam.py
- add data store
- added relevant API keys
- adding vapi
- webcam
- Frist page UI
- reflex
- Initial commit

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

### aiinterview-app/requirements.txt

```
reflex>=0.5.3
psycopg2-binary
```

### aiinterview-app/rxconfig.py

```python
import reflex as rx

config = rx.Config(
    app_name="aiinterview_app",
)
```

### vapi-app/interviewer-api.py

```python
from flask import Flask, request, render_template
from vapi_python import Vapi

#app = Flask(__name__)

# Your VAPI assistant credentials
API_KEY = "2b69f505-2aac-410c-bd38-6b8da2baddf8" #anagha: "ef315768-0514-4089-8c7e-17e831657af6"
ASSISTANT_ID = "4d742d2d-2afe-484b-8eab-4fd5fa41e825" # anagha: cc4108b8-369e-4ece-8b69-58d09958da28"

# Initialize the VAPI assistant
#assistant = Assistant(api_key=API_KEY, assistant_id=ASSISTANT_ID)

# @app.route('/', methods=['GET', 'POST'])
# def home():
#     assistant_response = None
#     user_message = None

#     if request.method == 'POST':
#         # Get the message from the form
#         user_message = request.form.get('message')
        
#         # Send message to VAPI assistant and get response
#         if user_message:
#             response = vapi.send_message(user_message)
#             assistant_response = response.get('response', 'No response received')

    # Render the HTML template with the assistant's response
    
    # return render_template('index.html', user_message=user_message, assistant_response=assistant_response)

if __name__ == "__main__":
    vapi = Vapi(api_key=API_KEY)
    vapi.start(assistant_id=ASSISTANT_ID)
    # app.run(debug=True)

```

### vapi-app/templates/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat with VAPI Assistant</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
            margin: 0;
            padding: 20px;
        }
        .chat-container {
            max-width: 600px;
            margin: auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
        input[type="text"], input[type="submit"] {
            width: 100%;
            padding: 10px;
            margin: 10px 0;
            border-radius: 4px;
            border: 1px solid #ccc;
        }
        .message-box {
            margin-top: 20px;
        }
        .user-message, .assistant-message {
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        .user-message {
            background-color: #e0f7fa;
        }
        .assistant-message {
            background-color: #fce4ec;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <h1>Chat with VAPI Assistant</h1>
        
        <!-- Form for user input -->
        <form method="POST" action="/">
            <input type="text" name="message" placeholder="Enter your message here" required>
            <input type="submit" value="Send">
        </form>

        <!-- Display user message and assistant response -->
        <div class="message-box">
            {% if user_message %}
                <div class="user-message">You: {{ user_message }}</div>
            {% endif %}
            {% if assistant_response %}
                <div class="assistant-message">Assistant: {{ assistant_response }}</div>
            {% endif %}
        </div>
    </div>
</body>
</html>

```

### aiinterview-app/alembic/env.py

```python
from logging.config import fileConfig

from alembic import context
from sqlalchemy import engine_from_config, pool

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(connection=connection, target_metadata=target_metadata)

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

```

### aiinterview-app/aiinterview_app/aiinterview_app.py

```python
import reflex as rx
from .components.stats_cards import stats_cards_group
from .components.videocam import webcam_upload_component
from .components.report import create_interview_scores_card
from .views.navbar import navbar
from .backend.backend import State


def create_content_section():
    return rx.flex(
            rx.vstack(
                rx.badge(
                    rx.icon(tag="message-circle-question", size=28),
                    rx.heading("Interview Room", size="6"),
                    color_scheme="green",
                    radius="large",
                    align="center",
                    variant="surface",
                    padding="0.65rem",
                ),
                rx.flex(
                    rx.card(
                        rx.center(
                        webcam_upload_component("webcam"),
                        width="100%",
                        height="100%",
                        ),
                    ),
                    rx.box(
                        rx.vstack(
                            rx.card(
                                rx.image(
                                alt="interviewer",
                                src="https://reflex-hosting-dev-flexgen.s3.us-west-2.amazonaws.com/replicate/38gX1mFs9ZpQNhfoAex3yaUgyzvyfqyPIgFndLtgPevsdPiOB/out-0.webp",
                                height="auto",
                                width="100%",
                                ),
                                background_color="#ffffff",
                                overflow="hidden",
                                border_radius="0.5rem",
                                box_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
                            ),
                            spacing="6",
                        ),
                        width="25%",
                    ),
                    rx.box(
                        rx.button(
                            "Finish",
                            color_scheme="red",
                            size="4",
                            width="300px",
                            on_click=rx.redirect("/report"),
                            ),
                        position="absolute",
                        bottom="2rem",
                        right="5rem",
                    ),
                    spacing = "5",
                    display="flex",
                    height="90vh",
                    padding_bottom="2em",
                ),
            ),
            id="contentSection",
            # display="none",
            padding="2em",
            spacing = "5",
            display="flex",
            max_width="100rem",
            height="100vh",
            width="100%",
            justify="center",
            wrap="wrap",
            )
    
def index() -> rx.Component:
    return rx.vstack(
        navbar(),
        stats_cards_group(),
        rx.center(
            rx.button(
                rx.text("Start Interview", size="4", display=["none", "none", "block"]),
                id="startButton",
                size="3",
                # on_click=rx.redirect("/interview")
            ),
            spacing="6",
            width="100%",
        ),
        rx.box(
            create_content_section(),
            rx.script(
                """
                document.getElementById('startButton').addEventListener('click', function() {
                    var contentSection = document.getElementById('contentSection');
                    contentSection.classList.toggle('hidden');
                });
            """
            ),
            background_color="#F3F4F6",
            min_height="100vh",
        ),
        width="100%",
        spacing="6",
        justify = "center",
        padding_x=["1.5em", "1.5em", "3em"],
    )

def interview() -> rx.Component:
    return rx.flex(
        rx.vstack(
            rx.badge(
                rx.icon(tag="message-circle-question", size=28),
                rx.heading("Interview Room", size="6"),
                color_scheme="green",
                radius="large",
                align="center",
                variant="surface",
                padding="0.65rem",
            ),
            rx.flex(
                rx.card(
                    rx.center(
                    webcam_upload_component("webcam"),
                    width="100%",
                    height="100%",
                    ),
                ),
                rx.box(
                    rx.vstack(
                        rx.card(
                            rx.image(
                            alt="interviewer",
                            src="https://reflex-hosting-dev-flexgen.s3.us-west-2.amazonaws.com/replicate/38gX1mFs9ZpQNhfoAex3yaUgyzvyfqyPIgFndLtgPevsdPiOB/out-0.webp",
                            height="auto",
                            width="100%",
                            ),
                            background_color="#ffffff",
                            overflow="hidden",
                            border_radius="0.5rem",
                            box_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
                        ),
                        spacing="6",
                    ),
                    width="25%",
                ),
                rx.box(
                    rx.button(
                        "Finish",
                        color_scheme="red",
                        size="4",
                        width="300px",
                        on_click=rx.redirect("/report"),
                        ),
                    position="absolute",
                    bottom="2rem",
                    right="5rem",
                ),
                spacing = "5",
                display="flex",
                height="90vh",
                padding_bottom="2em",
            ),
        ),
        padding="2em",
        spacin
[truncated — 971 more characters]
```

### aiinterview-app/aiinterview_app/components/interviewRecorder.py

```python
import reflex as rx
from reactpy import component, html

@component
def AudioRecorder():
    return html.div(
        html.button("Start Recording", id="start-recording"),
        html.button("Stop Recording", id="stop-recording"),
        html.audio(id="audio-playback", controls=True),
    )

def index():
    return rx.vstack(
        rx.heading("Audio Recorder"),
        AudioRecorder(),
    )
```

### aiinterview-app/aiinterview_app/components/status_badges.py

```python
import reflex as rx


def _badge(icon: str, text: str, color_scheme: str):
    return rx.badge(
        rx.icon(icon, size=16),
        text,
        color_scheme=color_scheme,
        radius="full",
        variant="soft",
        size="3",
    )


def status_badge(status: str):
    badge_mapping = {
        "Delivered": ("check", "Delivered", "green"),
        "Pending": ("loader", "Pending", "yellow"),
        "Cancelled": ("ban", "Cancelled", "red"),
    }
    return _badge(*badge_mapping.get(status, ("loader", "Pending", "yellow")))

```

### aiinterview-app/aiinterview_app/views/navbar.py

```python
import reflex as rx

def navbar():
    return rx.flex(
        rx.badge(
            rx.icon(tag="table-2", size=28),
            rx.heading("AInterview", size="6"),
            color_scheme="green",
            radius="large",
            align="center",
            variant="surface",
            padding="0.65rem",
        ),
        rx.spacer(),
        rx.hstack(
            rx.logo(),
            rx.color_mode.button(),
            align="center",
            spacing="3",
        ),
        spacing="2",
        flex_direction=["column", "column", "row"],
        align="center",
        width="100%",
        top="0px",
        padding_top="2em",
    )

```

### aiinterview-app/aiinterview_app/components/form_field.py

```python
import reflex as rx


def form_field(
    label: str,
    placeholder: str,
    type: str,
    name: str,
    icon: str,
    default_value: str = "",
) -> rx.Component:
    return rx.form.field(
        rx.flex(
            rx.hstack(
                rx.icon(icon, size=16, stroke_width=1.5),
                rx.form.label(label),
                align="center",
                spacing="2",
            ),
            rx.form.control(
                rx.input(
                    placeholder=placeholder, type=type, default_value=default_value
                ),
                as_child=True,
            ),
            direction="column",
            spacing="1",
        ),
        name=name,
        width="100%",
    )

```

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