# Project export: PrivAds

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 12.0
- Tagline: The Privacy-First AI Ad Network
- Devpost: https://devpost.com/software/aura-e1marq
- GitHub: https://github.com/acmcmc/calhacks-12
- Demo: https://www.loom.com/share/e830ebef76b2495dbfd9d899350b7d61
- Result: winner (Reka: Best Use of Reka)
- Team: 3 GitHub contributor(s) — ACMCMC (30 commits), sg (15 commits), Isita (4 commits)

## Devpost submission (written by the team)

### Overview

Big tech ad networks rely on tracking and personal data to target users. PrivAds is a new kind of ad platform: it predicts what ads users are most likely to engage with — without ever storing their identity, browsing history, or demographics. We use only click feedback and page context to serve relevant, privacy-respecting ads.

### Inspiration

We took ideas from many of the challenges at CalHacks. For example, we loved AppLovin’s challenge to extract high-value signals from ads as a way to gain user insights, but we built upon that and pushed it further by asking: “What if we could build an ad recommendation engine that’s smart and privacy first?” We also drew from Y Combinator’s AI native track to imagine what an AI-powered, privacy-centric alternative to existing Y Combinator ad startups, such as Plai, might look like in today’s world. What we do Imagine an ad network that actually respects your privacy. Instead of tracking you with cookies and personal data, our system learns from your interactions like what types of ads you click on, what pages you are interested in, and builds a privacy-first user embedding that never stores your identity. We process ad images or videos using multimodal AI, where we extract visual, textual, and contextual signals from images and videos using a VLM encoder. Then, we use contrastive learning to "match" those embeddings against a user’s embedding space. With Fetch.ai, we simulate autonomous user personas who explore thousands of ads, and Claude AI helps us model click probabilities for each ad-user pair. This all came together into a scalable, privacy-first ad recommendation engine that knows what you like without knowing who you are. What makes us special No user tracking or segments: We use user embeddings, not personas or segments that could be traced back. Multimodal ad understanding: Ads are processed with a vision-language model (Jina CLIP v2) to extract both visual and textual signals. Context-aware serving: Ad selection considers both user embedding and the current page context. Custom and dynamic ads: The system can generate or enhance ads on the fly, tailored to user interests and page content. Less ads: Showing ads to people who are not going to interact with the material is a waste of time and money. For instance, if someone's in a hurry, they won't click on any ads. We detect user interaction patterns and avoid showing ads when we anticipate low performance. Everybody wins!

### How we built it

Dataset collection: The contrastive learning model requires data on ads and users interacting with those ads. To do so, we built on AppLovin's provided dataset. We used Google Gemini and Reka to extract key features from image and video ads, respectively. We then used an Anthropic AI agent with BrightData's MCP server and Langchain to build a large dataset of ads. Furthermore, for user dataset generation, we used another agent to build realistic user profiles. Contrastive Learning LLM for Ad-Serving: Unlike OpenAI's CLIP model, instead of a one-to-one relationship amongst data, we have many users interacting with many ads -- aka many-to-many data. We generate embeddings for user profiles, context of the user, and ads. The model crunches out which ads to serve to which users. The model is deployed at an endpoint. Platform for companies: In the grand scheme of things, the ad-serving platform serves companies publishing advertisements. We have already figured out ad-understanding, customer-segmentation, and effective ad-serving. Custom Ads: The platform allows companies to generate custom ads; companies can also opt to enhance their pre-existing ads dynamically on each user's device. The system extracts contextual information about the app / webpage the ad is being deployed on, makes inferences about the user, and specifically tailors the ad to the user's interests to improve click-through rate. Demo website: https://privads-demo.onrender.com/

### Challenges we ran into

It was a very complex system to implement because there were several dependent parts. While we doubled down on the research problem of effectively learning from creatives granted high dimensional data, we ideated a lot about what direction to take it in as a product, what problem we were solving for our users (companies), and our unique value proposition. Luckily, we were able to find our direction and niche.

### Accomplishments we're proud of

To have built so many agents and a custom LLM for ad-serving that works despite high-dimensional, sparse data.

### What we learned

Architecture matters: Separating concerns (frontend, web_ad_service, backend) makes scaling easier, but coordinating them and building them separately at the same time as a team is complex. Environment management: Having one shared miniconda env for multiple services is messy; separate envs or containers (Docker) are better. Privacy-first is hard but valuable - Building without personal data tracking is more complex but more aligned with user interests We learned to test external APIs in isolation. Setting up isolated tests for each part of the frontend customized ad generation pipeline is what helped us catch the real issue in the process not working.

### What's next

Comparing the use of contrastive learning to ML models oriented toward high-dimensional sparse data.

## README (from the GitHub repository)


# PrivAds: Privacy-First AI Ad Recommendation Engine

## Why PrivAds?
Big tech ad networks rely on tracking and personal data to target users. PrivAds is a new kind of ad platform: it learns what users like—without ever storing their identity, browsing history, or demographics. We use only click feedback and page context to serve relevant, privacy-respecting ads.

## What Makes It Unique
- **No user tracking or segments:** We use user embeddings, not personas or segments that could be traced back.
- **Multimodal ad understanding:** Ads are processed with a vision-language model (Jina CLIP v2) to extract both visual and textual signals.
- **Context-aware serving:** Ad selection considers both user embedding and the current page context.
- **Custom and dynamic ads:** The system can generate or enhance ads on the fly, tailored to user interests and page content.

## Technical Architecture
```
Ad (text + image) → Jina CLIP v2 (frozen) → z_ad (2048D) → Projector (MLP) → p_ad (512D)
                                                        ↓
User clicks → Co-click graph → Margin/Contrastive Loss → user_embeddings (512D)
                                                        ↓
Scoring: cos(user_emb, p_ad)
```
- **Frozen VLM:** Jina CLIP v2 is never fine-tuned, ensuring robust, general ad representations.
- **Learned Projector:** A 2-layer MLP maps ad embeddings into the user space.
- **User Embeddings:** Learned via margin-based contrastive loss on a co-click graph (users who clicked the same ads).
- **All embeddings L2-normalized** for cosine similarity.

## Quick Start
1. `pip install -r requirements.txt`
2. `python main.py`  
   - Loads Jina CLIP v2
   - Generates synthetic or loads real click data
   - Trains user embeddings and projector
   - Saves models and embeddings
3. Outputs in `models/` and `data/` (see below)

## Outputs
```
models/
  ├── user_embeddings.npy      # (n_users, 512)
  ├── global_mean.npy          # (512,)
  └── projector.pt             # Projector weights
data/
  ├── ad_embeddings_raw.npz    # (n_ads, 2048)
  └── ad_projected.npz         # (n_ads, 512)
```

## Technical Details
- **Contrastive Learning:** Margin-based loss encourages user embeddings to be closer to ads they clicked than to negatives, by a margin.
- **Co-click Graph:** Users are connected if they clicked the same ad; this graph is the basis for contrastive training.
- **Synthetic & Real Data:** Swap between synthetic and real click data with a single line of code.
- **Evaluation:** Metrics include Recall@100 and AUC for retrieval quality.
- **No PII, no history:** Only abstract vectors are stored; no user or behavioral data is ever saved.

## Component Testing
```bash
cd src
python ad_encoder.py         # Test ad encoder
python projector.py          # Test projector
python click_data.py         # Test click data
python train_user_embeddings.py
python train_projector.py
```

## Privacy by Design
- No user metadata, no tracking, no segments
- Embeddings are abstract and cannot be reversed to user data
- GDPR-compliant: delete a user by removing their embedding

## Next Steps
- [ ] Thompson Sampling for exploration
- [ ] Real-time serving API
- [ ] Feedback loop for online learning
- [ ] Real ad data with images/videos
- [ ] Advanced evaluation (NDCG, etc.)
- [ ] Fast ANN retrieval (FAISS)

## Demo
[https://privads-demo.onrender.com/](https://privads-demo.onrender.com/)

## License
Apache 2.0


## Detected evidence (automated analysis)

Indexed codebase: 95 recognized source files, 671 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Hugging Face (technology) — detected in the code
- JavaScript (language) — detected in the code
- LangChain (technology) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (115 of 115)

```
.gitignore
ad_processing/encoder.py
ad_processing/feature_correlation_analysis.py
ad_processing/feature_extractor.py
ad_processing/tagger.py
backend/elastic_search.py
backend/evaluation_results/archetype_confusion.csv
backend/interaction_modeling/model_trainer.py
backend/interaction_modeling/onnx_exporter.py
backend/interaction_modeling/README.md
backend/interaction_modeling/run_pipeline.py
backend/interaction_modeling/sliding_window_extractor.py
backend/interaction_modeling/synthetic_generator.py
backend/interaction_modeling/test_onnx_manual.py
backend/main.py
backend/privads_core.py
backend/requirements.txt
brightdata_fetch_example.py
CHECKLIST.md
datagen/AD_INTELLIGENCE_README.md
datagen/package.json
datagen/README.md
datagen/src/ad_intelligence_main.js
datagen/src/ad_intelligence_pipeline.js
datagen/src/asset_analyzer.js
datagen/src/brightdata_api_demo.js
datagen/src/brightdata_collector.js
datagen/src/brightdata_mcp_demo.js
datagen/src/dataset_collector.js
datagen/src/feature_fingerprint.js
datagen/src/main_workflow.js
datagen/src/reka_integration.js
datagen/src/scaled_collector.js
datagen/src/simple_analyzer.js
datagen/src/smart_collector.js
datagen/src/test_reka.js
datagen/src/verify_setup.js
datagen/src/working_analyzer.js
datagen/TUTORIAL.md
feature_extraction/get_dataset.py
gemini_ad_analyzer.py
HACKATHON_GUIDE.md
main.py
models/feature_names.json
models/interaction_predictor.onnx
models/training_results.json
persona_ad_clicks.csv
personas.json
pipeline/load_databases.py
pipeline/run_ad_pipeline.py
pipeline/scrape_ads.py
pipeline/training/ad_encoder.py
pipeline/training/click_data.py
pipeline/training/evaluate.py
pipeline/training/generate_ad_embeddings.py
pipeline/training/projector.py
pipeline/training/README_migration.txt
pipeline/training/train_models.py
pipeline/training/train_projector.py
pipeline/training/train_user_embeddings.py
privads-demo/.gitignore
privads-demo/package.json
privads-demo/public/index.html
privads-demo/public/manifest.json
privads-demo/public/models/demo.html
privads-demo/public/models/interaction_predictor.js
privads-demo/public/models/interaction_predictor.onnx
privads-demo/public/robots.txt
privads-demo/README.md
privads-demo/src/App.css
privads-demo/src/App.test.tsx
privads-demo/src/App.tsx
privads-demo/src/components/AdPredictionBar.css
privads-demo/src/components/AdPredictionBar.tsx
privads-demo/src/components/CustomizedAd.css
privads-demo/src/components/CustomizedAd.tsx
privads-demo/src/components/PrivAdsProvider.tsx
privads-demo/src/hooks/useInteractionTracker.ts
privads-demo/src/index.css
privads-demo/src/index.tsx
privads-demo/src/react-app-env.d.ts
privads-demo/src/reportWebVitals.ts
privads-demo/src/setupProxy.js
privads-demo/src/setupTests.ts
privads-demo/src/websites/ChessTutorial.css
privads-demo/src/websites/ChessTutorial.tsx
privads-demo/src/websites/EducationalPlatform.css
privads-demo/src/websites/EducationalPlatform.tsx
privads-demo/src/websites/HealthWellness.css
privads-demo/src/websites/HealthWellness.tsx
privads-demo/src/websites/NewsSite.css
privads-demo/src/websites/NewsSite.tsx
privads-demo/tsconfig.json
README.md
reka_video_ad_analyzer.py
requirements.txt
SUMMARY.md
WEB_AD_IMPLEMENTATION.md
web_ad_service/ad_injection.py
web_ad_service/best_ad_service.py
web_ad_service/browser_extension/background.js
web_ad_service/browser_extension/content.css
web_ad_service/browser_extension/content.js
web_ad_service/browser_extension/manifest.json
web_ad_service/browser_extension/popup.html
web_ad_service/browser_extension/popup.js
web_ad_service/gemini_customizer.py
web_ad_service/main.py
web_ad_service/mock_data_generator.py
web_ad_service/mock_data.json
web_ad_service/QUICK_START.md
web_ad_service/README.md
web_ad_service/requirements.txt
web_ad_service/start_service.sh
web_ad_service/web_text_extractor.py
```

### Dependencies

- backend/requirements.txt: chromadb@==1.2.1, fastapi@==0.120.0, groq@==0.33.0, numpy@==2.3.4, pip@>=23.3.0, pydantic@==2.12.3, python-dotenv@==1.1.1, setuptools@>=70.0.0, uvicorn@==0.38.0, wheel@>=0.41.0
- datagen/package.json: @anthropic-ai/sdk@^0.32.1, @langchain/anthropic@^0.3.10, @langchain/core@^0.3.0, @langchain/langgraph@^0.4.8, @langchain/mcp-adapters@^0.6.0, dotenv@^16.4.5, node-fetch@^3.3.2
- privads-demo/package.json: @testing-library/dom@^10.4.1, @testing-library/jest-dom@^6.9.1, @testing-library/react@^16.3.0, @testing-library/user-event@^13.5.0, @types/jest@^27.5.2, @types/node@^16.18.126, @types/react@^19.2.2, @types/react-dom@^19.2.2, @xenova/transformers@^2.17.2, axios@^1.12.2, http-proxy-middleware@^3.0.5, lucide-react@^0.548.0, onnxruntime-web@^1.8.0, react@^19.2.0, react-dom@^19.2.0, react-router-dom@^7.9.4, react-scripts@5.0.1, recharts@^3.3.0, typescript@^4.9.5, web-vitals@^2.1.4
- requirements.txt: beautifulsoup4@>=4.12.2, black@>=23.0.0, brightdata-sdk, chromadb@>=0.4.0, datasets, easyocr@>=1.7.0, einops, elasticsearch@>=8.0.0, fastapi@>=0.100.0, flake8@>=6.0.0, google-generativeai@>=0.3.2, html5lib@>=1.1, httpx@>=0.25.2, lxml@>=4.9.3, matplotlib@>=3.6.0, nltk@>=3.8.1, numpy@>=1.24.0, opencv-python@>=4.8.0, pandas@>=2.0.0, pathlib2@>=2.3.7, pillow@>=10.0.0, pydantic@>=2.0.0, pytest@>=7.0.0, pytest-asyncio@>=0.21.1, python-dotenv@>=1.0.0, python-multipart@>=0.0.6, requests@>=2.31.0, scikit-learn@>=1.3.0, scipy@>=1.11.0, seaborn@>=0.12.0, skl2onnx, spacy@>=3.7.2, timm, torch@>=2.0.0, torchvision, tqdm@>=4.65.0, transformers@>=4.35.0, urllib3@>=2.1.0, uvicorn@>=0.23.0
- web_ad_service/requirements.txt: beautifulsoup4@==4.12.2, fastapi@==0.104.1, google-generativeai@>=0.3.0, numpy@==1.24.3, pip@>=23.3.0, pydantic@==2.5.0, python-dotenv@==1.0.0, requests@==2.31.0, scipy@==1.11.4, setuptools@>=70.0.0, uvicorn@==0.24.0, wheel@>=0.41.0

### Recent commits (newest first)

- WIP
- Fix Interaction
- WIP Restore Interaction
- Merge remote-tracking branch 'origin/web-ad-customization'
- WIP
- rem big req
- backend req
- req edit
- Merge remote-tracking branch 'origin/web-ad-customization' + rename to PrivAds
- WIP
- req
- req edit
- req edit 2
- req edit
- Merge branch 'main' of https://github.com/ACMCMC/privads
- WIP
- reqs.txt
- Merge pull request #3 from ACMCMC/web-ad-customization
- working gemini ad customization
- WIP

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

### CHECKLIST.md

```markdown
# ✅ Phase 1 Complete - Verification Checklist

## Files Created (15 total)

### Root Files
- [x] `main.py` - Full pipeline orchestrator
- [x] `test_components.py` - Quick component tests
- [x] `requirements.txt` - Python dependencies
- [x] `.gitignore` - Git ignore rules

### Documentation
- [x] `README.md` - Full project documentation
- [x] `HACKATHON_GUIDE.md` - Quick start for hackathon
- [x] `SUMMARY.md` - Implementation summary
- [x] `CHECKLIST.md` - This file

### Source Code (`src/`)
- [x] `ad_encoder.py` - Jina CLIP v2 wrapper (3.7 KB)
- [x] `projector.py` - MLP projector (1.1 KB)
- [x] `click_data.py` - Data sources (4.0 KB)
- [x] `train_user_embeddings.py` - User training (4.5 KB)
- [x] `train_projector.py` - Projector training (5.0 KB)

### Directories
- [x] `models/` - For trained models
- [x] `data/` - For embeddings
- [x] `notebooks/` - For experiments

---

## Features Implemented

### Core Pipeline ✅
- [x] Jina CLIP v2 integration (frozen)
- [x] Unified text+image embeddings
- [x] User embedding training (InfoNCE)
- [x] Projector training (InfoNCE + centroid)
- [x] End-to-end orchestration

### Data Handling ✅
- [x] Synthetic click generator
- [x] Abstract data source interface
- [x] One-line swap for real data
- [x] Position bias simulation

### Privacy Features ✅
- [x] No user metadata storage
- [x] Only abstract embeddings
- [x] Global prior for new users
- [x] GDPR-compliant design

### Code Quality ✅
- [x] Comprehensive docstrings
- [x] Type hints
- [x] Error handling
- [x] Modular design
- [x] Well-commented

### Documentation ✅
- [x] Architecture diagrams
- [x] Quick start guide
- [x] API documentation
- [x] Troubleshooting guide
- [x] Next steps outlined

---

## Ready to Run?

### Quick Test (2 min)
```bash
python test_components.py
```

### Full Pipeline (5-10 min)
```bash
pip install -r requirements.txt
python main.py
```

---

## What You Get After Running

```
models/
  ├── user_embeddings.npy    # (1000, 128)
  ├── global_mean.npy        # (128,)
  └── projector.pt           # PyTorch weights

data/
  ├── ad_embeddings_raw.npz  # (500, 768)
  └── ad_projected.npz       # (500, 128)
```

---

## Next Actions for Hackathon

### Immediate (Hour 1-2)
1. Run `python main.py` to verify everything works
2. Prepare your real ad data (CSV + images)
3. Test with a few real ads

### Short-term (Hour 3-6)
4. Swap in real click data
5. Start Phase 2: Thompson Sampling
6. Build simple serving API

### Medium-term (Hour 7-12)
7. Add evaluation metrics
8. Build demo UI
9. Optimize performance
10. Prepare presentation

---

## Success Metrics

- [x] Code compiles without errors
- [x] All components have tests
- [x] Documentation is complete
- [x] Easy to swap data sources
- [x] Clear next steps
- [x] Production-quality code
- [x] Fast to run (<10 min full pipeline)
- [x] Privacy-first design

---

## Team Roles (Suggested)

**Backend Lead:** Run main.py, tune hyperparameters, add Thompson Sampling

**Data Lead:** Prepare real ad CSV 
[truncated — 767 more characters]
```

### SUMMARY.md

```markdown
# Phase 1 Implementation Summary

## ✅ What We Built

A complete, production-ready **Phase 1 pipeline** for privacy-first ad recommendations:

### Core Components

1. **`ad_encoder.py`** - Jina CLIP v2 wrapper
   - Single unified embedding for text + image
   - 768-dim output, L2-normalized
   - Batch encoding support
   - Handles missing images gracefully

2. **`projector.py`** - Ad space → User space MLP
   - 2-layer MLP: 768 → 512 → 128
   - LayerNorm + GELU + Dropout
   - L2-normalized output

3. **`click_data.py`** - Modular data sources
   - Abstract interface: `ClickDataSource`
   - `SyntheticClickGenerator`: realistic synthetic clicks from archetypes
   - `RealClickData`: drop-in replacement for production
   - **One-line swap** between synthetic and real!

4. **`train_user_embeddings.py`** - User space learning
   - InfoNCE on co-click graph
   - Handles sparse graphs gracefully
   - Outputs: user embeddings + global_mean (for new users)

5. **`train_projector.py`** - Cross-space alignment
   - InfoNCE: align ads with users who clicked them
   - Centroid auxiliary loss: match mean of clickers
   - Weighted combination of both losses

6. **`main.py`** - End-to-end pipeline
   - 6-step orchestration
   - Progress logging
   - Saves all artifacts for serving

7. **`test_components.py`** - Fast validation
   - Tests all modules without full training
   - Runs in ~30 seconds

## 🎯 Key Design Wins

- ✅ **Frozen VLM** → No fine-tuning needed, fast inference
- ✅ **Privacy-first** → Only embeddings stored, no metadata
- ✅ **Modular** → Swap synthetic ↔ real data with 1 line
- ✅ **Global prior init** → Simple, effective cold-start
- ✅ **Single embedding per ad** → Text + image fused seamlessly
- ✅ **Well-documented** → Every function has docstrings

## 📊 Pipeline Flow

```
Input: Ad text + optional image
  ↓
Jina CLIP v2 (frozen)
  ↓ z_ad (768-dim)
Projector (trained)
  ↓ p_ad (128-dim)
User Space
  ↓
Scoring: cos(u, p_ad)
```

User embeddings learned separately from co-click graph via InfoNCE.

## 📁 Outputs

After running `main.py`:

```
models/
  user_embeddings.npy    # (n_users, 128)
  global_mean.npy        # (128,) - for new users
  projector.pt           # PyTorch state dict

data/
  ad_embeddings_raw.npz  # (n_ads, 768) - from Jina CLIP
  ad_projected.npz       # (n_ads, 128) - in user space
```

## 🚀 Ready for Hackathon

**What your team can do NOW:**

1. **Run the pipeline** → `python main.py` (5-10 min)
2. **Add real ads** → Just edit the `sample_ads` section
3. **Add real clicks** → One-line swap in `main.py`
4. **Build on top:**
   - Phase 2: Thompson Sampling
   - Serving API
   - Demo UI
   - Evaluation metrics

## 🔧 Technical Stack

- **VLM:** Jina CLIP v2 (jinaai/jina-clip-v2)
- **Framework:** PyTorch + Transformers
- **Losses:** InfoNCE (contrastive) + MSE centroid
- **Optimizer:** AdamW with weight decay
- **Normalization:** L2 on all embeddings

## 📈 Hyperparameters (tuned)

```python
# User embeddings
d_user = 128
epochs
[truncated — 1519 more characters]
```

### requirements.txt

```
# PrivAds - Consolidated Requirements
# Install with: pip install -r requirements_consolidated.txt

# Core ML dependencies
torch>=2.0.0
transformers>=4.35.0
numpy>=1.24.0
scikit-learn>=1.3.0
scipy>=1.11.0
einops

# Image processing
pillow>=10.0.0
opencv-python>=4.8.0

# Data processing
pandas>=2.0.0
tqdm>=4.65.0

# Visualization
matplotlib>=3.6.0
seaborn>=0.12.0

# Vector databases
chromadb>=0.4.0

# Search
elasticsearch>=8.0.0

# Sentiment analysis
torchvision
timm

# Web framework & API
fastapi>=0.100.0
uvicorn>=0.23.0
pydantic>=2.0.0
requests>=2.31.0
python-multipart>=0.0.6
python-dotenv>=1.0.0
httpx>=0.25.2

# Web scraping
beautifulsoup4>=4.12.2
lxml>=4.9.3
urllib3>=2.1.0
html5lib>=1.1

# AI/ML - Gemini API
google-generativeai>=0.3.2

# Text processing (optional)
nltk>=3.8.1
spacy>=3.7.2

# OCR (optional)
easyocr>=1.7.0

# Development & Testing
pytest>=7.0.0
pytest-asyncio>=0.21.1
black>=23.0.0
flake8>=6.0.0

# Utilities
pathlib2>=2.3.7
skl2onnx
datasets
brightdata-sdk

```

### backend/requirements.txt

```
setuptools>=70.0.0
wheel>=0.41.0
pip>=23.3.0
fastapi==0.120.0
uvicorn==0.38.0
pydantic==2.12.3
python-dotenv==1.1.1
chromadb==1.2.1
numpy==2.3.4
groq==0.33.0

```

### web_ad_service/requirements.txt

```
setuptools>=70.0.0
wheel>=0.41.0
pip>=23.3.0
fastapi==0.104.1
uvicorn==0.24.0
pydantic==2.5.0
google-generativeai>=0.3.0
requests==2.31.0
beautifulsoup4==4.12.2
python-dotenv==1.0.0
numpy==1.24.3
scipy==1.11.4
```

### privads-demo/package.json

```
{
  "name": "privads-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/dom": "^10.4.1",
    "@testing-library/jest-dom": "^6.9.1",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.126",
    "@types/react": "^19.2.2",
    "@types/react-dom": "^19.2.2",
    "@xenova/transformers": "^2.17.2",
    "axios": "^1.12.2",
    "lucide-react": "^0.548.0",
    "onnxruntime-web": "^1.8.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "react-router-dom": "^7.9.4",
    "react-scripts": "5.0.1",
    "recharts": "^3.3.0",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "http-proxy-middleware": "^3.0.5"
  }
}

```

### datagen/package.json

```
{
  "name": "brightdata-live-web-access-workshop",
  "version": "1.0.0",
  "description": "Learn how to give AI agents access to real-time web data",
  "main": "src/main_workflow.js",
  "type": "module",
  "scripts": {
    "start": "node src/main_workflow.js",
    "verify": "node src/verify_setup.js",
    "demo:api": "node src/brightdata_api_demo.js",
    "demo:mcp": "node src/brightdata_mcp_demo.js",
    "ad-intelligence": "node src/ad_intelligence_main.js",
    "collect-ads": "node src/brightdata_collector.js",
    "collect-datasets": "node src/dataset_collector.js",
    "analyze-assets": "node src/asset_analyzer.js",
    "simple-analyze": "node src/simple_analyzer.js",
    "test-reka": "node src/test_reka.js",
    "working-analyze": "node src/working_analyzer.js",
    "create-fingerprint": "node src/feature_fingerprint.js",
    "smart-collect": "node src/smart_collector.js",
    "scale-collect": "node src/scaled_collector.js",
    "extract-features": "node src/reka_integration.js"
  },
  "keywords": [
    "brightdata",
    "web-scraping",
    "ai",
    "llm",
    "serp-api"
  ],
  "author": "Bright Data",
  "license": "MIT",
  "dependencies": {
    "dotenv": "^16.4.5",
    "node-fetch": "^3.3.2",
    "@anthropic-ai/sdk": "^0.32.1",
    "@langchain/anthropic": "^0.3.10",
    "@langchain/langgraph": "^0.4.8",
    "@langchain/mcp-adapters": "^0.6.0",
    "@langchain/core": "^0.3.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

```

### main.py

```python
"""
PrivAds - Automated Pipeline Runner
Runs all components in sequence, skipping completed steps.
"""

import sys
import os
from pathlib import Path

def check_models_exist():
    """Check if trained models exist."""
    models_dir = Path("backend/models")
    required_files = ["user_embeddings.npy", "global_mean.npy", "projector.pt"]
    return all((models_dir / f).exists() for f in required_files)

def check_interaction_model_exists():
    """Check if interaction ML model exists."""
    models_dir = Path("backend/models")
    required_files = ["interaction_predictor.pkl", "interaction_scaler.pkl", "feature_names.json"]
    return all((models_dir / f).exists() for f in required_files)

def check_ad_metadata_exists():
    """Check if ad metadata exists."""
    metadata_file = Path("backend/data/ad_metadata.jsonl")
    return metadata_file.exists()

def check_chroma_db_exists():
    """Check if Chroma database exists."""
    chroma_dir = Path("backend/chroma_db")
    return chroma_dir.exists() and len(list(chroma_dir.glob("*"))) > 0

def run_training_pipeline():
    """Run the PrivAds training pipeline."""
    print("🚀 Running PrivAds Training Pipeline...")
    exit_code = os.system("cd /home/acreomarino/privads && python pipeline/training/train_models.py")
    if exit_code != 0:
        print("❌ Training pipeline failed!")
        return False
    return True

def run_ad_processing():
    """Run the ad processing pipeline."""
    print("🎨 Running Ad Processing Pipeline...")
    exit_code = os.system("cd /home/acreomarino/privads && python pipeline/run_ad_pipeline.py")
    if exit_code != 0:
        print("❌ Ad processing pipeline failed!")
        return False
    return True

def run_interaction_modeling():
    """Run the interaction modeling training pipeline."""
    print("🤖 Running Interaction Modeling Pipeline...")
    exit_code = os.system("cd backend/interaction_modeling && python run_pipeline.py --samples 10000")
    if exit_code != 0:
        print("❌ Interaction modeling pipeline failed!")
        return False
    return True

def run_database_loading():
    """Load data into databases."""
    print("💾 Loading Data into Databases...")
    exit_code = os.system("cd /home/acreomarino/privads && python pipeline/load_databases.py")
    if exit_code != 0:
        print("❌ Database loading failed!")
        return False
    return True

def start_backend():
    """Start the FastAPI backend."""
    print("🌐 To start the Backend API, run in a separate terminal:")
    print("   cd backend && python main.py")
    print("   API will be available at: http://localhost:8000")

def deploy_to_baseten(truss_dir, publish=False):
    """
    Deploy a Truss model to Baseten. If publish=True, deploys to production.
    Requires truss CLI and Baseten API key configured.
    """
    import subprocess
    cmd = ["truss", "push"]
    if publish:
        cmd.append("--publish")
    cmd.append(truss_dir)
    print(f"Deploying model to Baseten with command: {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True)
    print(result.stdout)
    if result.returncode != 0:
        print("Baseten deployment failed:")
        print(result.stderr)
        return False
    print("Model deployed to Baseten successfully.")
    return True

def main():
    """Run the complete PrivAds pipeline."""
    print("\n" + "="*60)
    print("🎯 PrivAds: Automated Pipeline Runner")
    print("="*60)

    success = True

    # Step 1: Check and run training pipeline
    print("\n📊 Step 1: PrivAds Model Training")
    if check_models_exist():
        print("✅ Models already exist, skipping training")
    else:
        print("⚠️  Models not found, running training pipeline...")
        if not run_training_pipeline():
            success = False

    # Step 2: Check and run interaction modeling
    print("\n🤖 Step 2: Interaction Modeling")
    if check_interaction_model_exists():
        print("✅ Interaction model already exists, skipping training")
    else:
        print("⚠️  Interaction model not found, running interaction modeling...")
        if not run_interaction_modeling():
            success = False

    # Step 3: Check and run ad processing
    print("\n🎨 Step 3: Ad Processing")
    if check_ad_metadata_exists():
        print("✅ Ad metadata already exists, skipping processing")
    else:
        print("⚠️  Ad metadata not found, running ad processing...")
        if not run_ad_processing():
            success = False

    # Step 4: Check and run database loading
    print("\n💾 Step 4: Database Loading")
    if check_chroma_db_exists():
        print("✅ Chroma database already exists, skipping loading")
    else:
        print("⚠️  Chroma database not found, running database loading...")
        if not run_database_loading():
            success = False

    # Step 5: Instructions for starting backend API
    print("\n🌐 Step 5: Backend API")
    if success:
        print("✅ All components ready!")
        start_backend()
    else:
        print("❌ Some components failed. Please check the errors above.")
        print("You can try running individual components manually:")
        print("  - python pipeline/training/train_models.py")
        print("  - python pipeline/run_ad_pipeline.py")
        print("  - python pipeline/load_databases.py")
        print("  - cd backend && python main.py")
        sys.exit(1)

    # Deploy trained model to Baseten
    truss_dir = "path/to/your/truss_model"  # Update with your actual Truss directory
    deploy_to_baseten(truss_dir, publish=True)

if __name__ == "__main__":
    main()

if __name__ == "__main__":
    main()

```

### web_ad_service/main.py

```python
"""
Main Web Ad Service API
Orchestrates the complete web ad customization pipeline.
"""

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, List, Optional, Any
import logging
import uvicorn
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()  # Only look in current directory

# Import our services
from web_text_extractor import WebTextExtractor
from best_ad_service import BestAdService
from gemini_customizer import GeminiAdCustomizer
from ad_injection import AdInjectionService

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize services
text_extractor = WebTextExtractor()
best_ad_service = BestAdService()
gemini_customizer = GeminiAdCustomizer()
injection_service = AdInjectionService()

# FastAPI app
app = FastAPI(
    title="PrivAds Web Ad Service",
    description="Complete web ad customization pipeline",
    version="1.0.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Configure appropriately for production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Request/Response Models
class WebAdRequest(BaseModel):
    """Complete web ad customization request."""
    url: str
    user_embedding: Optional[List[float]] = None
    user_id: Optional[str] = None
    injection_method: str = "web_component"
    position: str = "bottom"
    customization_preferences: Optional[Dict[str, Any]] = None

class WebAdResponse(BaseModel):
    """Complete web ad customization response."""
    success: bool
    ad_id: str
    customized_ad_text: str
    injection_code: str
    web_context: Dict[str, Any]
    ad_context: Dict[str, Any]
    customization_metadata: Dict[str, Any]
    error_message: Optional[str] = None

class QuickAdRequest(BaseModel):
    """Quick ad request for testing."""
    url: str
    ad_text: str

class QuickAdResponse(BaseModel):
    """Quick ad response."""
    injection_code: str
    success: bool

class PageContextRequest(BaseModel):
    """Request with page context directly from frontend."""
    page_context: Dict[str, Any]  # title, content, keywords, page_type
    user_embedding: Optional[List[float]] = None
    user_id: Optional[str] = None
    injection_method: str = "web_component"

class PageContextResponse(BaseModel):
    """Response with customized ad."""
    success: bool
    ad_id: str
    customized_ad_text: str
    original_ad_description: str
    confidence_score: float
    ad_features: Dict[str, Any]
    customization_metadata: Dict[str, Any]
    error_message: Optional[str] = None

@app.post("/web_ad/complete", response_model=WebAdResponse)
async def get_complete_web_ad(request: WebAdRequest):
    """
    Complete web ad customization pipeline:
    1. Extract web content
    2. Find best ad
    3. Customize with Gemini
    4. Generate injection code
    """
    try:
        logger.info(f"Processing complete web ad request for URL: {request.url}")
        
        # Step 1: Extract web content
        web_content = text_extractor.extract_page_content(request.url)
        logger.info(f"Extracted web content: {web_content.get('page_type', 'unknown')} page")
        
        # Step 2: Find best ad
        from best_ad_service import WebContentRequest as BestAdReq
        best_ad_req = BestAdReq(
            url=request.url,
            user_embedding=request.user_embedding,
            user_id=request.user_id
        )
        
        best_ad_response = best_ad_service.get_best_ad(best_ad_req)
        logger.info(f"Found best ad: {best_ad_response.ad_id}")
        
        # Step 3: Customize ad with Gemini
        from gemini_customizer import AdCustomizationRequest as CustomizationReq
        customization_req = CustomizationReq(
            ad_context={
                'ad_id': best_ad_response.ad_id,
                'description': best_ad_response.description,
                'ad_features': best_ad_response.ad_features,
                'content_signals': best_ad_response.content_signals
            },
            web_context=web_content,
            customization_preferences=request.customization_preferences
        )
        
        customized_response = gemini_customizer.customize_ad(customization_req)
        logger.info(f"Customized ad with confidence: {customized_response.confidence_score}")
        
        # Step 4: Generate injection code
        from ad_injection import AdInjectionRequest as InjectionReq
        injection_req = InjectionReq(
            customized_ad={
                'customized_ad_text': customized_response.customized_ad_text,
                'original_ad_id': customized_response.original_ad_id,
                'confidence_score': customized_response.confidence_score
            },
            target_url=request.url,
            injection_method=request.injection_method,
            position=request.position
        )
        
        injection_response = injection_service.inject_ad(injection_req)
        logger.info(f"Generated injection code: {injection_response.success}")
        
        return WebAdResponse(
            success=True,
            ad_id=best_ad_response.ad_id,
            customized_ad_text=customized_response.customized_ad_text,
            injection_code=injection_response.injection_code,
            web_context=web_content,
            ad_context={
                'ad_id': best_ad_response.ad_id,
                'description': best_ad_response.description,
                'similarity_score': best_ad_response.similarity_score,
                'ad_features': best_ad_response.ad_features
            },
            customization_metadata={
                'confidence_score': customized_response.confidence_score,
                'customization_applied': customized_response.customization_applied,
                'generation_metadata': customized_response.generation_m
[truncated — 7920 more characters]
```

### backend/main.py

```python
"""
PrivAds Backend API
FastAPI application providing ad serving and search endpoints.
"""

from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from groq import Groq
from pydantic import BaseModel
from typing import List, Dict, Any, Optional
import numpy as np
import chromadb
import os
from pathlib import Path
import joblib
import json
import sys

# Import our core modules
from privads_core import PrivAdsCore
from elastic_search import search_ads_elastic
from dotenv import load_dotenv

load_dotenv()

app = FastAPI(
    title="PrivAds API",
    description="AI-Native Growth Platform Backend",
    version="1.0.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://127.0.0.1:3000",
        os.environ.get("AD_CUSTOMIZATION_BACKEND_SERVER_URL", "http://localhost:8002"),
        "*"
    ],  # Frontend URLs + Web Ad Service
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize core components
core = PrivAdsCore()

# Initialize Chroma client
try:
    chroma_client = chromadb.PersistentClient(path="./chroma_db")
    print("✓ ChromaDB initialized")
except Exception as e:
    print(f"⚠ ChromaDB initialization failed: {e}")
    chroma_client = None

# Load ML model for click prediction
try:
    model_path = Path("../models/click_predictor.pkl")
    scaler_path = Path("../models/feature_scaler.pkl")
    features_path = Path("../models/feature_names.json")
    
    click_model = joblib.load(model_path)
    feature_scaler = joblib.load(scaler_path)
    
    with open(features_path, 'r') as f:
        feature_names = json.load(f)
        
    print(f"Loaded click prediction model with {len(feature_names)} features: {feature_names}")
except Exception as e:
    print(f"Warning: Could not load click prediction model: {e}")
    click_model = None
    feature_scaler = None
    feature_names = []

class AdRequest(BaseModel):
    user_id: str
    p_receptive: float
    site_context: Dict[str, Any]

class SearchRequest(BaseModel):
    query: str

class AdResponse(BaseModel):
    decision: str
    ad_id: Optional[str] = None
    creative_url: Optional[str] = None
    score: Optional[float] = None
    reason: Optional[str] = None

class SearchResult(BaseModel):
    ad_id: str
    thumbnail_url: str
    metadata: Dict[str, Any]

class SearchResponse(BaseModel):
    results: List[SearchResult]

class ClickPredictionRequest(BaseModel):
    features: Dict[str, float]

class ClickPredictionResponse(BaseModel):
    probability: float
    features_used: List[str]

class ClickPredictionRequest(BaseModel):
    features: Dict[str, float]

class ClickPredictionResponse(BaseModel):
    probability: float
    features_used: List[str]

class BestAdRequest(BaseModel):
    web_text: str
    user_embedding: List[float]

class BestAdResponse(BaseModel):
    ad_id: str
    description: str
    source_url: str
    similarity_score: float
    projected_embedding: List[float]

@app.post("/get_best_ad", response_model=BestAdResponse)
async def get_ad(request: AdRequest):
    """
    Core ad serving endpoint that combines user preference, receptiveness, and context.
    """
    try:
        # Check receptiveness threshold
        if request.p_receptive < 0.7:
            return AdResponse(
                decision="BLOCK",
                reason="User not receptive"
            )

        # Get user embedding (or global mean for cold start)
        user_embedding = core.get_user_embedding(request.user_id)

        # Apply any contextual modulation (placeholder for now)
        final_embedding = user_embedding  # Could modulate based on site_context

        # Query Chroma with context filtering
        results = core.query_ads_chroma(
            query_embedding=final_embedding,
            site_context=request.site_context,
            n_results=10
        )

        if not results:
            return AdResponse(
                decision="BLOCK",
                reason="No relevant ads found for context"
            )

        # Select best ad (already ranked by Chroma)
        best_ad = results[0]
        ad_id = best_ad['id']
        score = best_ad['distance']  # Cosine similarity

        # Get creative URL (placeholder - would come from metadata)
        creative_url = f"https://example.com/ads/{ad_id}.jpg"

        return AdResponse(
            decision="SERVE",
            ad_id=ad_id,
            creative_url=creative_url,
            score=score
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Ad serving error: {str(e)}")

@app.post("/search_ads", response_model=SearchResponse)
async def search_ads(request: SearchRequest):
    """
    Natural language search over ad metadata using Elastic Agent Builder.
    """
    try:
        results = search_ads_elastic(request.query)

        # Format results
        formatted_results = []
        for result in results:
            formatted_results.append(SearchResult(
                ad_id=result['ad_id'],
                thumbnail_url=result.get('thumbnail_url', f"https://example.com/thumbnails/{result['ad_id']}.jpg"),
                metadata=result.get('metadata', {})
            ))

        return SearchResponse(results=formatted_results)

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Search error: {str(e)}")

@app.post("/get_best_ad", response_model=BestAdResponse)
async def get_best_ad(request: BestAdRequest):
    """
    Find the best ad by projecting ad embeddings into user space and matching with user embedding.
    """
    try:
        import torch
        from pathlib import Path
        import json
        from scipy.spatial.distance import cosine

        # Load ad metadata
        ad_metadata_path = Path("../ad_creatives/scraped_metadata.json")
        if not ad_metadata_pat
[truncated — 9340 more characters]
```

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