# Project export: Rallie

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: CruzHacks 2025
- Tagline: Bureaucracy is hard, but community knowledge can make it easier
- Devpost: https://devpost.com/software/rallie
- GitHub: https://github.com/aidanjmaldonado/cruzhacks-2025
- Demo: http://rallie.tech/
- Team: 3 GitHub contributor(s) — Aidan Maldonado (29 commits), Ari Ferreros (25 commits), Sylvia Soup (9 commits)

## Devpost submission (written by the team)

### Inspiration

I am a student activist, and I have faced bureaucratic systems often. My team and I are building the tools we wish we had when we were learning about new systems, using advanced AI technologies

### What it does

We have a dataset we collected through interviewing UC Santa Cruz (UCSC) students at the hackathon using an agentic interviewer. You, as a student of UCSC, can learn from the trials of your predecessors.

### How we built it

We built it using Cloudflare for our infra, Gemini for our inference, MongoDB for our database, FastAPI for our backend, and a React front end. Other tools used: MongoDB, BAML, LangChain, Pydantic, Vite, Typescript, Python.

### Challenges we ran into

A challenge I spent a ton of time on was configuring a cloudflare tunnel. I had to step into unfamiliar territory, and failing meant letting my team mates down. Something I found difficult was learning how to communicate over http from the back end to the front end. It required a lot of coordination and exposed some oversights on both ends that we would have otherwise overlooked.

### Accomplishments we're proud of

Getting the front end and back end connected Having time to gather data from real interviews Getting the cloudflare site working

### What we learned

Database frameworks Different perspectives on bureaucracy Rallie's future: Pre Release Interview UCSC students to gather pre-release dataset Perfect RAG pipeline Add Wiki feature Chat history feature Chat history feature Beta Testing UX Testing, nail interaction design for agents Focus on gathering early adopters and building community Release A final version of Rallie is one where we have a stable knowledge-base of UCSC, that will last for decades. We aim to expand to other schools and communities, because the problems we face here at UCSC are not unique.

## README (from the GitHub repository)

# CruzHacks 2025

## Detected evidence (automated analysis)

Indexed codebase: 40 recognized source files, 133 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- LangChain (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code
- TypeScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (54 of 54)

```
.gitignore
backend/app.py
backend/baml_client/__init__.py
backend/baml_client/async_client.py
backend/baml_client/async_request.py
backend/baml_client/config.py
backend/baml_client/globals.py
backend/baml_client/inlinedbaml.py
backend/baml_client/parser.py
backend/baml_client/partial_types.py
backend/baml_client/sync_client.py
backend/baml_client/sync_request.py
backend/baml_client/tracing.py
backend/baml_client/type_builder.py
backend/baml_client/types.py
backend/baml_src/clients.baml
backend/baml_src/generators.baml
backend/baml_src/hazel_1.baml
backend/baml_src/hello.baml
backend/baml_src/lightbulb.baml
backend/baml_src/resume.baml
backend/chroma_vs/chroma.sqlite3
backend/docker-compose.yml
backend/Dockerfile
backend/hazel.py
backend/nut.py
backend/requirements.txt
backend/test_hazel.py
backend/zohar.py
docker-compose.yml
frontend/Dockerfile
frontend/index.html
frontend/package.json
frontend/requirements.txt
frontend/src/Animations/model_typing.jsx
frontend/src/Animations/typing.jsx
frontend/src/App.css
frontend/src/App.jsx
frontend/src/chatService.js
frontend/src/Common/ChatWindow.jsx
frontend/src/Common/Credits.jsx
frontend/src/Common/ErrorPage.jsx
frontend/src/Common/Home.jsx
frontend/src/Common/NavBar.jsx
frontend/src/Contexts/AppContext.jsx
frontend/src/credits.json
frontend/src/Desktop/Layout.jsx
frontend/src/index.css
frontend/src/Interview.jsx
frontend/src/main.jsx
frontend/src/Theme.js
frontend/src/User.jsx
frontend/vite.config.js
README.md
```

### Dependencies

- backend/requirements.txt: annotated-types@==0.7.0, anyio@==4.9.0, baml-py@==0.83.0, click@==8.1.8, dnspython@==2.7.0, fastapi@==0.115.12, h11@==0.14.0, idna@==3.10, langchain@==0.3.23, langchain_google_genai@==2.1.2, langchain-chroma@==0.2.2, langchain-community@==0.3.21, motor@==3.7.0, pydantic@==2.11.3, pydantic_core@==2.33.1, pymongo@==4.12.0, python-dotenv@==1.1.0, sniffio@==1.3.1, starlette@==0.46.1, typing_extensions@==4.13.2, typing-inspection@==0.4.0, uvicorn@==0.34.0
- frontend/package.json: @emotion/react@^11.14.0, @emotion/styled@^11.14.0, @eslint/js@^9.21.0, @mui/icons-material@^7.0.2, @mui/material@^7.0.2, @tailwindcss/vite@^4.1.3, @types/react@^19.0.10, @types/react-dom@^19.0.4, @vitejs/plugin-react@^4.3.4, autoprefixer@^10.4.21, concurrently@^9.1.2, eslint@^9.21.0, eslint-plugin-react-hooks@^5.1.0, eslint-plugin-react-refresh@^0.4.19, globals@^15.15.0, install@^0.13.0, npm@^11.3.0, postcss@^8.5.3, react@^19.0.0, react-dom@^19.0.0, react-router-dom@^7.5.0, tailwindcss@^4.1.3, vite@^6.2.0

### Recent commits (newest first)

- credits
- ui mui
- favicon nut
- morme mui
- new package.json
- with mui icons
- quality of life
- function
- Fix vector db
- Merge branch 'main' of https://github.com/aidanjmaldonado/cruzhacks-2025
- working user interaction
- Saved now
- remove le.py
- Combine APP/LE
- Merge branch 'main' of https://github.com/aidanjmaldonado/cruzhacks-2025
- styles
- Fix RAG
- Remove buggy package
- Questioning model
- clear cache btn

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

### docker-compose.yml

```yaml
version: "3"

services:
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "5000:5000"
    volumes:
      - ./backend/interviews:/app/interviews

```

### backend/Dockerfile

```
# run `docker container prune` to kill all cached builds
# build: docker buiild -t <name> .
# run: docker 

# Use a python base image
FROM python:3.12.7

# Set the working directory
WORKDIR /workspaces/
COPY . .

# Install required packages
RUN pip3 install --no-cache-dir -r requirements.txt
# RUN baml-cli generate

# Run app code and launch the server
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8081"]
```

### backend/requirements.txt

```
annotated-types==0.7.0
anyio==4.9.0
baml-py==0.83.0
click==8.1.8
dnspython==2.7.0
fastapi==0.115.12
h11==0.14.0
idna==3.10
motor==3.7.0
pydantic==2.11.3
pydantic_core==2.33.1
pymongo==4.12.0
python-dotenv==1.1.0
sniffio==1.3.1
starlette==0.46.1
typing-inspection==0.4.0
typing_extensions==4.13.2
uvicorn==0.34.0
langchain==0.3.23
langchain-community==0.3.21
langchain_google_genai==2.1.2
# chromadb==1.0.4
langchain-chroma==0.2.2
```

### backend/docker-compose.yml

```yaml
version: '3.8'
services:
  mariadb:
    image: bitnami/mariadb:latest
    environment:
      - MARIADB_ROOT_PASSWORD=your_root_password # Replace with a strong password
      - MARIADB_USER=bn_mediawiki
      - MARIADB_PASSWORD=your_mediawiki_password # Replace with a strong password
      - MARIADB_DATABASE=bitnami_mediawiki
    ports:
      - "3306:3306" # Optional: Expose the database port for external access (use with caution)
    volumes:
      - mariadb_data:/bitnami/mariadb/data

  mediawiki:
    image: bitnami/mediawiki:latest
    ports:
      - "4050:8080"
      - "8443:8443"
    environment:
      - ALLOW_EMPTY_PASSWORD=yes # For quick setup, but not recommended for production
      - MEDIAWIKI_DATABASE_HOST=mariadb
      - MEDIAWIKI_DATABASE_PORT_NUMBER=3306
      - MEDIAWIKI_DATABASE_USER=bn_mediawiki
      - MEDIAWIKI_DATABASE_PASSWORD=your_mediawiki_password # Use the same password as above
      - MEDIAWIKI_DATABASE_NAME=bitnami_mediawiki
    depends_on:
      - mariadb

volumes:
  mariadb_data:
    driver: local

```

### frontend/package.json

```
{
    "name": "frontend",
    "private": true,
    "version": "0.0.0",
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "lint": "eslint .",
        "preview": "vite preview"
    },
    "dependencies": {
        "@emotion/react": "^11.14.0",
        "@emotion/styled": "^11.14.0",
        "@mui/icons-material": "^7.0.2",
        "@mui/material": "^7.0.2",
        "@tailwindcss/vite": "^4.1.3",
        "install": "^0.13.0",
        "npm": "^11.3.0",
        "react": "^19.0.0",
        "react-dom": "^19.0.0",
        "react-router-dom": "^7.5.0"
    },
    "devDependencies": {
        "@eslint/js": "^9.21.0",
        "@types/react": "^19.0.10",
        "@types/react-dom": "^19.0.4",
        "@vitejs/plugin-react": "^4.3.4",
        "autoprefixer": "^10.4.21",
        "concurrently": "^9.1.2",
        "eslint": "^9.21.0",
        "eslint-plugin-react-hooks": "^5.1.0",
        "eslint-plugin-react-refresh": "^0.4.19",
        "globals": "^15.15.0",
        "postcss": "^8.5.3",
        "tailwindcss": "^4.1.3",
        "vite": "^6.2.0"
    }
}

```

### backend/app.py

```python
from hazel import Hazel
from nut import Nut
from fastapi import FastAPI, HTTPException, Header
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, Annotated
from motor.motor_asyncio import AsyncIOMotorClient
from bson import ObjectId
from dotenv import load_dotenv
import uuid
import os

# Create app and interviews directory
# (Run separately with uvicorn app:app --reload --host 0.0.0.0 --port 8081)
app = FastAPI()

# Allow requests from frontend (localhost:5173)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # or ["*"] for all
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
# Questions of interest: How do we have FastAPI have sessions - so that our LLM Api has differentiation between users?
# app.add_middleware(
#     myuserauth
# )
def myuserauth():
    pass

# Pydantic Payloads
class StartPayload(BaseModel):
    user_ID: str
    session_ID: str
    initial_question: str

class SubmitPayload(BaseModel):
    question: str
    answer: str

class SubmitPayloadPrompt(BaseModel):
    chat: list

# Connect to MongoDB TO-DO
try:
    client = AsyncIOMotorClient(
        "mongodb+srv://aijmaldo:7Bk3rNMQHpcMHDpF@interviewcluster.coi67ff.mongodb.net/?retryWrites=true&w=majority&appName=interviewCluster",
        serverSelectionTimeoutMS=20000
    )
    # Test the connection
    client.admin.command('ping')
    print("Connected successfully to MongoDB")
except Exception as e:
    print(f"MongoDB connection error: {e}")

db = client["interviews-db"]
collection = db["interviews"]

@app.get("/interview/dog")
async def start_interview():
    return {"dog":"fluff"}

@app.get("/interview")
async def start_interview(user_auth: Annotated[Optional[str], Header(alias="Authorization")] = None):

    token = None
    if user_auth and user_auth.startswith("Bearer "):
        token = user_auth.removeprefix("Bearer ").strip()

    # Determine if new or returning user
    if not token:
        # New user: generate unique ID
        user_ID = str(uuid.uuid4())
        name = ""
    else:
        # Returning user: keep existing id
        user_ID = token
        print("User token", token)

    # Determine if new or returning user
    if not user_auth:
        # New user: generate unique id
        user_ID = user_ID = str(uuid.uuid4())
        name = ""
    else:
        # Returning user: keep existing id
        user_ID = token
        print("User token", user_auth)
        # Set existing user-name
        latest = await collection.find_one({"userID": user_ID}, sort=[("interview_number", -1)])
        if not latest:
            raise HTTPException(status_code=404, detail="Returning user ID not found.")
        name = latest["name"]
        
    # Get the amount of interviews created by User
    count = await collection.count_documents({"userID": user_ID})
    interview_num = count + 1

    # Create new interview document
    new_interview = {
        "userID": user_ID,
        "name": name,
        "interview_number": interview_num,
        "conversation": []
    }

    # Insert into MongoDB
    result = await collection.insert_one(new_interview)

    # Initial question:
    if not user_auth:
        initial_question = """Hi there!\nMy name is Hazel, and I'm gathering stories from UCSC students. What's your name?\nI work with Rallie, and we aim to understand and improve UCSC's systems for students. Are you comfortable with me using your stories, noting that they may not be anonymized initially? Our goal is to build a chatbot that helps students navigate challenges."""
    else:
        initial_question = f"Hi {name}! I really appreciate you being willing to talk to me. To get us started, could you tell me about a time you felt particularly frustrated while navigating something at UCSC? What specifically was the issue, and what steps did you take to resolve it?"

    # Generate unique Session ID
    session_ID = result.inserted_id

    # Return Response
    return {
        "user_ID": user_ID,
        "session_ID": str(session_ID),
        "initial_question": initial_question,
        "name": name
    }

@app.post("/interview/{session_ID}")
async def submit_answer(session_ID, payload: SubmitPayload, user_auth: Annotated[str, Header(alias="Authorization")]):

    load_dotenv()

    # Convert session id into valid string
    try:
        obj_id = ObjectId(session_ID)
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid session ID.")

    # Open most recent (Current) interview with this user
    latest_interview = await collection.find_one(
        {"_id": obj_id},
        sort=[("interview_number", -1)]
    )
    
    if not latest_interview:
        raise HTTPException(status_code=404, detail="Interview not found.")

    # Update MongoDB .json file with {"question": "-", "answer", "-"}
    # Add new conversation turn
    conversation_entry = {
        "Hazel": payload.question,
        "User": payload.answer,
    }
    
    # Update the document by pushing to the conversation array
    await collection.update_one(
        {"_id": obj_id},
        {"$push": {"conversation": conversation_entry}}
    )

    # Get updated document to check conversation length
    updated_interview = await collection.find_one({"_id": obj_id})
    conversation = updated_interview["conversation"]
    messages = []
    # General procedure
    for message in conversation:
        # Append assistant message
        messages.append({"role": "hazel", "content": message["Hazel"]})
        # Append user message
        messages.append({"role": "user", "content": message["User"]})

    name = updated_interview.get("name", "Unknown")

    # Generate next quesiton
    hazel = Hazel()
    next_question, next_state, updated_name, topic = hazel.get_message(
        messages=messages,
        state=("INITIAL" if name == "" else "SHARING")
    )
    # Update Mongo DB with Hazel information
    await collection.update_o
[truncated — 827 more characters]
```

### frontend/src/main.jsx

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { CssBaseline } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import App from './App';
import theme from './theme.js';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <App />
    </ThemeProvider>
  </React.StrictMode>
);

```

### frontend/src/App.jsx

```javascript
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import Home from './Common/Home'; // New component to handle sessionId fetch
import Interview from './Interview';
import ErrorPage from './Common/ErrorPage'; // Optional: for error handling
import AppContextProvider from './Contexts/AppContext';
import Credits from './Common/Credits';
import User from './User';
// import AppHeader from './Common/NavBar';
const router = createBrowserRouter([
  {
    path: '/',
    element: <User></User>
  },
  {
    path: '/chat',
    element: <Home />, // Fetches sessionId and navigates
  },
  {
    path: '/chat/:sessionId',
    element: <Interview />,
  },
  {
    path: '/error',
    element: <ErrorPage />,
  },
  // Fallback for unmatched routes
  {
    path: '*',
    element: <Navigate to="/" replace />,
  },
  {
    path: '/credits',
    element: <Credits />
  }
]);

function App() {
  return (
  <AppContextProvider>
    <RouterProvider router={router} />
    </AppContextProvider>)
  
}

export default App;
```

### frontend/vite.config.js

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

// https://vite.dev/config/
export default defineConfig({
  plugins: [    tailwindcss(),
    react()],
  server: {
    allowedHosts: ["miku"]
  }
})
```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

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