# Project export: Iris

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 2026
- Tagline: Lightweight AI-powered security and theft detection for small businesses.
- Devpost: https://devpost.com/software/iris-win85f
- GitHub: https://github.com/tyler-ham05/cruzhacks2026
- Video: https://www.youtube.com/embed/wnYt14Syrz8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — tyler-ham05 (14 commits), Profese (12 commits), Koolaidpick (9 commits), BeezyBMO70 (6 commits)

## Devpost submission (written by the team)

### Inspiration

Shoplifting has seen a surge in incidents over the last decade, with retailers reporting a 93% increase from 2019-2023 alone. Although large companies like Target and Whole foods can afford security guards and live camera monitoring, this puts local and small businesses at an even greater risk for repeat shoplifters.

### What it does

Iris aims to provide lightweight theft-detection software that can monitor and classify theft incidents with live updates, while requiring zero specialized hardware other than a computer and a camera. A store owner can mark out zones designating exits and cashiers, and the model marks customers that walk in and out of a store without walking near a cashier suspicious. The software clips the recording, which is handled by the Gemini API to confirm if misconduct actually occurred, where the results are then sent to our dashboard

### How we built it

We used openCV as the basis for our detection software, and the Gemini API to confirm and classify misconduct. The dashboard is built in react, and server with Express.js, which we deployed to Vercel. Additionally, OAuth handled authentication for the dashboard. For databasing, we used MongoDB for user data, and Mux for video storage and playback.

### Challenges we ran into

We tried a lot of different approaches when considering what should be considered as suspicious, as detecting "suspicious actions". We decided to settle on tracking customers that exit the store without walking towards the cashier, in order to combat shoplifters that quickly steal on their way out of a store

### Accomplishments we're proud of

Implementing momentum and individual object tracking within the model were both great accomplishments for this project.

### What we learned

Computer Vision is very finicky

### What's next

Refining the Computer Vision model would be first on our list of improvements. Past that though, it would be cool to establish proprietary hardware that stores could use if they didn't have a camera or computer available

## README (from the GitHub repository)

# cruzhacks2026

## Detected evidence (automated analysis)

Indexed codebase: 17 recognized source files, 34 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- MongoDB (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code

## Codebase structure (from repository index)

### Files (28 of 28)

```
.gitignore
cruzHacksProject/.gitignore
cruzHacksProject/index.html
cruzHacksProject/package.json
cruzHacksProject/src/App.css
cruzHacksProject/src/App.jsx
cruzHacksProject/src/homepage.jsx
cruzHacksProject/src/incident.css
cruzHacksProject/src/IncidentDetails.jsx
cruzHacksProject/src/index.css
cruzHacksProject/src/login.css
cruzHacksProject/src/main.jsx
cruzHacksProject/src/old_App.css
cruzHacksProject/src/old_App.jsx
cruzHacksProject/vite.config.js
detection/.gitignore
detection/detection.py
detection/GeminiMux.py
detection/model/best.pt
detection/model/people.pt
detection/requirements.txt
README.md
requirements.txt
server/.gitignore
server/index.js
server/modules/entry.js
server/package.json
server/vercel.json
```

### Dependencies

- cruzHacksProject/package.json: @auth0/auth0-react@^2.11.0, @mux/mux-player-react@^3.10.2, @types/react@^18.0.27, @types/react-dom@^18.0.10, @vitejs/plugin-react@^5.1.2, axios@^1.13.2, react@^18.2.0, react-dom@^18.2.0, react-router-dom@^7.12.0, react-wavify@^1.11.1, vite@^7.3.1
- server/package.json: cors@^2.8.5, dotenv@^16.3.1, express@^4.21.2, mongoose@^7.5.0, morgan@^1.10.0, nodemon@^3.0.1

### Recent commits (newest first)

- adwdwad
- Testing 123
- Testing 123
- smth
- Merge branch 'main' of https://github.com/tyler-ham05/cruzhacks2026
- Going
- schema update
- updated dependencies
- updated object detection and ai pipeline
- fixed merge
- logout button
- Merge branch 'main' of https://github.com/tyler-ham05/cruzhacks2026
- fixed image src
- please
- Fixed small issue
- Merge branch 'main' of https://github.com/tyler-ham05/cruzhacks2026
- Fixed small issue
- please
- Finished v1 of computer vision component
- Merge branch 'main' of https://github.com/tyler-ham05/cruzhacks2026

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

### requirements.txt

```
��a c c e l e r a t e = = 1 . 1 2 . 0  
 a d d i c t = = 2 . 4 . 0  
 a i o f i l e s = = 2 5 . 1 . 0  
 a i o h a p p y e y e b a l l s = = 2 . 6 . 1  
 a i o h t t p = = 3 . 1 0 . 1 1  
 a i o h t t p - r e t r y = = 2 . 8 . 3  
 a i o i c e = = 0 . 1 0 . 2  
 a i o r t c = = 1 . 1 4 . 0  
 a i o s i g n a l = = 1 . 4 . 0  
 a i o s q l i t e = = 0 . 2 2 . 1  
 a n n o t a t e d - t y p e s = = 0 . 7 . 0  
 a n t h r o p i c = = 0 . 4 9 . 0  
 a n t l r 4 - p y t h o n 3 - r u n t i m e = = 4 . 9 . 3  
 a n y a s c i i = = 0 . 3 . 3  
 a n y i o = = 4 . 1 2 . 1  
 A P S c h e d u l e r = = 3 . 1 1 . 2  
 a r g o n 2 - c f f i = = 2 5 . 1 . 0  
 a r g o n 2 - c f f i - b i n d i n g s = = 2 5 . 1 . 0  
 a s y n c u a = = 1 . 1 . 8  
 a t t r s = = 2 5 . 4 . 0  
 a v = = 1 4 . 2 . 0  
 b a c k o f f = = 2 . 2 . 1  
 b i t s a n d b y t e s = = 0 . 4 7 . 0  
 b o t o 3 = = 1 . 4 1 . 5  
 b o t o c o r e = = 1 . 4 1 . 5  
 c a c h e t o o l s = = 5 . 5 . 2  
 c e r t i f i = = 2 0 2 6 . 1 . 4  
 c f f i = = 2 . 0 . 0  
 c h a r s e t - n o r m a l i z e r = = 3 . 4 . 4  
 c l i c k = = 8 . 1 . 8  
 c o l o r a m a = = 0 . 4 . 6  
 c o l o r e d l o g s = = 1 5 . 0 . 1  
 c o n t o u r p y = = 1 . 3 . 3  
 c r y p t o g r a p h y = = 4 6 . 0 . 3  
 c y c l e r = = 0 . 1 2 . 1  
 C y t h o n = = 3 . 0 . 1 2  
 d a t a c l a s s e s - j s o n = = 0 . 6 . 7  
 d e e p - s o r t - r e a l t i m e = = 1 . 3 . 2  
 d e f u s e d x m l = = 0 . 7 . 1  
 d i l l = = 0 . 4 . 0  
 d i s t r o = = 1 . 9 . 0  
 d n s p y t h o n = = 2 . 8 . 0  
 d o c k e r = = 7 . 1 . 0  
 d o c o p t = = 0 . 6 . 2  
 e a s y o c r = = 1 . 7 . 2  
 e i n o p s = = 0 . 8 . 1  
 f a s t a p i = = 0 . 1 1 5 . 1 4  
 f i l e l o c k = = 3 . 1 7 . 0  
 f l a t b u f f e r s = = 2 5 . 1 2 . 1 9  
 f o n t t o o l s = = 4 . 6 1 . 1  
 f r o z e n l i s t = = 1 . 8 . 0  
 f s s p e c = = 2 0 2 6 . 1 . 0  
 f t f y = = 6 . 3 . 1  
 g o o g l e - a u t h = = 2 . 4 7 . 0  
 g o o g l e - c r c 3 2 c = = 1 . 8 . 0  
 g o o g l e - g e n a i = = 1 . 5 9 . 0  
 h 1 1 = = 0 . 1 6 . 0  
 h 5 p y = = 3 . 1 5 . 1  
 h t t p c o r e = = 1 . 0 . 9  
 h t t p x = = 0 . 2 8 . 1  
 h u g g i n g f a c e - h u b = = 0 . 3 6 . 0  
 h u m a n f r i e n d l y = = 1 0 . 0  
 h y d r a - c o r e = = 1 . 3 . 2  
 i d n a = = 3 . 1 1  
 i f a d d r = = 0 . 2 . 0  
 I m a g e I O = = 2 . 3 7 . 2  
 i n f e r e n c e - m o d e l s = = 0 . 1 8 . 4  
 i n i c o n f i g = = 2 . 3 . 0  
 i o p a t h = = 0 . 1 . 1 0  
 i s o d a t e = = 0 . 7 . 2  
 J i n j a 2 = = 3 . 1 . 6  
 j i t e r = = 0 . 1 2 . 0  
 j m e s p a t h = = 1 . 0 . 1  
 k i w i s o l v e r = = 1 . 4 . 9  
 l a n g d e t e c t = = 1 . 0 . 9  
 l a z y _ l o a d e r = = 0 . 4  
 l o g g e r = = 1 . 4  
 l x m l = = 6 . 0 . 2  
 m a r k d o w n - i t - p y = = 4 . 0 . 0  
 M a r k u p S a f e = = 3 . 0 . 3  
 m a r s h m a l l o w = = 3 . 2 6 . 2  
 m a t p l o t l i b = = 3 . 1 0 . 8  
 m d u r l = = 0 . 1 . 2  
 m l _ d t y p e s = = 0 . 5 . 4  
 m p m a t h = = 1 . 3 . 0  
 m u l t i d i c t = = 6 . 7 . 0  
 m u x _ p y t h o n   @   g i t + h t t p s : / / g i t h u b . c o m / m u x i n c / m u x - p y t h o n . g i t @ 4 9 6 f 6 2 c 3 a 3 e 3 9 5 7 1 5 a 2 9 0 5 f b d 6 f c 4 0 4 1 5 1 3 0 8 3 8 d  
 m y p y _ e x t e n s i o n s = = 1 . 1 . 0  
 n e t w o r k x = = 3 . 6 . 1  
 n i n j a = = 1 . 1 3 . 0  
 n u m 2 w o r d s = = 0 . 5 . 1 4  
 n u m p y = = 2 . 2 . 6  
 n v i d i a - m l - p y = = 1 2 . 5 7 5 . 5 1  
 o m e g a c o n f = = 2 . 3 . 0  
 o n n x = = 1 . 2 0 . 1  
 o n n x r u n t i m e = = 1 . 2 1 . 1  
 o n v i f - z e e p - a s y n c = = 2 . 0 . 0  
 o p e n a i = = 1 . 1 0 9 . 1  
 o p e n c v - c o n t r i b - p y t h o n = = 4 . 1 0 . 0 . 8 4  
 o p e n c v - p y t h o n = = 4 . 1 0 . 0 . 8 4  
 o p e n c v - p y t h o n - h e a d l e s s = = 4 . 1 2 . 0 . 8 8  
 p a c k a g i n g = = 2 4 . 2  
 p a h o - m q t t = = 1 . 6 . 1  
 p a n d a s = = 2 . 3 . 3  
 p e f t = = 0 . 1 7 . 1  
 p i l l o w = = 1 1 . 3 . 0  
 p l a t f o r m d i r s = = 4 . 5 . 1  
 p l u g g y = = 1 . 6 . 0  
 p o l a r s = = 1 . 3 7 . 1  
 p o l a r s - r u n t i m e - 3 2 = = 1 . 3 7 . 1  
 p o r t a l o c k e r = = 3 . 2 . 0  
 p r o m e t h e u s - f a s t a p i - i n s t r u m e n t a t o r = = 6 . 0 . 0  
 p r o m e t h e u s _ c l i e n t = = 0 . 2 4 . 1  
 p r o p c a c h e = = 0 . 4 . 1  
 p r o t o b u f = = 6 . 3 3 . 4  
 p s u t i l = = 7 . 2 . 1  
 p y - c p u i n f o = = 9 . 0 . 0  
 p y a s n 1 = = 0 . 6 . 2  
 p y a s n 1 _ m o d u l e s = = 0 . 4 . 2  
 p y b a s e 6 4 = = 1 . 0 . 2  
 p y c l i p p e r = = 1 . 4 . 0  
 p y c o c o t o o l s = = 2 . 0 . 1 1  
 p y c p a r s e r = = 2 . 2 3  
 p y d a n t i c = = 2 . 1 2 . 5  
 p y d a n t i c - s e t t i n g s = = 2 . 7 . 1  
 p y d a n t i c _ c o r e = = 2 . 4 1 . 5  
 p y d o t = = 2 . 0 . 0  
 p y e e = = 1 3 . 0 . 0  
 P y g m e n t s = = 2 . 1 9 . 2  
 P y J W T = = 2 . 1 0 . 1  
 p y l i b s r t p = = 1 . 0 . 0  
 p y l o g i x = = 1 . 0 . 5  
 p y m o d b u s = = 3 . 8 . 3  
 p y O p e n S S L = = 2 5 . 3 . 0  
 p y p a r s i n g = = 3 . 3 . 1  
 p y p d f i u m 2 = = 4 . 3 0 . 0  
 p y r e a d l i n e 3 = = 3 . 5 . 4  
 p y t e s t = = 8 . 4 . 2  
 p y t h o n - b i d i = = 0 . 6 . 7  
 p y t h o n - d a t e u t i l = = 2 . 9 . 0 . p o s t 0  
 p y t h o n - d o c t r = = 0 . 1 1 . 0  
 p y t h o n - d o t e n v = = 1 . 2 . 1  
 p y t z = = 2 0 2 5 . 2  
 p y v i p s = = 2 . 2 . 3  
 p y w i n 3 2 = = 3 1 1  
 P y Y A M L = = 6 . 0 . 3  
 q r c o d e = = 8 . 0  
 R a p i d F u z z = = 3 . 1 4 . 3  
 r e d i s = = 5 . 0 . 8  
 r e g e x = = 2 0 2 6 . 1 . 1 5  
 r e q u e s t s = = 2 . 3 2 . 5  
 r e q u e s t s - f i l e = = 3 . 0 . 1  
 r e q u e s t s - t o o l b e l t = = 1 . 0 . 0  
 r f - c l i p = = 1 . 1  
 R F - S A M - 2 = = 1 . 0 . 2  
 r f - s e g m e n 
[truncated — 1796 more characters]
```

### cruzHacksProject/package.json

```
{
  "name": "cruzhacksproject",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@auth0/auth0-react": "^2.11.0",
    "@mux/mux-player-react": "^3.10.2",
    "axios": "^1.13.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^7.12.0",
    "react-wavify": "^1.11.1"
  },
  "devDependencies": {
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-react": "^5.1.2",
    "vite": "^7.3.1"
  }
}

```

### server/package.json

```
{
  "name": "server",
  "version": "0.0.1",
  "description": "tooling:",
  "homepage": "https://github.com/tyler-ham05/cruzhacks2026#readme",
  "bugs": {
    "url": "https://github.com/tyler-ham05/cruzhacks2026/issues"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/tyler-ham05/cruzhacks2026.git"
  },
  "license": "ISC",
  "author": "",
  "type": "commonjs",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.21.2",
    "mongoose": "^7.5.0",
    "morgan": "^1.10.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

```

### detection/requirements.txt

```
��a c c e l e r a t e = = 1 . 1 2 . 0  
 a d d i c t = = 2 . 4 . 0  
 a i o f i l e s = = 2 5 . 1 . 0  
 a i o h a p p y e y e b a l l s = = 2 . 6 . 1  
 a i o h t t p = = 3 . 1 0 . 1 1  
 a i o h t t p - r e t r y = = 2 . 8 . 3  
 a i o i c e = = 0 . 1 0 . 2  
 a i o r t c = = 1 . 1 4 . 0  
 a i o s i g n a l = = 1 . 4 . 0  
 a i o s q l i t e = = 0 . 2 2 . 1  
 a n n o t a t e d - t y p e s = = 0 . 7 . 0  
 a n t h r o p i c = = 0 . 4 9 . 0  
 a n t l r 4 - p y t h o n 3 - r u n t i m e = = 4 . 9 . 3  
 a n y a s c i i = = 0 . 3 . 3  
 a n y i o = = 4 . 1 2 . 1  
 A P S c h e d u l e r = = 3 . 1 1 . 2  
 a r g o n 2 - c f f i = = 2 5 . 1 . 0  
 a r g o n 2 - c f f i - b i n d i n g s = = 2 5 . 1 . 0  
 a s y n c u a = = 1 . 1 . 8  
 a t t r s = = 2 5 . 4 . 0  
 A u t h l i b = = 1 . 6 . 6  
 a v = = 1 4 . 2 . 0  
 b a c k o f f = = 2 . 2 . 1  
 b i t s a n d b y t e s = = 0 . 4 7 . 0  
 b o t o 3 = = 1 . 4 1 . 5  
 b o t o c o r e = = 1 . 4 1 . 5  
 c a c h e t o o l s = = 5 . 5 . 2  
 c e r t i f i = = 2 0 2 6 . 1 . 4  
 c f f i = = 2 . 0 . 0  
 c h a r s e t - n o r m a l i z e r = = 3 . 4 . 4  
 c l i c k = = 8 . 1 . 8  
 c o l o r a m a = = 0 . 4 . 6  
 c o l o r e d l o g s = = 1 5 . 0 . 1  
 c o n t o u r p y = = 1 . 3 . 3  
 c r y p t o g r a p h y = = 4 6 . 0 . 3  
 c y c l e r = = 0 . 1 2 . 1  
 C y t h o n = = 3 . 0 . 1 2  
 d a t a c l a s s e s - j s o n = = 0 . 6 . 7  
 d e e p - s o r t - r e a l t i m e = = 1 . 3 . 2  
 d e f u s e d x m l = = 0 . 7 . 1  
 d i l l = = 0 . 4 . 0  
 d i s t r o = = 1 . 9 . 0  
 d n s p y t h o n = = 2 . 8 . 0  
 d o c k e r = = 7 . 1 . 0  
 d o c o p t = = 0 . 6 . 2  
 e a s y o c r = = 1 . 7 . 2  
 e i n o p s = = 0 . 8 . 1  
 f a s t a p i = = 0 . 1 1 5 . 1 4  
 f i l e l o c k = = 3 . 1 7 . 0  
 f l a t b u f f e r s = = 2 5 . 1 2 . 1 9  
 f o n t t o o l s = = 4 . 6 1 . 1  
 f r o z e n l i s t = = 1 . 8 . 0  
 f s s p e c = = 2 0 2 6 . 1 . 0  
 f t f y = = 6 . 3 . 1  
 g o o g l e - a u t h = = 2 . 4 7 . 0  
 g o o g l e - c r c 3 2 c = = 1 . 8 . 0  
 g o o g l e - g e n a i = = 1 . 5 9 . 0  
 h 1 1 = = 0 . 1 6 . 0  
 h 5 p y = = 3 . 1 5 . 1  
 h t t p c o r e = = 1 . 0 . 9  
 h t t p x = = 0 . 2 8 . 1  
 h u g g i n g f a c e - h u b = = 0 . 3 6 . 0  
 h u m a n f r i e n d l y = = 1 0 . 0  
 h y d r a - c o r e = = 1 . 3 . 2  
 i d n a = = 3 . 1 1  
 i f a d d r = = 0 . 2 . 0  
 I m a g e I O = = 2 . 3 7 . 2  
 i n f e r e n c e - m o d e l s = = 0 . 1 8 . 4  
 i n i c o n f i g = = 2 . 3 . 0  
 i o p a t h = = 0 . 1 . 1 0  
 i s o d a t e = = 0 . 7 . 2  
 J i n j a 2 = = 3 . 1 . 6  
 j i t e r = = 0 . 1 2 . 0  
 j m e s p a t h = = 1 . 0 . 1  
 k i w i s o l v e r = = 1 . 4 . 9  
 l a n g d e t e c t = = 1 . 0 . 9  
 l a z y _ l o a d e r = = 0 . 4  
 l o g g e r = = 1 . 4  
 l x m l = = 6 . 0 . 2  
 m a r k d o w n - i t - p y = = 4 . 0 . 0  
 M a r k u p S a f e = = 3 . 0 . 3  
 m a r s h m a l l o w = = 3 . 2 6 . 2  
 m a t p l o t l i b = = 3 . 1 0 . 8  
 m d u r l = = 0 . 1 . 2  
 m l _ d t y p e s = = 0 . 5 . 4  
 m p m a t h = = 1 . 3 . 0  
 m u l t i d i c t = = 6 . 7 . 0  
 m u x _ p y t h o n   @   g i t + h t t p s : / / g i t h u b . c o m / m u x i n c / m u x - p y t h o n . g i t @ 4 9 6 f 6 2 c 3 a 3 e 3 9 5 7 1 5 a 2 9 0 5 f b d 6 f c 4 0 4 1 5 1 3 0 8 3 8 d  
 m y p y _ e x t e n s i o n s = = 1 . 1 . 0  
 n e t w o r k x = = 3 . 6 . 1  
 n i n j a = = 1 . 1 3 . 0  
 n u m 2 w o r d s = = 0 . 5 . 1 4  
 n u m p y = = 2 . 2 . 6  
 n v i d i a - m l - p y = = 1 2 . 5 7 5 . 5 1  
 o m e g a c o n f = = 2 . 3 . 0  
 o n n x = = 1 . 2 0 . 1  
 o n n x r u n t i m e = = 1 . 2 1 . 1  
 o n v i f - z e e p - a s y n c = = 2 . 0 . 0  
 o p e n a i = = 1 . 1 0 9 . 1  
 o p e n c v - c o n t r i b - p y t h o n = = 4 . 1 0 . 0 . 8 4  
 o p e n c v - p y t h o n = = 4 . 1 0 . 0 . 8 4  
 o p e n c v - p y t h o n - h e a d l e s s = = 4 . 1 2 . 0 . 8 8  
 p a c k a g i n g = = 2 4 . 2  
 p a h o - m q t t = = 1 . 6 . 1  
 p a n d a s = = 2 . 3 . 3  
 p e f t = = 0 . 1 7 . 1  
 p i l l o w = = 1 1 . 3 . 0  
 p l a t f o r m d i r s = = 4 . 5 . 1  
 p l u g g y = = 1 . 6 . 0  
 p o l a r s = = 1 . 3 7 . 1  
 p o l a r s - r u n t i m e - 3 2 = = 1 . 3 7 . 1  
 p o r t a l o c k e r = = 3 . 2 . 0  
 p r o m e t h e u s - f a s t a p i - i n s t r u m e n t a t o r = = 6 . 0 . 0  
 p r o m e t h e u s _ c l i e n t = = 0 . 2 4 . 1  
 p r o p c a c h e = = 0 . 4 . 1  
 p r o t o b u f = = 6 . 3 3 . 4  
 p s u t i l = = 7 . 2 . 1  
 p y - c p u i n f o = = 9 . 0 . 0  
 p y a s n 1 = = 0 . 6 . 2  
 p y a s n 1 _ m o d u l e s = = 0 . 4 . 2  
 p y b a s e 6 4 = = 1 . 0 . 2  
 p y c l i p p e r = = 1 . 4 . 0  
 p y c o c o t o o l s = = 2 . 0 . 1 1  
 p y c p a r s e r = = 2 . 2 3  
 p y d a n t i c = = 2 . 1 2 . 5  
 p y d a n t i c - s e t t i n g s = = 2 . 7 . 1  
 p y d a n t i c _ c o r e = = 2 . 4 1 . 5  
 p y d o t = = 2 . 0 . 0  
 p y e e = = 1 3 . 0 . 0  
 P y g m e n t s = = 2 . 1 9 . 2  
 P y J W T = = 2 . 1 0 . 1  
 p y l i b s r t p = = 1 . 0 . 0  
 p y l o g i x = = 1 . 0 . 5  
 p y m o d b u s = = 3 . 8 . 3  
 p y O p e n S S L = = 2 5 . 3 . 0  
 p y p a r s i n g = = 3 . 3 . 1  
 p y p d f i u m 2 = = 4 . 3 0 . 0  
 p y r e a d l i n e 3 = = 3 . 5 . 4  
 p y t e s t = = 8 . 4 . 2  
 p y t h o n - b i d i = = 0 . 6 . 7  
 p y t h o n - d a t e u t i l = = 2 . 9 . 0 . p o s t 0  
 p y t h o n - d o c t r = = 0 . 1 1 . 0  
 p y t h o n - d o t e n v = = 1 . 2 . 1  
 p y t z = = 2 0 2 5 . 2  
 p y v i p s = = 2 . 2 . 3  
 p y w i n 3 2 = = 3 1 1  
 P y Y A M L = = 6 . 0 . 3  
 q r c o d e = = 8 . 0  
 R a p i d F u z z = = 3 . 1 4 . 3  
 r e d i s = = 5 . 0 . 8  
 r e g e x = = 2 0 2 6 . 1 . 1 5  
 r e q u e s t s = = 2 . 3 2 . 5  
 r e q u e s t s - f i l e = = 3 . 0 . 1  
 r e q u e s t s - t o o l b e l t = = 1 . 0 . 0  
 r f - c l i p = = 1 . 1  
 R F - S A M - 2 = = 
[truncated — 1860 more characters]
```

### server/index.js

```javascript
const express = require('express')
const morgan = require('morgan')
const cors = require('cors')
const app = express()
const {getEntryModel } = require('./modules/entry')
require('dotenv').config()

morgan.token('body', (req) => {
  return req.method === 'POST' ? JSON.stringify(req.body) : ''
})

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  }
  else if (error.name == 'ValidationError'){
    return response.status(400).json({error: error.message})
  } 

  next(error)
}

app.use(express.json())
app.use(morgan(':method :url :status :res[content-length] :response-time ms :body'))
app.use(express.static('dist'))
app.use(cors())

console.log('hello world')
//api calls go here

app.get('/', async (req, res) => {
  res.json("hello world")
})

app.get('/api/:userID', async (req, res) => {
  try {
    const { userID } = req.params
    const EntryModel = getEntryModel(userID)
    const entries = await EntryModel.find({}).sort({'_id': -1})
    res.json(entries)
  } catch (error) {
    next(error)
  }
})

app.post('/api', async (req, res, next) => {
    const user = req.body.userID
    const EntryModel = getEntryModel(user)
    const body = req.body
    const entry = new EntryModel({
        summary: body.summary,
        extended_summary: body.extended_summary,
        timestamp: body.timestamp,
        videoURL: body.videoURL,
        severity: body.severity,
        incidentType: body.incidentType
    })
    entry.save()
    .then(savedEntry => {
    res.status(201).json(savedEntry)
  })
    .catch(error => next(error))
})

//api calls end here
app.use(errorHandler)

const PORT = process.env.PORT
/*
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})
*/
module.exports = app
```

### cruzHacksProject/src/main.jsx

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { Auth0Provider } from '@auth0/auth0-react'
import { BrowserRouter } from 'react-router-dom';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
    <Auth0Provider
      domain="dev-2hydh4svq703m2os.us.auth0.com"
      clientId="vQrdCOeyKaEEpEvhgNkORvGqs6qzmJzc"
      authorizationParams={{ redirect_uri: window.location.origin }}
    >
    <App />
    </Auth0Provider>
    </BrowserRouter>
  </React.StrictMode>,
)

```

### cruzHacksProject/src/App.jsx

```javascript
import { useAuth0 } from '@auth0/auth0-react';
import { Routes, Route } from 'react-router-dom';
import HomePage from './homepage';
import './login.css';
import IncidentDetails from './IncidentDetails';
import placeholder from './assets/placeholder.png';

function App() {
  const {
    isLoading, // Loading state, the SDK needs to reach Auth0 on load
    isAuthenticated,
    error,
    loginWithRedirect: login, // Starts the login flow
    logout: auth0Logout, // Starts the logout flow
    user, // User profile
  } = useAuth0();

  const signup = () =>
    login({ authorizationParams: { screen_hint: 'signup' } });

  const logout = () =>
    auth0Logout({ logoutParams: { returnTo: window.location.origin } });

  if (isLoading) return 'Loading...';

  if (!isAuthenticated) {
    return (
      <>
        {error && <p>Error: {error.message}</p>}
        <div className="fullView">
          <div className="mainCol">
            <img src={placeholder} />
            <button
              className="loginButtons"
              onClick={() =>
                login({ authorizationParams: { screen_hint: 'signup' } })
              }
            >
              Signup
            </button>
            <button className="loginButtons" onClick={login}>
              Login
            </button>
          </div>
        </div>
      </>
    );
  }

  return (
    <Routes>
      <Route path="/" element={<HomePage userId = {user.sub} logout={logout} />} />
      <Route path="/incident" element={<IncidentDetails />} />
    </Routes>
  );
}

export default App;

```

### cruzHacksProject/vite.config.js

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
    },
  },
});

```

### cruzHacksProject/index.html

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

```

### detection/GeminiMux.py

```python
from google import genai
from google.genai import types
import os
from dotenv import load_dotenv
import os
import mux_python
from mux_python.rest import ApiException
import time
import requests
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum
from typing import Literal
import subprocess
import cv2 as cv
#processes the clip with an AI summary from gemini
# ensure you have enviroment variables 
# GEMINI_API_KEY 

prompt = """
You are analyzing a short video clip to determine whether a meaningful incident occurred.

Your task is to produce a single IncidentReport object that strictly matches the following schema:

- summary (string):
  A concise 1–3 sentence summary of what happened in the video. If nothing significant occurs, briefly state that the scene shows normal activity.

- extended_summary (string):
  A detailed natural-language description of the event, including:
  • What happened and how it unfolded
  • Who was involved (describe people by appearance or actions, not IDs)
  • Relevant objects or environment
  • Why the event matters
  • Any reasonable follow-up actions if applicable
  If no meaningful incident occurred, explain clearly why the activity is considered normal or insignificant.

- severity (integer: 0–3):
  Assign exactly one numeric severity:
    1 = Minor (routine or low-risk activity worth noting)
    2 = Medium (unusual or suspicious activity requiring review)
    3 = High (critical incident requiring immediate attention)

- incident_type (IncidentType enum):
  Choose exactly one category that best describes the situation:
    Crime
    Medical Emergency
    Traffic Incident
    Property Damage
    Safety Hazard
    Suspicious Activity
    Normal Activity
    Camera Interference

IMPORTANT RULES:
- If no meaningful or noteworthy incident occurs, set:
    severity = 1
    incident_type = Normal Activity
- Only escalate severity when there is clear visual evidence.
- Do not invent actions, intent, or outcomes that cannot be observed.
- Do not include tags, timestamps, object lists, or multiple events.
- Output must conform exactly to the IncidentReport schema.
"""

class IncidentType(str, Enum):
    SHOPLIFTING = "Shoplifting"
    FIGHT = "Fight"
    TRESPASSING = "Trespassing"
    VANDALISM = "Vandalism"
    SUSPICIOUS_BEHAVIOR = "Suspicious_Behavior"
    NONE = "None"
    OTHER = "Other"

class IncidentReport(BaseModel):
    summary: str = Field(
        description="Text summary describing what happened in the video incident."
    )

    extended_summary: str = Field(
        description="Extended summmary of the current event, providing more details what occurs in the given clip, details about the suspect, and future actions that should be taken."
    )

    severity: Literal[0, 1, 2, 3] = Field(
        description="Incident severity level from 1 (low) to 3 (high). Return 0 if no major incident occurs."
    )
    incident_type: IncidentType = Field(
        description="Classifier describing the type of incident."
    )

def clipProcessing(video_file_path):
    # The client gets the API key from the environment variable `GEMINI_API_KEY`.
    video_bytes = open(video_file_path, 'rb').read()
    load_dotenv()
    client = genai.Client(api_key= os.getenv("GEMINI_API_KEY"))
    response = client.models.generate_content(
        model='models/gemini-3-flash-preview',
        contents=types.Content(
            parts=[
                types.Part(
                    inline_data=types.Blob(data=video_bytes, mime_type='video/mp4')
                ),
                types.Part(text=prompt)
            ]
        ),
                config={
        "response_mime_type": "application/json",
        "response_json_schema": IncidentReport.model_json_schema(),
        },
    )
    print(f"This is response.text{response.text}")
    response = IncidentReport.model_validate_json(response.text)
    return(response)

# uploads clip to database
# ensure you have enviroment variables 
# MUX_TOKEN_ID
# MUX_TOKEN_SECRET
def uploadToMux(video_file_path):
    # Authentication Setup
    load_dotenv()
    configuration = mux_python.Configuration()
    configuration.username = os.environ["MUX_TOKEN_ID"]
    configuration.password = os.environ["MUX_TOKEN_SECRET"]
    # API Client Initialization
    uploads_api = mux_python.DirectUploadsApi(
        mux_python.ApiClient(configuration)
    )
    # ========== create-direct-upload ==========
    create_asset_request = mux_python.CreateAssetRequest(
        playback_policy=[mux_python.PlaybackPolicy.PUBLIC]
    )
    create_upload_request = mux_python.CreateUploadRequest(
        timeout=3600,
        new_asset_settings=create_asset_request,
        cors_origin="philcluff.co.uk"
    )
    create_upload_response = uploads_api.create_direct_upload(
        create_upload_request
    )
    upload_id = create_upload_response.data.id
    upload_url = create_upload_response.data.url
    print(f"Upload ID: {upload_id}")
    print(f"Upload URL: {upload_url}")
    print("create-direct-upload OK ✅")

    # ========== ACTUAL FILE UPLOAD ==========
    print("Uploading file to Mux...")
    with open(video_file_path, "rb") as f:
        upload_response = requests.put(
            upload_url,
            data=f,
            headers={
                "Content-Type": "application/octet-stream"
            }
        )

    upload_response.raise_for_status()
    print("File upload completed ✅")
    # ========== wait for Mux to process ==========
    print("Waiting for asset creation...")
    asset_id = None
    for _ in range(30):  # ~30 seconds max
        upload_status = uploads_api.get_direct_upload(upload_id)
        asset_id = upload_status.data.asset_id
        if asset_id:
            break
        time.sleep(1)

    assert asset_id is not None
    print(f"Asset created 🎉 Asset ID: {asset_id}")
    # OPTIONAL: fetch asset details
    assets_api = mux_python.AssetsApi(
        mux_python.ApiClient(configuration)

[truncated — 437 more characters]
```

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