vault backup: 2026-04-29 14:50:31

This commit is contained in:
Vadym Samoilenko 2026-04-29 14:50:31 +01:00
parent f6147c9e89
commit c352ac8e52
29 changed files with 3801 additions and 2305 deletions

View file

@ -1,22 +1,170 @@
---
name: "APAC Ops Bot"
client: Oliver Internal
client: Oliver Agency APAC
status: active
server: optical-web-1
tech: []
tech: [Python, FastAPI, React, TypeScript, PostgreSQL, OpenAI API, Azure AD, Docker]
local_path: "/Users/ai_leed/Documents/Projects/Oliver/APAC ops Bot"
deploy: TBD
deploy: docker compose up -d or deploy.sh
url:
tags: [oliver, apac, bot, ops]
created: 2026-04-14
port: 8048
db: PostgreSQL
---
## Overview
APAC operations bot. No README — details to be filled in during working sessions.
APAC ops Bot is an AI-powered operations assistant for Oliver Agency's APAC region. It's a RAG-powered chatbot that strictly answers questions from curated OpenAI Vector Store documents (preventing hallucinations), with Azure AD SSO authentication, multi-turn conversation support, and detailed token usage tracking. The bot helps APAC ops teams quickly find answers from internal documentation without requiring hallucination-prone open-ended AI generation.
## Tech Stack
- **Frontend:** React 18, TypeScript, MSAL (Microsoft Authentication Library) for Azure AD SSO, WebSocket for real-time streaming
- **Backend:** Python 3.x, FastAPI, async SQLAlchemy + asyncpg, Alembic for migrations
- **Database:** PostgreSQL (async)
- **Infrastructure:** Docker Compose, bash deployment script
- **AI/ML:** OpenAI Assistants API with `file_search` tool, model `gpt-5-nano-2025-08-07`, OpenAI Vector Store
- **Key libraries:** `openai`, `sqlalchemy`, `alembic`, `pytest`, `black`, `isort`, `flake8`
## Architecture
The system is a three-tier architecture:
1. **Frontend (React + TypeScript):** Browser-based UI with MSAL Azure AD PKCE flow authentication. Real-time chat interface connected via WebSocket.
2. **Backend (FastAPI):** REST API + WebSocket server. Handles auth, conversation state, OpenAI Assistants API calls with RAG, and token tracking. Uses async SQLAlchemy with PostgreSQL.
3. **Database (PostgreSQL):** Stores conversation history, user sessions, token usage metrics, and auth state.
**Data Flow:**
- User authenticates via Azure AD MSAL (or email/password fallback) → Frontend receives JWT/session token
- User sends chat message → WebSocket connection streams response from OpenAI Assistants API (file_search tool only)
- Backend logs conversation, tokens used, and metadata to PostgreSQL
- Frontend displays streamed response in real-time
**Key Design Decisions:**
- **RAG-only constraint:** OpenAI Assistants API with `file_search` tool ensures answers come strictly from Vector Store documents
- **WebSocket:** Enables real-time streaming of bot responses instead of polling
- **Async SQLAlchemy:** Non-blocking database operations for high concurrency
- **Azure AD MSAL:** Enterprise SSO with email/password fallback for flexibility
```
┌─────────────┐
│ Browser │
│ (React) │
└──────┬──────┘
│ HTTPS + WebSocket
│ (MSAL Auth, JWT)
┌─────────────────────────────────┐
│ FastAPI Backend │
│ - Auth (MSAL/Email) │
│ - WebSocket streaming │
│ - OpenAI Assistants (RAG) │
│ - Token tracking │
└──────┬──────────────────────────┘
│ asyncpg (async queries)
┌─────────────────────────────────┐
│ PostgreSQL Database │
│ - Conversations │
│ - Users / Sessions │
│ - Token usage logs │
└─────────────────────────────────┘
└──► OpenAI Vector Store
(file_search tool)
```
## Dev Commands
```bash
# Start all services (Docker Compose)
cd /Users/ai_leed/Documents/Projects/Oliver/APAC\ ops\ Bot && docker compose up -d
# Backend: run dev server with auto-reload
cd backend && uvicorn app.main:app --reload --port 8048
# Backend: run tests
cd backend && pytest
# Backend: run tests with coverage report
cd backend && pytest --cov=app
# Backend: lint and format code
cd backend && black app/ && isort app/ && flake8 app/
# Backend: apply database migrations
cd backend && alembic upgrade head
# Backend: rollback last migration
cd backend && alembic downgrade -1
# Frontend: start dev server
cd frontend && npm start
# Frontend: build for production
cd frontend && npm run build
# Frontend: run tests
cd frontend && npm test
```
## Deployment
- **Server:** Unknown (not documented)
- **Deploy:** `./deploy.sh` in project root, or `docker compose up -d`
- **URL:** Unknown (not documented)
- **Port:** 8048 (backend), frontend typically 3000 in dev
- **Service:** No systemd service documented
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/APAC ops Bot`
To deploy: navigate to project root and run `./deploy.sh` or use Docker Compose directly.
## Environment Variables
Key environment variables (location: likely `.env` file, not shown in source):
- `OPENAI_API_KEY` — API key for OpenAI Assistants API and Vector Store access
- `OPENAI_ASSISTANT_ID` — ID of the pre-configured OpenAI Assistant with file_search tool
- `OPENAI_VECTOR_STORE_ID` — ID of the Vector Store containing APAC ops documents
- `DATABASE_URL` — PostgreSQL connection string (format: `postgresql+asyncpg://user:pass@host:5432/dbname`)
- `AZURE_AD_TENANT_ID` — Azure tenant ID for MSAL SSO
- `AZURE_AD_CLIENT_ID` — Azure app registration client ID
- `AZURE_AD_CLIENT_SECRET` — Azure app secret (if not using PKCE)
- `BACKEND_URL` — Base URL of FastAPI backend (for frontend API calls)
- `FRONTEND_URL` — Base URL of React frontend (for CORS, redirects)
- `JWT_SECRET_KEY` — Secret for signing JWT tokens
- `JWT_ALGORITHM` — Algorithm for JWT (likely `HS256`)
## API / Endpoints
Key REST + WebSocket endpoints (exact paths not documented in source, but likely in `backend/app/`):
- `POST /auth/login` — Email/password authentication
- `POST /auth/azure-callback` — Azure AD SSO callback
- `POST /api/conversations` — Create new conversation
- `GET /api/conversations/{conversation_id}` — Fetch conversation history
- `WS /ws/chat/{conversation_id}` — WebSocket for real-time chat streaming
- `GET /api/metrics/tokens` — Fetch token usage stats
See `apac-ops-bot/backend/app/` for complete route definitions.
## Known Issues
- Server and production URL not documented — clarify with ops team
- OPENAI_ASSISTANT_ID and OPENAI_VECTOR_STORE_ID must be pre-created in OpenAI account manually (not auto-provisioned)
- Fallback email/password auth may be disabled if Azure AD is the only auth method configured
- No explicit documentation on Vector Store document upload/sync process — may require manual management
## Git
- **Remote:** Not documented in provided source files
---
**Last Updated:** Based on source files dated [check file timestamps]
**Maintainer:** Unknown (check git history)
**Related Notes:** None yet — create links as you discover dependencies
## Sessions
### 2026-04-14 Project catalogued
**Done:** Added to Obsidian second brain.
@ -25,4 +173,4 @@ APAC operations bot. No README — details to be filled in during working sessio
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 2026-04-14 | Initial setup | Note created | — |
| 2026-04-14 | Initial setup | Note created | — |

View file

@ -1,34 +1,178 @@
---
name: "Barclays Banner Builder"
client: Barclays
client: Barclays Mobile Banking
status: active
server: optical-dev
tech: [React, TypeScript, Vite, Zustand, FastAPI, Python, PostgreSQL, Alembic, Docker]
tech: [React 18, TypeScript, FastAPI, PostgreSQL, Redis, OpenAI, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/Barclays-banner-builder
deploy: bash deploy.sh
deploy: bash /opt/barclays-banner-builder/deploy.sh
url: https://optical-dev.oliver.solutions/barclays-banner-builder/
tags:
- project
- client/barclays
- domain/ai
created: 2026-04-17
port: 8010
db: PostgreSQL 16 + pgvector
---
## Overview
AI-assisted banner generation tool for Barclays marketing assets. Workflow: Brief → Edit Variants → Banner Editor → Export (CSV/PDF). Uses Zustand journey store for workflow state — backward navigation allowed, forward steps grayed until completed.
Barclays Banner Builder is an AI-powered web application that helps Barclays Mobile Banking campaign managers create FCA-compliant banner campaign assets through a conversational interface. Users describe campaign briefs in chat, receive AI-generated copy variants (Medium and Large formats) with icon and theme recommendations, select DAM images, preview banners in-context, and export a PDF contact sheet and Workfront-ready CSV. Built as a monolithic React SPA + FastAPI backend with RQ job queue for async copy generation via OpenAI.
## Tech Stack
- **Frontend:** React 18 + TypeScript + Vite + Zustand (journey store, Barclays design tokens)
- **Backend:** Python + FastAPI + PostgreSQL + Alembic migrations
- **Auth:** Azure AD (MSAL)
- **Infrastructure:** Docker Compose + Apache subpath on optical-dev (port 8010)
- **Frontend:** React 18 + Vite + TypeScript; UI components built on Oliver design system
- **Backend:** FastAPI + uvicorn (2 workers in production); asyncpg for async DB access
- **Database:** PostgreSQL 16 + pgvector for RAG embeddings and icon vector search
- **Queue / Job Worker:** Redis 7 + RQ (with scheduler)
- **AI/ML:** OpenAI gpt-5.4-mini (structured output for copy generation), text-embedding-3-small (RAG and icon embeddings)
- **PDF Rendering:** WeasyPrint for A4 contact sheet generation
- **Infrastructure:** Docker Compose (dev and prod), Apache 2.4 reverse proxy, Ubuntu host
## Architecture
The system follows a client-server model with background job processing:
```
Browser (React SPA)
│ HTTPS
Apache 2.4 (optical-dev.oliver.solutions)
├─ /barclays-banner-builder/api/* → ProxyPass → FastAPI :8010
└─ /barclays-banner-builder/* → Static SPA (/var/www/html/…)
FastAPI (uvicorn, :8010 on host)
├─ REST endpoints, auth, DB
├─ asyncpg → PostgreSQL 16 + pgvector
└─ enqueue RQ jobs
RQ Worker (separate container)
├─ Copy generation via OpenAI
├─ Intent classification (generate/refine/clarify)
├─ RAG chunk retrieval (cosine search)
├─ Icon matching (cosine search on embeddings)
└─ PDF rendering, CSV export
Redis 7 (queue broker)
PostgreSQL 16 (persistent state, RAG, embeddings)
OpenAI API (gpt-5.4-mini, text-embedding-3-small)
Adobe DAM API or MockDamClient (image search)
```
**Key components:**
- **Frontend:** Chat interface (ChatBrief) → Intent classification (intent_router) → Copy generation (copy_generation) → Variant review (VariantsGrid) → Banner editor (BannerEditor) → Export (exporter)
- **Database:** Conversations, BannerSets, BannerVariants, RAG chunks + embeddings, Icon embeddings, SystemPrompts, Users
- **RAG:** Corpus in `/rag-corpus/` embedded and searched by pgvector cosine similarity to enrich generation prompts
- **Icons:** 100+ PNG illustrations in 10 categories, indexed by OpenAI embeddings for semantic icon matching
- **Validation:** Character limits enforced on copy variants (up to 3 OpenAI retries with corrective prompt)
## Dev Commands
```bash
# Clone and setup
git clone git@bitbucket.org:zlalani/barclays-banner-builder.git
cd barclays-banner-builder
cp .env.example .env
# Edit .env: set OPENAI_API_KEY, leave APP_BASE_PATH empty for local dev
# Start all services (Docker Compose)
docker compose up -d --build
# Run database migrations
docker compose exec api alembic upgrade head
# Seed admin user
docker compose exec api python scripts/seed_admin.py
# Creates: admin@barclays.com / change_me_password
# Embed RAG corpus and index illustrations (costs OpenAI $)
docker compose exec api python scripts/ingest_rag.py
docker compose exec api python scripts/index_icons.py
# View logs
docker compose logs -f api
docker compose logs -f worker
# Access dev server
# Frontend: http://localhost:5174
# API docs: http://localhost:8010/docs
# Login: admin@barclays.com / change_me_password
```
## Deployment
- **Run locally:** `docker compose up --build`
- **Server:** `ssh optical-dev "cd /opt/barclays-banner-builder && git pull && bash deploy.sh"`
- **URL:** `https://optical-dev.oliver.solutions/barclays-banner-builder/`
- **Apache config:** `/opt/barclays-banner-builder/deploy/apache-barclays.conf`
- **Port:** 8010 (backend API)
- **Server:** optical-dev.oliver.solutions
- **Deploy:** `bash /opt/barclays-banner-builder/deploy.sh` (full deploy); `bash /opt/barclays-banner-builder/deploy.sh --skip-frontend` (Python-only); `bash /opt/barclays-banner-builder/deploy.sh --reindex` (force RAG/icon reindex)
- **URL:** https://optical-dev.oliver.solutions/barclays-banner-builder/
- **Port:** 8010 (API internal port, proxied by Apache)
- **Local path:** /opt/barclays-banner-builder/ (production); /Users/ai_leed/Documents/Projects/Oliver/Barclays-banner-builder (dev)
- **Service:** Docker Compose services: `api`, `worker`, `postgres`, `redis` (no systemd service)
- **Status:** Production
**First-time server setup:**
```bash
# Install Docker, Node.js, clone repo, create .env, then:
bash /opt/barclays-banner-builder/deploy.sh
```
**Check health:**
```bash
cd /opt/barclays-banner-builder
docker compose -f docker-compose.prod.yml ps
curl -s http://127.0.0.1:8010/api/health
docker compose -f docker-compose.prod.yml logs -f api
```
**Restart:**
```bash
docker compose -f docker-compose.prod.yml restart api # API only
docker compose -f docker-compose.prod.yml restart worker # Worker only
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate api worker # Full recreate
```
## Environment Variables
- `OPENAI_API_KEY` — OpenAI API key for copy generation and embeddings (required)
- `POSTGRES_PASSWORD` — PostgreSQL superuser password (required in production)
- `SECRET_KEY` — FastAPI session/token secret (required in production)
- `APP_BASE_PATH` — Base URL path; empty for local dev, `/barclays-banner-builder` for production
- `DATABASE_URL` — PostgreSQL connection string (auto-built from POSTGRES_PASSWORD if not set)
- `REDIS_URL` — Redis connection string (defaults to redis://redis:6379 in Docker)
- `DAM_API_URL` — Adobe DAM endpoint or mock endpoint (optional; fallback to mock if not set)
## API / Endpoints
Key REST endpoints (FastAPI + auto-docs at `/api/docs`):
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/auth/login` | User login (email/password) |
| POST | `/api/conversations` | Create conversation + chat turn (enqueues RQ job) |
| GET | `/api/conversations/{id}` | Retrieve conversation and messages |
| POST | `/api/conversations/{id}/messages` | Add message to conversation |
| GET | `/api/jobs/{job_id}` | Poll job status (returns `{status: "pending"\|"done"\|"failed", result}`) |
| GET | `/api/banner-sets/{banner_set_id}` | Retrieve BannerSet with variants |
| PATCH | `/api/banner-variants/{id}` | Update variant (copy, selected flag, DAM image, icon) |
| POST | `/api/exports/pdf` | Generate PDF contact sheet (async, returns job_id) |
| POST | `/api/exports/csv` | Generate Workfront CSV (returns data directly) |
| GET | `/api/health` | Health check |
## Known Issues
- **RAG corpus embedding cost:** Ingesting RAG documents and indexing icons incurs OpenAI API charges; run `ingest_rag.py` and `index_icons.py` only when corpus changes.
- **Character limit validation:** Copy generation retries up to 3 times if variants exceed character limits; failure logs are written but user sees error.
- **DAM API fallback:** If `DAM_API_URL` is not set or unreachable, the system falls back to `MockDamClient` (mock data).
- **No authentication on API health endpoint:** `/api/health` is unprotected for monitoring.
## Git
- **Remote:** git@bitbucket.org:zlalani/barclays-banner-builder.git
- **Main branch:** Implied `main` or `master` (not explicitly stated in provided logs)
- **Recent activity:** Active development; last commit `0f66fab` (Rebrand to Oliver design system). 20 commits in history showing feature iterations (chat interface, banner spec updates, export refinement, refine intent fixes).
## Sessions
### 2026-04-28 Can you change the copy colour
@ -414,4 +558,4 @@ AI-assisted banner generation tool for Barclays marketing assets. Workflow: Brie
| 2026-04-17 | Deployment infrastructure | docker-compose.yml, docker-compose.prod.yml, apache/barclays-copygen.conf, .env.example | docker-compose.yml, docker-compose.prod.yml, apache/barclays-copygen.conf |
| 2026-04-17 | App concept & dev plan | Project structure, architecture docs, deployment config | docker-compose.yml, apache-config.conf, project-structure.md |
## Related
## Related

View file

@ -1,10 +1,10 @@
---
name: "DevOps ↔ ClickUp Sync"
client: Oliver
client: Oliver Internal
status: active
tech: [Python, FastAPI, SQLAlchemy, SQLite, Docker, Azure DevOps API, ClickUp API]
tech: [Python, FastAPI, SQLAlchemy, SQLite, Docker, Pydantic, HTTPX]
local_path: /Users/ai_leed/Documents/Projects/Oliver/DevOps_Click_UP_sync
deploy: docker-compose up
deploy: docker compose up -d --build
url: http://localhost:8080
tags: [oliver, devops, ado, clickup, sync, webhook]
created: 2026-04-14
@ -14,113 +14,177 @@ db: SQLite
---
## Overview
DevOps_Click_UP_sync is a bidirectional synchronization service that bridges Azure DevOps and ClickUp, enabling seamless task/work item management across both platforms. It runs as a containerized FastAPI application that listens for webhooks and maintains data consistency between the two systems. The service is designed for teams using both Azure DevOps and ClickUp who need unified task tracking without manual duplication.
DevOps_Click_UP_sync is a FastAPI webhook service that bi-directionally synchronises Azure DevOps work items with ClickUp tasks in real time. It creates, updates, and mirrors comments and attachments across both platforms, preventing echo-event loops through deduplication logic. The service exposes a simple web dashboard for managing project mappings and viewing sync history, and is deployable as a Docker container with SQLite persistence.
## Tech Stack
- **Frontend:** N/A (API-only service)
- **Backend:** Python 3.x, FastAPI, Uvicorn ASGI server
- **Database:** SQLite with SQLAlchemy ORM and async support (aiosqlite)
- **Infrastructure:** Docker, Docker Compose
- **Frontend:** Vanilla JavaScript + HTML + Tailwind CSS (dashboard at `frontend/index.html`)
- **Backend:** Python 3.x + FastAPI + Uvicorn + Pydantic
- **Database:** SQLite with SQLAlchemy ORM (async via `aiosqlite`)
- **Infrastructure:** Docker Compose (single service, volume-mounted data directory)
- **AI/ML:** N/A
- **Key libraries:** FastAPI, SQLAlchemy, httpx (async HTTP client), Pydantic (validation), markdownify (content conversion)
- **Key libraries:** `httpx` (async HTTP client for ADO/ClickUp REST APIs), `markdownify` (HTML↔Markdown conversion), `python-dotenv` (env config), `aiofiles` (async file I/O)
## Architecture
The service follows a webhook-based event-driven architecture:
The service is a synchronisation engine that listens for webhooks from both Azure DevOps and ClickUp, then mirrors changes bidirectionally:
1. **Webhook Handlers** (`src/api/webhooks.py`):
- `POST /webhooks/ado` — receives ADO work item events
- `POST /webhooks/clickup` — receives ClickUp task events
- HMAC validation via `WEBHOOK_SECRET` prevents spoofing
2. **Sync Engine** (`src/sync/engine.py`):
- Core logic that determines what to create, update, or skip
- Calls Field Mapper to translate between ADO and ClickUp schemas
- Invokes REST clients to push changes back to origin system
3. **Field Mapper** (`src/sync/mapper.py`):
- Transforms ADO work item fields ↔ ClickUp task fields
- Handles title, description, state, assignee, priority, custom fields
- Converts HTML ↔ Markdown for comment bodies
4. **Echo-Event Deduplication** (`src/sync/dedup.py`):
- Maintains a `dedup` table: tracks (entity_id, direction, timestamp)
- Incoming webhook is **skipped** if the same entity was synced from the opposite direction within a TTL window (prevents A→B→A loops)
5. **REST Clients**:
- `src/clients/ado.py` — Azure DevOps REST API (work items, comments, attachments)
- `src/clients/clickup.py` — ClickUp API (tasks, comments, attachments)
6. **Database Models** (`src/database.py`):
- `SyncMap` — maps ADO work item ID ↔ ClickUp task ID
- `ProjectMap` — maps ADO project ↔ ClickUp space/folder
- `CommentMap` — maps ADO comment ↔ ClickUp comment for updates
- `DedupLog` — echo-event prevention records
7. **Dashboard APIs** (`src/api/dashboard.py`):
- `GET /api/dashboard/stats` — sync counts and health
- `GET /api/dashboard/project-maps` — view configured mappings
- `GET /api/dashboard/sync-log` — paginated sync history
8. **Setup API** (`src/api/setup.py`):
- `POST /api/setup/register-webhooks` — auto-registers webhook subscriptions with ADO/ClickUp
```
┌─────────────────────────────────────────────────────┐
│ External Systems │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Azure DevOps │ │ ClickUp │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
└───────────┼────────────────────────┼───────────────┘
│ Webhooks │ Webhooks
└────────────┬───────────┘
┌────────────────▼─────────────────┐
│ FastAPI Sync Service │
│ ┌──────────────────────────┐ │
│ │ /api/health │ │
│ │ /api/webhook/ado │ │
│ │ /api/webhook/clickup │ │
│ │ /api/sync │ │
│ └──────────────────────────┘ │
└────────────────┬──────────────────┘
┌────────────────▼─────────────────┐
│ SQLite Database (/app/data) │
│ - Sync mappings │
│ - Metadata │
└──────────────────────────────────┘
┌─────────────┐ ┌──────────────┐
│ Azure │ │ ClickUp │
│ DevOps │ │ │
└──────┬──────┘ └──────┬───────┘
│ │
│ POST /webhooks/ado │ POST /webhooks/clickup
│ (work item created/updated) │ (task created/updated)
│ │
└────────────┬─────────────────────────┘
┌───────▼────────┐
│ FastAPI │
│ Service │
│ (port 8080) │
└───────┬────────┘
┌────────────┼────────────┐
│ │ │
│ Sync │ Dedup │
│ Engine │ Check │
│ │ │
└──┬─────────┴────────┬───┘
│ │
┌─────▼──────┐ ┌──────▼────┐
│ Field │ │ Database │
│ Mapper │ │ (SQLite) │
│ │ │ │
└─────┬──────┘ └──────┬────┘
│ │
┌─────▼──────────────────▼────┐
│ REST Clients │
│ (ADO + ClickUp APIs) │
└──────────────────────────────┘
```
**Key Design Decisions:**
- **Async-first:** All database and HTTP operations use async/await for scalability
- **Webhook-driven:** Listens for changes from both platforms rather than polling
- **Content conversion:** Uses markdownify to normalize formatting between systems
- **Health checks:** Built-in health endpoint for container orchestration
- **Persistent storage:** SQLite for tracking sync state and mappings
## Dev Commands
```bash
# Install dependencies
pip install -r requirements.txt
# Create .env file from template
# Copy and configure environment variables
cp .env.example .env
# Edit .env with your credentials
# → Edit .env with ADO_PAT, CLICKUP_API_TOKEN, WEBHOOK_SECRET, PUBLIC_URL, etc.
# Run locally (requires Python 3.9+)
python -m uvicorn main:app --reload --port 8080
# Start service in Docker (builds image, mounts ./data volume, runs on port 8080)
docker compose up -d
# Build and run with Docker
docker-compose up --build
# Rebuild after code changes
docker compose up -d --build
# View logs
docker-compose logs -f sync-service
# View live logs
docker compose logs -f sync-service
# Stop services
docker-compose down
# Access health check
# Health check
curl http://localhost:8080/api/health
# Run locally without Docker (requires venv + pip install -r requirements.txt)
pip install -r requirements.txt
uvicorn src.main:app --host 0.0.0.0 --port 8080 --reload
# Access dashboard in browser
http://localhost:8080/
```
## Deployment
- **Server:** local (intended for container deployment)
- **Deploy:** `docker-compose up -d`
- **URL:** Configured via `PUBLIC_URL` env var (e.g., https://your-domain.com)
- **Port:** 8080
- **Service:** Docker container managed by docker-compose
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/DevOps_Click_UP_sync
**Container details:**
- Health check runs every 30s (timeout 10s, 3 retries)
- Auto-restart policy: unless-stopped
- Volumes: ./data mounted to /app/data (persistent database storage)
- **Server:** Unknown (no server hostname provided in config)
- **Deploy:** `docker compose up -d --build` (from project directory)
- **URL:** Controlled by `PUBLIC_URL` environment variable (must be HTTPS and reachable by ADO and ClickUp for webhook callbacks)
- **Port:** 8080
- **Service:** Docker Compose service named `sync-service` (not a systemd service)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/DevOps_Click_UP_sync`
- **Data persistence:** SQLite database stored in `./data/sync.db` (Docker volume `./data:/app/data`)
- **Health check:** Built-in Docker healthcheck (`GET /api/health` every 30s)
## Environment Variables
- `ADO_ORGANIZATION` — Azure DevOps organization name
- `ADO_PROJECT` — Azure DevOps project name
- `ADO_PAT` — Azure DevOps Personal Access Token (authentication)
- `CLICKUP_API_TOKEN` — ClickUp API token (format: pk_*)
- `CLICKUP_WORKSPACE_ID` — ClickUp workspace identifier
- `WEBHOOK_SECRET` — Secret key for validating incoming webhooks
- `PUBLIC_URL` — Public domain/URL where this service is accessible (for webhook callbacks)
- `LOG_LEVEL` — Logging verbosity (INFO, DEBUG, etc.)
- `DATABASE_URL` — SQLite connection string; default: `sqlite+aiosqlite:///./data/sync.db`
- `ADO_ORGANIZATION` — Azure DevOps organization name (required)
- `ADO_PROJECT` — Azure DevOps project name (required)
- `ADO_PAT` — Azure DevOps Personal Access Token with work item read/write scopes (required)
- `ADO_ASSIGNED_TO` — WIQL filter for assigned-to field; leave empty to sync all items, use `@Me` to sync all (optional)
- `CLICKUP_API_TOKEN` — ClickUp API token (format: `pk_...`); required
- `CLICKUP_WORKSPACE_ID` — ClickUp workspace ID (required)
- `WEBHOOK_SECRET` — Random secret for HMAC signature validation of incoming webhooks (required; **must not be `changeme`** or echo-event dedup will not activate)
- `PUBLIC_URL` — Public HTTPS URL where this service is reachable (required for webhook registration; e.g., `https://your-domain.com`)
- `LOG_LEVEL` — Logging level for uvicorn/app (`DEBUG`, `INFO`, `WARNING`, `ERROR`; default: `INFO`)
- `DATABASE_URL` — SQLAlchemy database URL (default: `sqlite+aiosqlite:///./data/sync.db`); supports PostgreSQL or MySQL if changed
## API / Endpoints
### Webhooks (External)
- `POST /webhooks/ado` — Azure DevOps webhook receiver (validates HMAC with `WEBHOOK_SECRET`)
- `POST /webhooks/clickup` — ClickUp webhook receiver (validates HMAC with `WEBHOOK_SECRET`)
### Setup
- `POST /api/setup/register-webhooks` — Automatically registers webhook subscriptions with ADO and ClickUp (idempotent)
### Dashboard
- `GET /` — Serve `frontend/index.html` (vanilla JS dashboard)
- `GET /api/health` — Health check endpoint (used by Docker healthcheck)
- `POST /api/webhook/ado` — Receives webhook events from Azure DevOps
- `POST /api/webhook/clickup` — Receives webhook events from ClickUp
- `POST /api/sync` — Manually trigger synchronization (if implemented)
- `GET /api/dashboard/stats` — Sync statistics (total synced, last sync time, error count)
- `GET /api/dashboard/project-maps` — List all configured ADO↔ClickUp project mappings
- `GET /api/dashboard/sync-log?page=1&limit=20` — Paginated sync history log
### Config
- Routes defined in `src/main.py` (routers from `webhooks.py`, `setup.py`, `dashboard.py`)
## Known Issues
None documented. Ensure webhook payloads are validated with `WEBHOOK_SECRET` to prevent unauthorized sync triggers.
## Git
- **Remote:** [Not provided in source files]
- **No test suite** — no unit or integration tests configured; no linting setup
- **Production hardening needed** — before deploying to production:
- Add pytest + test coverage
- Add black/flake8 linting
- Validate all exception handling (especially in sync engine)
- Add request rate-limiting
- Implement robust logging/monitoring
- **SSH access constraint** — no SSH to server without explicit user instruction
- **Field mapping gaps** — some custom fields may not be handled; ADO→ClickUp HTML→Markdown conversion may lose formatting in edge cases
- **No automatic webhook re-registration
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,144 +1,178 @@
---
name: "Oliver AI Bot 2.0 (Nexus MVP)"
client: Enterprise
client: OLIVER Agency
status: active
tech: [React, TypeScript, FastAPI, PostgreSQL, Redis, Qdrant, Docker]
tech: [Next.js 14, FastAPI, PostgreSQL, Qdrant, Docker, Azure Entra ID, OpenAI, Anthropic]
local_path: /Users/ai_leed/Documents/Projects/Oliver/Oliver-ai-bot_2.0
deploy: docker-compose up -d
url: http://localhost:3000
deploy: ./deploy.sh
url: https://nexus.oliver.agency/nexus
tags: [oliver, ai, rag, nexus, executive-assistant, notebook]
created: 2026-04-14
server: local
port: 3000
db: PostgreSQL
server: optical-web-1
port: 443
db: PostgreSQL 16
service: docker-compose
---
## Overview
**Nexus** (Oliver-ai-bot_2.0) is an enterprise-grade unified AI platform combining RAG (Retrieval-Augmented Generation), an AI Executive Assistant, and a Notebook mode for document analysis. It provides corporate users with a centralized interface to query knowledge bases with citations, perform AI-assisted productivity tasks (summarization, translation, action item extraction), and manage documents. The platform includes Microsoft Entra ID authentication, role-based access control (Super Admin, Content Manager, User), and an admin dashboard for system configuration—currently at MVP completion status running in Docker.
Oliver-ai-bot 2.0 (Enterprise AI Hub Nexus) is a multi-tier enterprise AI platform deployed internally at OLIVER Agency. It provides RAG-powered knowledge base chat, configurable AI agents, a Microsoft 365 personal assistant, SharePoint document sync, and a full admin panel—all secured behind Azure Entra ID (PKCE SPA flow). The system combines a Next.js frontend SPA, a FastAPI REST+SSE backend, vector search (Qdrant), PostgreSQL metadata storage, and background task processing (Celery) to deliver real-time AI-assisted knowledge retrieval and M365 integration for internal employees.
## Tech Stack
- **Frontend:** React 18+, TypeScript, Vite, TailwindCSS, Shadcn/ui, React Query
- **Backend:** FastAPI (Python), Uvicorn, SQLAlchemy ORM
- **Database:** PostgreSQL 16, Redis 7 (caching), Qdrant (vector database for RAG)
- **Infrastructure:** Docker, Docker Compose, multi-container orchestration
- **AI/ML:** OpenAI (GPT-5.1, embeddings), Google Gemini 2.5 Flash, Anthropic Claude Sonnet 4.5, NotebookLlama integration
- **Key libraries:** Pydantic (validation), python-jose (JWT), psycopg2 (PostgreSQL driver), aioredis, httpx
- **Frontend:** Next.js 14 (static SPA export), TypeScript, React
- **Backend:** FastAPI (Python 3.11+), Uvicorn, async/await
- **Database:** PostgreSQL 16 (metadata), Qdrant v1.12.1 (vector embeddings), Redis 7 (Celery broker + cache)
- **Infrastructure:** Docker Compose, Google Compute Engine (n2d-standard-4 VM), Google Cloud Run (document processor), Apache 2.4 reverse proxy
- **AI/ML:** OpenAI GPT-5.2 (RAG answers), text-embedding-3-large (embeddings), Anthropic Claude Sonnet 4.6 (agent tool loop), Claude Haiku 4.5 (reranking/expansion), Google Gemini 3.1 Pro (summarization)
- **External APIs:** Azure Entra ID (PKCE auth), Microsoft Graph v1.0 (M365 integration), LlamaParse (OCR), Tavily (web search), Firecrawl (web scraping), LibreCodeInterpreter (sandboxed Python)
- **Key libraries:** alembic (migrations), Celery (background tasks), pydantic (validation), SQLAlchemy (ORM)
## Architecture
Nexus follows a 3-tier microservices architecture with containerized services:
- **Frontend** (React/TypeScript): SPA running on port 3000, consumes REST API, handles authentication via OAuth2, implements SSE streaming for real-time responses
- **Backend** (FastAPI): REST API on port 8000, handles auth (Microsoft Entra ID + JWT), RAG chat logic, document processing, user/admin endpoints, integrates with external LLM providers
- **Data Layer:**
- PostgreSQL (port 5432): Stores users, conversations, documents, configuration
- Redis (port 6379): Session caching, token blacklisting, rate limiting
- Qdrant (port 6333): Vector embeddings for RAG semantic search
- **External Integrations:** OpenAI API (embeddings & chat), Google AI (assistant tasks), Anthropic API (writing tasks), Microsoft Entra ID (corporate SSO), NotebookLlama (notebook mode)
**Nexus** is a four-layer system:
Data flow: Frontend sends queries → FastAPI validates & routes → LLM APIs process → Qdrant retrieves similar docs → Response streams back via SSE → Frontend renders with markdown & citations.
1. **Frontend (Next.js SPA):** Static-exported React app served via Apache at `/nexus`. Handles PKCE OAuth flow with Azure Entra ID, stores JWT, and communicates with backend over HTTPS + SSE.
2. **API Gateway (Apache 2.4):** Reverse proxy on `optical-web-1` that terminates TLS, serves static files, and proxies `/api/v1/*` to FastAPI backend on `127.0.0.1:1222`.
3. **Backend (FastAPI):** REST + Server-Sent Events API with 16 endpoint modules. Guards all routes with JWT verification. Core features:
- **RAG Pipeline:** Multi-query expansion → Qdrant vector search → LLM reranking → streaming answers
- **Agent Engine:** Configurable agents (LLM choice, tool scope, RAG documents)
- **M365 Tools:** Calendar, Mail, Teams, Planner, People queries via MS Graph
- **Document Processor:** Two-phase extraction (Cloud Run) + embedding/upsert (VM)
- **Code Interpreter:** Sandboxed Python execution via LibreCodeInterpreter container
4. **Data Layer:**
- **PostgreSQL:** User profiles, documents, knowledge bases, chat history, agent configs
- **Qdrant:** Vector embeddings for RAG retrieval
- **Redis:** Celery task broker and result backend
- **Uploads volume:** Knowledge docs and chat attachments
**Background Workers (Celery):**
- **celery-worker:** Processes two queues: `sharepoint` (document sync), `default` (general tasks)
- **celery-beat:** Scheduler for periodic tasks (e.g., token refresh, SharePoint sync)
**Document Flow:**
```
┌─────────────────────────────────────────────────────────┐
│ Browser (React SPA) │
│ http://localhost:3000 │
└────────────────────┬────────────────────────────────────┘
│ REST + SSE
┌─────────────────────────────────────────────────────────┐
│ FastAPI Backend │
│ http://localhost:8000 │
│ ├─ /auth (OAuth2 Entra ID + JWT) │
│ ├─ /api/v1/chat (RAG streaming) │
│ ├─ /api/v1/assistant (summarize/translate/extract) │
│ ├─ /api/v1/admin (user mgmt, config) │
│ └─ /docs (Swagger UI) │
└──┬──────────────────┬──────────────────┬────────────────┘
│ │ │
▼ ▼ ▼
PostgreSQL Redis Qdrant
(Users, (Sessions, (Vector
Conversations) Tokens) Embeddings)
Upload → FastAPI validator → Cloud Run nexus-processor (extract/chunk)
→ LlamaParse (OCR) + LLM structuring
→ Backend upsert to PostgreSQL + Qdrant vectors
```
**Auth Flow (PKCE):**
```
Browser → generate code_verifier + code_challenge (S256)
→ redirect to Azure AD
→ Azure returns auth_code
→ exchange code_code + code_verifier → MS access_token
→ POST ms_access_token to /api/v1/auth/login
→ Backend validates via Graph /me + auto-provisions user
→ Backend returns JWT (HS256, 15-min access + 7-day refresh)
```
**Container Topology:**
```
┌─────────────────────────────────────────┐
│ Browser (Next.js SPA @ /nexus) │
└────────────────┬────────────────────────┘
│ HTTPS/SSE
┌────────────────────────────┐
│ Apache (reverse proxy) │
└───┬──────────────────────┬─┘
│ /api/v1/* │ /nexus/*
▼ ▼
FastAPI:1222 static files
┌───────────┐ /var/www/html/
│ Backend │
└─────┬─────┘
┌────┼────┬────────┬──────────┐
▼ ▼ ▼ ▼ ▼
PG Redis Qdrant Celery Code-Int
:5432 :6379 :6333 worker :8877
└─ Cloud Run nexus-processor
└─ Azure AD / MS Graph
```
## Dev Commands
```bash
# Clone and navigate
# Clone repo
git clone git@bitbucket.org:zlalani/enterprise-ai-hub-nexus.git
cd Oliver-ai-bot_2.0
cd enterprise-ai-hub-nexus
# Start all backend services with Docker Compose
docker-compose up -d
# Configure environment
cp backend/.env.example backend/.env
# Edit backend/.env: DATABASE_URL, REDIS_URL, QDRANT_URL, ENTRA_CLIENT_ID,
# ENTRA_TENANT_ID, OPENAI_API_KEY, ANTHROPIC_API_KEY, JWT_SECRET (gen: openssl rand -hex 32)
# Verify services are healthy
docker ps
# Start backend services (PostgreSQL, Redis, Qdrant, FastAPI, Celery)
docker compose -f docker-compose.yml up -d
# Start frontend development server
# Apply migrations
docker exec nexus-backend alembic upgrade head
# Start frontend (separate terminal)
cd frontend
npm install
npm run dev
# http://localhost:3000/nexus
# Backend API documentation (once running)
# Open: http://localhost:8000/docs
# View API docs
# http://localhost:1222/docs
# View backend logs
docker logs nexus-backend -f
# View Celery worker logs
docker logs nexus-celery-worker -f
# View PostgreSQL logs
docker logs nexus-postgres -f
# Stop all services
docker-compose down
# View logs
docker-compose logs -f backend
docker-compose logs -f db
docker compose -f docker-compose.yml down
```
## Deployment
- **Server:** local (Docker containers)
- **Deploy:** `docker-compose up -d` (starts all services: PostgreSQL, Redis, Qdrant, FastAPI backend)
- **URL:** http://localhost:3000 (frontend), http://localhost:8000 (API)
- **Port:** 3000 (frontend), 8000 (backend API), 5432 (PostgreSQL), 6379 (Redis), 6333 (Qdrant)
- **Service:** Docker Compose managed (no systemd service)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/Oliver-ai-bot_2.0
- **Server:** `optical-web-1` (Google Compute Engine, n2d-standard-4, 4 vCPU, 16 GB RAM, europe-west1)
- **Deploy:** Run `./deploy.sh` from `/opt/enterprise-ai-hub-nexus/` as non-root user
- Pulls latest code (`git pull --ff-only`)
- Builds Docker images (`docker compose -f docker-compose.prod.yml build`)
- Builds frontend (`npm ci && npm run build`)
- Syncs static export to `/var/www/html/enterprise-ai-hub-nexus/`
- Starts backend, celery-worker, celery-beat
- Runs migrations (`alembic upgrade head`)
- **URL:** `https://nexus.oliver.agency/nexus` (Apache reverse proxy on optical-web-1, TLS terminated)
- **Port:** 443 (Apache HTTPS), backend listens on 127.0.0.1:1222 (proxied)
- **Service:** Docker Compose (`docker-compose.prod.yml`), no systemd service
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/Oliver-ai-bot_2.0`
## Environment Variables
Key variables (see `.env.example` for complete list):
- `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` — PostgreSQL credentials and database name
- `DATABASE_URL` — Full PostgreSQL connection string
- `REDIS_URL` — Redis connection (default: redis://localhost:6379/0)
- `QDRANT_URL` — Vector DB endpoint (default: http://localhost:6333)
- `ENTRA_CLIENT_ID`, `ENTRA_CLIENT_SECRET`, `ENTRA_TENANT_ID` — Microsoft Azure AD OAuth2 credentials
- `ENTRA_REDIRECT_URI` — OAuth callback URL (default: http://localhost:8000/api/v1/auth/callback)
- `OPENAI_API_KEY` — OpenAI API key (for GPT-5.1 chat & embeddings)
- `GOOGLE_API_KEY` — Google AI Studio key (for Gemini 2.5 Flash assistant)
- `ANTHROPIC_API_KEY` — Anthropic API key (for Claude Sonnet 4.5 writing)
- `JWT_SECRET` — Signing key for JWT tokens (min 32 chars, generate via `openssl rand -hex 32`)
- `JWT_ALGORITHM`, `JWT_EXPIRATION_MINUTES`, `REFRESH_TOKEN_EXPIRATION_DAYS` — Token config
- `NOTEBOOKLLAMA_URL` — Internal NotebookLlama service endpoint
- `MAX_UPLOAD_SIZE_MB` — File upload limit (default: 100 MB)
- `ENVIRONMENT` — development | staging | production
- `DEBUG` — Enable debug logging
## API / Endpoints
- `POST /api/v1/auth/login` — Microsoft Entra ID OAuth login redirect
- `POST /api/v1/auth/callback` — OAuth callback handler
- `POST /api/v1/auth/refresh` — Refresh JWT token
- `POST /api/v1/chat` — Stream RAG chat responses (SSE)
- `GET /api/v1/chat/history` — Retrieve conversation history
- `POST /api/v1/assistant/summarize` — Summarize meeting notes
- `POST /api/v1/assistant/translate` — Translate document (8 languages)
- `POST /api/v1/assistant/extract` — Extract action items
- `GET /api/v1/admin/users` — List users (Super Admin only)
- `POST /api/v1/admin/config` — Update LLM provider config (Super Admin)
- `GET /api/v1/profile` — Get current user profile
- `GET /docs` — Swagger API documentation
## Known Issues
- Admin Dashboard Analytics is a placeholder UI (not yet implemented)
- Mobile UI optimized but desktop-primary
- NotebookLlama integration URL must be configured in `.env`
- Frontend at 100% completion, Backend at 85% completion (likely referring to advanced features/edge cases)
## Git
- **Remote:** git@bitbucket.org:zlalani/enterprise-ai-hub-nexus.git
- **Latest commits:** Phase 6 complete (Assistant Mode, Admin Dashboard, final polish); MVP running in Docker
- **Branch strategy:** Not specified in provided logs
**Backend (`backend/.env`):**
- `DATABASE_URL` — PostgreSQL connection string (e.g., `postgresql+asyncpg://user:pass@localhost:5432/nexus`)
- `REDIS_URL` — Redis broker URL (e.g., `redis://localhost:6380`)
- `QDRANT_URL` — Qdrant HTTP endpoint (e.g., `http://localhost:6333`)
- `ENTRA_CLIENT_ID` — Azure Entra app registration client ID
- `ENTRA_TENANT_ID` — Azure tenant ID
- `JWT_SECRET` — HS256 signing key (generate: `openssl rand -hex 32`)
- `JWT_ALGORITHM` — Default: `HS256`
- `JWT_EXPIRATION_HOURS` — Access token TTL (default: 0.25 = 15 min)
- `REFRESH_TOKEN_EXPIRATION_DAYS` — Refresh token TTL (default: 7)
- `OPENAI_API_KEY` — OpenAI API key for GPT-5.2 and embeddings
- `ANTHROPIC_API_KEY` — Anthropic API key for Claude models
- `GOOGLE_API_KEY` — Google API key for Gemini
- `TAVILY_API_KEY` — Tavily web search API key
- `FIRECRAWL_API_KEY` — Firecrawl web scraping API key
- `LLAMAPARSE_API_KEY` — LlamaParse OCR API key
- `MS_GRAPH_SCOPES` — Microsoft Graph permission scopes (auto-synced during login)
- `CELERY_BROKER_URL` — Celery task broker (points to Redis)
- `CELERY_
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,193 +1,167 @@
---
name: "Activation Calendar Helper"
client: Oliver
client: Oliver Agency
status: active
server: ai-sandbox.oliver.solutions
tech: [PHP, JavaScript, PostgreSQL, Docker, Azure AD, OpenAI, Google Gemini, Anthropic Claude]
server: optical-web-1
tech: [React 19, TypeScript, Python 3.11, Quart, PostgreSQL 16, Azure AD, Google Gemini, Docker Compose]
local_path: /Users/ai_leed/Documents/Projects/Oliver/ac-helper
deploy: docker-compose up -d
deploy: sudo ./deploy.sh
url: https://ai-sandbox.oliver.solutions/ac-helper/
tags: [oliver, activation-calendar, ai, gemini, deliverables]
created: 2026-04-14
port: 8100
db: PostgreSQL
db: PostgreSQL 16
---
## Overview
AC Helper is a web-based marketing deliverables management tool for the Oliver agency that combines a smart spreadsheet interface with AI-powered automation. Users manage activation campaigns across a 3-level hierarchy (Category → Media → Sub-media) with AI commands powered by Gemini, OpenAI, and Claude. The platform supports multi-sheet workspaces, real-time exports, role-based access control via Azure AD SSO, and emergency token-based login for failover access.
AC Helper is a web-based internal tool for Oliver Agency staff to manage marketing deliverables (activation calendars). It combines an AI-powered natural language command interface for creating and updating deliverables with a brief extractor pipeline that automatically parses uploaded marketing documents (PDF, PPTX, DOCX, XLSX) and converts them into structured rows. The app is live in production on optical-web-1 and serves as the primary interface for activation calendar management.
## Tech Stack
- **Frontend:** JavaScript (Vanilla), Handsontable (spreadsheet grid), MSAL (Azure AD SPA PKCE auth)
- **Backend:** PHP (Legacy), Python/FastAPI (New containerized version), asyncpg for async PostgreSQL
- **Database:** PostgreSQL 16 Alpine (sheets, metadata, audit logs)
- **Infrastructure:** Docker Compose (app + postgres), Apache reverse proxy, environment-based configuration
- **AI/ML:** Google Gemini 2.0 Flash, OpenAI GPT-4.1, Anthropic Claude Opus/Sonnet, LlamaCloud (PDF parsing)
- **Key Libraries:** Jspreadsheet (older grid), Handsontable (newer grid), axios, dotenv
- **Frontend:** React 19 + TypeScript + Vite, Handsontable grid component, Tailwind CSS 4, Zustand state management
- **Backend:** Python 3.11, Quart (async Flask), Hypercorn ASGI server
- **Database:** PostgreSQL 16 with asyncpg connection pool; JSONB columns for structured data
- **Infrastructure:** Docker Compose (2 containers: app + postgres); Apache reverse proxy with TLS termination
- **AI/ML:** Google Gemini (flash model) for natural language commands; parallel LLM analysis (OpenAI GPT-4.1, Google Gemini, Anthropic Claude) for brief extraction
- **Auth:** Azure AD MSAL (PKCE flow); JWT validation with PyJWT; dev mode bypass support
- **Real-time:** WebSocket (Quart native) for job progress streaming
- **Key libraries:** axios, react-query, TypeScript, pydantic, asyncpg, aiohttp, PyJWT
## Architecture
The project is undergoing migration from a legacy PHP file-based system to a modern containerized Python/PostgreSQL stack:
**Current Production (Hybrid):**
- Apache serves static frontend files (`index.html`, `script.js`, `style.css`)
- Apache reverse-proxies API requests (`/api/`, `/ws/`) to Docker container on localhost:8100
- PostgreSQL runs in a separate Docker service (`ac-tool-db`)
- Session management via `SESSION_SECRET` environment variable
- Authentication: Azure AD MSAL (primary) + Emergency Token bypass
**Data Flow:**
1. User logs in via Azure AD PKCE flow or emergency token
2. Frontend makes API calls to `/api/` endpoints
3. Backend queries PostgreSQL for sheets, metadata, and user roles
4. AI commands sent to Gemini/OpenAI/Claude for natural language processing
5. Results written back to database and real-time updates via websockets
**Key Design Decisions:**
- Strict 3-level hierarchy enforced at DB level (prevents invalid combinations)
- Per-client category hierarchies support multi-tenant use
- Role-based access (admin, user, viewer) with admin bootstrap on first login
- Emergency token allows agency staff to access app if Azure AD is down
- Cost estimation for AI processing with `MAX_PROCESSING_COST_USD` limit
- File retention policy: uploaded files deleted after `FILE_RETENTION_HOURS`
AC Helper follows a client-server architecture with a single Apache reverse proxy fronting all traffic:
```
┌─────────────────────┐
│ Browser (SPA) │
│ • MSAL auth │
│ • Handsontable UI │
└──────────┬──────────┘
│ /api/, /ws (proxied)
┌─────────────────────┐
│ Apache 2.4 │
│ • Reverse proxy │
│ • Static files │
└──────────┬──────────┘
┌─────────────────────────────────────┐
│ Docker Container (Python/FastAPI) │
│ • API handlers │
│ • WebSocket server │
│ • AI model orchestration │
└──────────┬──────────────────────────┘
┌─────────────────────────────────────┐
│ PostgreSQL (Docker) │
│ • sheets, metadata, users, logs │
│ • audit trails │
└─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Browser (React SPA) │
│ - CommandBar (AI natural language input) │
│ - Handsontable grid (deliverable editing) │
│ - Brief upload interface │
│ - Admin panels │
└────────────────────┬────────────────────────────────────────────┘
│ HTTPS
┌─────────────────────────┐
│ Apache (optical-web-1) │
│ - TLS terminator │
│ - Reverse proxy │
└──┬──────────────────┬───┘
│ │
┌──────▼─────────┐ ┌───▼──────────┐
│ Static files │ │ API/WS proxy │
│ /var/www/html/ │ │ :8100→:8000 │
│ ac-helper/ │ └───┬──────────┘
└────────────────┘ │
┌────────────────────────────┐
│ Docker: ac-tool (Quart) │
│ - REST API blueprints │
│ - WebSocket handler │
│ - Background job workers │
│ - asyncpg pool │
└────────────┬───────────────┘
┌────────────────────────────┐
│ Docker: ac-tool-db │
│ PostgreSQL 16 (named vol) │
└────────────────────────────┘
```
**Key components:**
- **Frontend:** Vite SPA with Zustand state store; Handsontable for grid UI; Quart WebSocket listener for real-time job progress
- **Backend:** Blueprints split by domain (auth, sheets, jobs, ai, export, dropdowns, admin, clients); sheets manager handles CRUD with JSONB persistence; job manager queues and executes background tasks; AI command processor validates against dropdown constraints then calls Gemini
- **Database:** PostgreSQL with sheets stored as JSONB; clients, users, exports as relational tables
- **Job system:** In-memory queue with asyncio locks for concurrency control; background workers process brief extraction, exports, etc.; results broadcast via WebSocket
## Dev Commands
**Option A — Backend only (fastest for API work):**
```bash
# Local development (legacy PHP server, requires GEMINI_API_KEY in config.php)
php -S localhost:8000
cd /Users/ai_leed/Documents/Projects/Oliver/ac-helper
git clone git@bitbucket.org:zlalani/ac-helper.git
cp .env.example .env
# Edit .env: set DEV_MODE=true, GEMINI_API_KEY, POSTGRES_PASSWORD
# Start a separate PostgreSQL container:
docker run -d --name ac-pg \
-e POSTGRES_DB=achelper \
-e POSTGRES_USER=achelper \
-e POSTGRES_PASSWORD=achelper \
-p 5432:5432 postgres:16-alpine
# Docker development (full stack)
docker-compose up -d
# Terminal 1: Backend
cd backend
pip install -r requirements.txt
python run_server.py
# Listens on http://localhost:8000
# Stop containers
docker-compose down
# Terminal 2: Frontend dev server
cd frontend
npm install
npm run dev
# Listens on http://localhost:5173; proxies /api to localhost:8000
```
# View logs
docker-compose logs -f app
**Option B — Full Docker Compose (preferred for realistic testing):**
```bash
cd /Users/ai_leed/Documents/Projects/Oliver/ac-helper
cp .env.example .env
# Edit .env: GEMINI_API_KEY, SESSION_SECRET, POSTGRES_PASSWORD (at minimum)
docker compose up --build
# Access http://localhost:8100
```
# Access database CLI
docker-compose exec postgres psql -U achelper -d achelper
**Build frontend only:**
```bash
cd frontend && npm run build
# Output: dist/ (served by Apache in prod)
```
# Rebuild container after code changes
docker-compose up -d --build
# Run database migrations (if applicable)
docker-compose exec app python /app/server/migrations/run.py
**Run tests:**
```bash
cd backend && pytest
```
## Deployment
- **Server:** ai-sandbox.oliver.solutions
- **Deploy:** `docker-compose up -d` (inside VirtualHost with Apache reverse proxy rules)
- **Server:** optical-web-1 (ai-sandbox.oliver.solutions)
- **Deploy:** `sudo ./deploy.sh` (run from project root as root)
- **URL:** https://ai-sandbox.oliver.solutions/ac-helper/
- **Port:** 8100 (Docker app port, mapped from container's 8000)
- **Service:** Docker Compose (no systemd service)
- **Port:** 8100 (internal Docker port; Apache proxies from 443)
- **Service:** Docker Compose (no systemd service; containers use `restart: unless-stopped`)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/ac-helper
**Deployment Steps:**
1. Copy `.env.example``.env` and fill in secrets
2. Ensure Apache modules enabled: `a2enmod proxy proxy_http proxy_wstunnel`
3. Add VirtualHost reverse proxy rules (see docker-compose.yml comments)
4. Run `docker-compose up -d`
5. Verify: `curl https://ai-sandbox.oliver.solutions/ac-helper/`
**Deploy process** (automated by deploy.sh):
1. Validates `.env` exists and required keys are set
2. Checks Docker, docker compose, and git are installed
3. `git pull --ff-only` (no merges)
4. `docker compose build --pull` (rebuilds both containers)
5. Extracts frontend dist to `/var/www/html/ac-helper/` and fixes ownership
6. `docker compose down && docker compose up -d` (blue-green restart)
7. Waits for PostgreSQL healthcheck and app `/health` endpoint
8. **Rollback:** `git revert <commit>` + `./deploy.sh` (or manual `docker compose down && git checkout <previous> && docker compose up`)
## Environment Variables
| Variable | Purpose |
|----------|---------|
| `APP_PORT` | Docker host port (default 8100) |
| `AZURE_TENANT_ID` | Azure AD tenant for SSO |
| `AZURE_CLIENT_ID` | Azure AD SPA application ID |
| `AZURE_REDIRECT_URI` | Azure AD callback URL |
| `ADMIN_EMAILS` | Comma-separated emails auto-promoted to admin on first login |
| `EMERGENCY_TOKEN` | Long random hex string to enable bypass login (leave empty to disable) |
| `EMERGENCY_USER_EMAIL` | Email for emergency token user |
| `GEMINI_API_KEY` | Google Gemini API key (required for AI commands) |
| `GOOGLE_MODEL` | Gemini model name (default: gemini-2.0-flash-exp) |
| `OPENAI_API_KEY` | OpenAI API key |
| `OPENAI_MODEL` | GPT model (default: gpt-4.1) |
| `ANTHROPIC_API_KEY` | Claude API key |
| `LLAMA_CLOUD_API_KEY` | LlamaCloud for PDF parsing |
| `POSTGRES_PASSWORD` | PostgreSQL achelper user password |
| `SESSION_SECRET` | Secure session signing key (generate: `python3 -c "import secrets; print(secrets.token_hex(32))"`) |
| `MAX_UPLOAD_SIZE_MB` | Max file upload size (default: 200) |
| `FILE_RETENTION_HOURS` | Auto-delete uploaded files after N hours |
| `DEV_MODE` | Boolean; if true, uses DEV_USER_ID/DEV_USER_ROLE instead of Azure AD |
| `MAX_PROCESSING_COST_USD` | Kill-switch for AI processing (prevents runaway API costs) |
## API / Endpoints
**Authentication:**
- `POST /api/auth/login` — Exchange Azure token for session
- `POST /api/auth/emergency-token` — Bypass login with `EMERGENCY_TOKEN`
- `POST /api/auth/logout` — Terminate session
**Sheets:**
- `GET /api/sheets/` — List all sheets for current user
- `GET /api/sheets/{sheet_id}` — Fetch sheet data + hierarchy
- `POST /api/sheets/` — Create new sheet
- `PUT /api/sheets/{sheet_id}` — Update sheet metadata
- `DELETE /api/sheets/{sheet_id}` — Delete sheet
**Data:**
- `POST /api/sheets/{sheet_id}/rows` — Add/update rows
- `DELETE /api/sheets/{sheet_id}/rows/{row_id}` — Delete row
- `GET /api/sheets/{sheet_id}/export?format=csv` — Export to CSV
**AI:**
- `POST /api/ai/command` — Execute natural language command (Gemini)
- `WS /ws` — WebSocket for real-time updates + streaming AI responses
**Admin:**
- `GET /api/admin/users` — List users (admin only)
- `PUT /api/admin/users/{user_id}/role` — Assign role (admin only)
- `GET /api/admin/audit-log` — View audit trail (admin only)
## Known Issues
- **Migration in progress:** Project transitioning from PHP file-based storage to PostgreSQL; some legacy PHP code paths may still exist
- **Handsontable rendering:** Requires ResizeObserver workaround for pixel height calculation on empty grids
- **AI model drift:** Recent commits show model swaps (gemini-3.1-pro-preview → gemini-2.0-flash-exp); verify `GOOGLE_MODEL` matches deployed version
- **Azure AD token expiry:** Frontend auto-refreshes tokens, but edge cases with long-running sessions may require page reload
- **WebSocket reliability:** No explicit reconnection logic documented; connection drops require manual page refresh
- **CSV export templates:** Per-client/per-user overrides exist but global default behavior under-documented
## Git
- **Remote:** git@bitbucket.org:zlalani/ac-helper.git
- **Recent activity:** Active development; last 20 commits show migration to PostgreSQL, Azure AD auth hardening, multi-client support, and AI model tuning
| Variable | Purpose | Dev default | Production | Required |
|----------|---------|------------|-----------|----------|
| `DEV_MODE` | Bypass Azure AD auth; allow all endpoints | `true` | `false` | Yes |
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://achelper:achelper@localhost:5432/achelper` | Built from `POSTGRES_PASSWORD` | Yes |
| `POSTGRES_PASSWORD` | DB password | `achelper_secret` | Set in `.env` (secret) | Yes |
| `SESSION_SECRET` | JWT signing key | `change-me-in-production` | Long random string (50+ chars) | Yes |
| `GEMINI_API_KEY` | Google Gemini API key (AI commands) | (empty) | Set in `.env` | Yes |
| `GEMINI_MODEL` | Gemini model identifier | `gemini-3-flash-preview` | `gemini-3-flash-preview` | No |
| `OPENAI_API_KEY` | OpenAI API key (brief extractor) | (empty) | Set in `.env` | Optional |
| `OPENAI_MODEL` | OpenAI model identifier | `gpt-4.1` | `gpt-4.1` | No |
| `ANTHROPIC_API_KEY` | Anthropic Claude API key (brief extractor) | (empty) | Set in `.env` | Optional |
| `ANTHROPIC_MODEL_SONNET` | Claude model identifier | `claude-sonnet-4-5-20250929` | `claude-sonnet-4-5-20250929` | No |
| `AZURE_TENANT_ID` | Azure AD tenant | (from .env.example) | `e519c2e6-bc6d-4fdf-8d9c-923c2f002385` | Yes (prod) |
| `AZURE_CLIENT_ID` | Azure AD app registration | (from .env.example) | `9079054c-9620-4757-a256-23413042f1ef` | Yes (prod) |
| `AZURE_REDIRECT_URI` | OAuth callback URL | `http://localhost:5173/` | `https://ai-sandbox.oliver.solutions/ac-helper/` | Yes (prod) |
| `ADMIN_EMAILS` | Comma-separated admin emails | (empty) | `daveporter@oliver.agency,vadymsamoilenko@oliver.agency` | No |
| `ADMIN_EMAIL` | Primary admin email | (empty) | `daveporter@oliver.agency` | No |
| `EMERGENCY_TOKEN` | Bypass SSO token (for outages) | (empty) | Set in `.env`; leave blank to disable | Optional |
| `MAX_CONCURRENT_JOBS` | Max parallel job queue size | `5` | `5` | No |
| `MAX_UPLOAD_SIZE_MB` |
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,166 +1,173 @@
---
name: "AC Tool"
client: Oliver Agency
client: Oliver Agency (Internal)
status: active
tech: [Python, FastAPI, React, TypeScript, Docker, Azure AD, Handsontable, Claude/Gemini/GPT-4]
tech: [React, TypeScript, Quart, Python, PostgreSQL, Docker, Azure AD]
local_path: /Users/ai_leed/Documents/Projects/Oliver/ac-tool
deploy: docker-compose up -d
deploy: sudo bash /opt/ac-tool/deploy.sh
url: https://ai-sandbox.oliver.solutions/ac-helper/
tags: [oliver, activation-calendar]
created: 2026-04-14
server: ai-sandbox.oliver.solutions
server: optical-web-1
port: 8100
db: PostgreSQL 16
---
## Overview
**ac-tool** (formerly ac-helper) is a web application for AI-powered brief extraction and spreadsheet manipulation. It integrates multiple LLM providers (OpenAI, Google Gemini, Anthropic Claude) to process documents, extract key information, and populate structured data in interactive spreadsheets. Users authenticate via Azure AD (MSAL) and can manage AI jobs with real-time progress tracking via WebSocket. The tool is designed for agency workflows requiring intelligent document analysis and data consolidation.
AC Tool (Activation Calendar Tool / AC Helper) is an internal web application for Oliver Agency that combines AI-powered brief extraction with a collaborative spreadsheet editor. Teams upload client campaign briefs in multiple formats (PDF, PPTX, DOCX, XLSX), AI models extract and structure deliverables, users review and edit the results in an interactive calendar grid, and export formatted CSV files. The system orchestrates multiple LLM providers in parallel, consolidates results, and provides real-time job progress updates via WebSocket.
## Tech Stack
- **Frontend:** React (TypeScript), Handsontable (dark theme spreadsheet UI), MSAL for Azure AD auth, Axios for HTTP
- **Backend:** Python 3 + FastAPI, uvicorn ASGI server, WebSocket support for real-time job updates
- **Database:** File-based storage in `/app/data` (no traditional DB)
- **Infrastructure:** Docker Compose (containerized), Apache reverse proxy frontend (static files + proxying)
- **AI/ML:** OpenAI (gpt-4.1), Google Gemini (gemini-2.0-flash), Anthropic Claude (Opus/Sonnet), LlamaCloud (PDF parsing)
- **Key libraries:** pydantic (config), FastAPI (REST/WS), MSAL Python (auth), llama-cloud (document extraction), google-generativeai, anthropic client
- **Frontend:** React 18 + TypeScript, Vite (build), Handsontable (spreadsheet editor), MSAL (Azure AD auth), Axios (HTTP client)
- **Backend:** Quart (async Python web framework), Hypercorn (ASGI server), asyncio task workers
- **Database:** PostgreSQL 16 (asyncpg async driver)
- **Infrastructure:** Docker + Docker Compose, Apache reverse proxy (optical-web-1)
- **AI/ML:** Anthropic Claude, Google Gemini (parallel LLM analysis + consolidation)
- **Key libraries:** LibreOffice (document parsing), PyMuPDF, python-pptx, openpyxl, Handsontable, MSAL.js
## Architecture
The application follows a **three-tier proxy pattern**:
1. **Apache Reverse Proxy** (frontend, static files)
- Serves frontend `/ac-helper/` from disk
- Routes API calls (`/ac-helper/api/*`) to Docker container on `http://localhost:8100/api/`
- Routes WebSocket (`/ac-helper/ws`) to container on `ws://localhost:8100/ws`
- Falls back to `index.html` for SPA routing
AC Tool is a three-layer system deployed on a single server:
2. **FastAPI Backend** (Docker container, port 8000 internally, 8100 externally)
- REST API endpoints (`/api/*`) for auth, jobs, spreadsheets, models
- WebSocket endpoint (`/ws`) for real-time job progress streaming
- LLM orchestration: multi-model parallel requests, cost estimation, retry logic
- File storage in `/app/data` (mounted volume)
**Frontend (React SPA):** Built with Vite, served as static files by Apache at `/ac-helper/`. Handles user auth via MSAL (Azure AD PKCE flow), brief upload UI, deliverable review, interactive spreadsheet editing with Handsontable, and natural-language AI commands.
3. **React Frontend** (SPA)
- Azure AD login via MSAL (PKCE flow, no client secret)
- Spreadsheet UI via Handsontable with dynamic dropdowns
- Real-time job monitoring via WebSocket
- Document upload, brief extraction, model selection
**Backend (Quart API):** Python async web framework running on Hypercorn (Docker container, port 8000 → proxied as `localhost:8100` by Apache). Seven API blueprints handle auth, job management, sheet persistence, exports, AI commands, dropdowns/enums, admin operations, and client management. A WebSocket handler (`/ws`) broadcasts real-time job progress to connected clients.
**Database (PostgreSQL 16):** Stores sheets (activation calendars with structured rows), client metadata, category hierarchies, and audit logs. Connected via asyncpg async pool. Old JSON-based storage migrated to PostgreSQL at deployment time.
**Job Processing Pipeline:**
1. User uploads brief file → `JobManager` creates in-memory job, writes file to `/app/data/uploads`
2. Background worker extracts text (LibreOffice, PyMuPDF, python-pptx, openpyxl) based on file type
3. Multiple LLM providers (Anthropic + Gemini) analyze extracted text in parallel
4. Consolidation model merges provider outputs into a single structured list
5. CSV generation writes deliverables to `/app/data/outputs`
6. WebSocket broadcasts progress (`job.created``job.accepted``job.completed` or `job.failed`) to user in real-time
7. User reviews, edits, and imports into a persistent sheet
**Key Data Flow:**
```
User Login (Azure AD)
→ Fetch user role/permissions
→ Upload brief/spreadsheet
→ Select AI models
→ Backend: parse → extract via LLM(s) → consolidate
→ WS: stream job progress to frontend
→ Backend: save results to /app/data
→ Frontend: populate Handsontable with extracted data
Browser (React SPA on :5173 dev / Apache prod)
↓ HTTPS + WebSocket
Apache (ai-sandbox.oliver.solutions)
├─ /ac-helper/ → /var/www/html/ac-helper/ (static)
├─ /ac-helper/api/* → http://localhost:8100/api/ (Docker)
└─ /ac-helper/ws → ws://localhost:8100/ws (Docker)
Docker: ac-tool (Hypercorn :8000 → host :8100)
├─ Quart app (routes, WebSocket, background workers)
├─ JobManager (in-memory queue + file I/O)
└─ asyncpg connection pool
Docker: ac-tool-db (PostgreSQL :5432)
```
## Dev Commands
```bash
# Local development (requires Python 3.11+, Node.js 18+)
# Backend only (if you have server/config_runtime.py set up):
cd /Users/ai_leed/Documents/Projects/Oliver/ac-tool
python3 -m venv venv
source venv/bin/activate
# Clone repo
git clone git@bitbucket.org:zlalani/ac-helper.git ac-tool
cd ac-tool
# Setup local environment
cp .env.example .env
# Edit .env: set DEV_MODE=true, add GEMINI_API_KEY
# Terminal 1: Frontend dev server (Vite, :5173)
cd frontend
npm install
npm run dev
# Terminal 2: Backend dev server (Quart, :8000)
cd backend
pip install -r requirements.txt
python server/main.py
docker compose up -d postgres # Start PostgreSQL only in Docker
python run_server.py
# Full Docker Compose (recommended):
docker-compose up --build
# Rebuild container (no cache):
docker-compose build --no-cache && docker-compose up -d
# View logs:
docker-compose logs -f app
# Stop container:
docker-compose down
# Access locally:
# API: http://localhost:8100/api/
# WebSocket: ws://localhost:8100/ws
# Frontend (static): served by Apache at https://ai-sandbox.oliver.solutions/ac-helper/
# In DEV_MODE=true, auth bypasses Azure AD (auto-grants admin role)
# Frontend proxies to http://localhost:8000 (vite.config.ts)
# Access app at http://localhost:5173
```
## Deployment
- **Server:** ai-sandbox.oliver.solutions
- **Deploy:** `docker-compose up -d` (after pushing to Bitbucket and pulling on target server)
- **Server:** `optical-web-1` (ai-sandbox.oliver.solutions)
- **Deploy:** `sudo bash /opt/ac-tool/deploy.sh`
- **URL:** `https://ai-sandbox.oliver.solutions/ac-helper/`
- **Port:** 8100 (host) → 8000 (container internal)
- **Service:** Docker Compose (container name: `ac-tool`, restart policy: `unless-stopped`)
- **Port:** 8100 (Docker mapped; 8000 internal Hypercorn)
- **Service:** None (Docker Compose managed)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/ac-tool`
**Pre-deployment checklist:**
1. Copy `.env.example` to `.env` and fill in all secrets (API keys, Azure credentials, SESSION_SECRET)
2. Set `DEV_MODE=false` in production `.env`
3. Ensure `AZURE_REDIRECT_URI` matches production domain
4. Configure Apache VirtualHost with proxy directives (see docker-compose.yml comments)
5. Run `docker-compose up -d` on target server
6. Verify `/api/health` or similar endpoint responds
**First-time setup:**
1. SSH to `optical-web-1`
2. Clone to `/opt/ac-tool`: `git clone git@bitbucket.org:zlalani/ac-helper.git /opt/ac-tool`
3. Create `.env` with `GEMINI_API_KEY`, `SESSION_SECRET`, `POSTGRES_PASSWORD`, `ADMIN_EMAILS`
4. Run `sudo bash /opt/ac-tool/deploy.sh`
5. Add Apache VirtualHost config (see infrastructure.md) and reload Apache
**Subsequent deploys:** Run `sudo bash /opt/ac-tool/deploy.sh` (pulls latest, rebuilds Docker, migrates data if needed, health-checks)
**Container logs:**
- App: `docker logs -f ac-tool`
- DB: `docker logs -f ac-tool-db`
## Environment Variables
Core environment variables (see `.env.example` for full list):
**Application:**
- `APP_PORT` — Host port Docker binds to (default: 8100)
- `DEV_MODE` — Enable mock auth, bypass real Azure AD (default: false; **must be false in production**)
- `DEV_USER_ID` / `DEV_USER_ROLE` — Mock user credentials when DEV_MODE=true
**Authentication (Azure AD / MSAL):**
- `AZURE_TENANT_ID` — Azure tenant (default: e519c2e6-bc6d-4fdf-8d9c-923c2f002385)
- `AZURE_CLIENT_ID` — App registration client ID (default: 9079054c-9620-4757-a256-23413042f1ef)
- `AZURE_REDIRECT_URI` — Post-login redirect (default: https://ai-sandbox.oliver.solutions/ac-helper/)
- `MSAL_*` — Same as AZURE_* (read by backend server/config_runtime.py)
- `SESSION_SECRET` — Session signing key (generate: `python3 -c "import secrets; print(secrets.token_hex(32))"`)
- `ADMIN_EMAIL` — First login with this email gets admin role
**AI Model APIs:**
- `OPENAI_API_KEY` / `OPENAI_MODEL` / `OPENAI_REASONING_EFFORT` / `OPENAI_TIMEOUT` / `OPENAI_MAX_RETRIES`
- `GEMINI_API_KEY` / `GOOGLE_MODEL` / `GOOGLE_TEMPERATURE` / `GOOGLE_MAX_OUTPUT_TOKENS` / `GOOGLE_THINKING_BUDGET` / `GOOGLE_TIMEOUT`
- `ANTHROPIC_API_KEY` / `ANTHROPIC_MODEL_OPUS` / `ANTHROPIC_MODEL_SONNET` / `ANTHROPIC_TEMPERATURE` / `ANTHROPIC_MAX_TOKENS` / `ANTHROPIC_THINKING_BUDGET` / `ANTHROPIC_TIMEOUT`
- `LLAMA_CLOUD_API_KEY` — For advanced PDF parsing (fallback: local extraction if absent)
**Brief Extraction Processing:**
- `DEFAULT_PRIMARY_MODELS` — Comma-separated model IDs for initial extraction (e.g., `anthropic-sonnet45,google-gemini20`)
- `DEFAULT_CONSOLIDATION_MODEL` — Model for consolidating multiple extractions
- `MINIMUM_SUCCESS_THRESHOLD` — Min successful extractions required
- `ENABLE_COST_ESTIMATION` — Pre-calculate API costs (default: true)
- `MAX_PROCESSING_COST_USD` — Reject jobs exceeding this cost (default: 10.00)
- `MAX_CONCURRENT_JOBS` — Max parallel jobs (default: 5)
**Network & Security:**
- `ALLOWED_ORIGINS` — CORS allowed origins (default: https://ai-sandbox.oliver.solutions)
- `FILE_RETENTION_HOURS` — Auto-delete uploaded files after N hours (default: 24)
- `MAX_UPLOAD_SIZE_MB` — Max file upload size (default: 200)
- `WS_PING_INTERVAL_SECONDS` — WebSocket keepalive interval (default: 30)
- `DEV_MODE` — If `true`, bypasses Azure AD auth and auto-grants admin role (dev only)
- `GEMINI_API_KEY` — Google Gemini API key (required for LLM analysis)
- `SESSION_SECRET` — Secret for session signing (required in production)
- `POSTGRES_PASSWORD` — PostgreSQL password (required in production)
- `ADMIN_EMAILS` — Comma-separated list of admin user email addresses (required in production)
- `DATA_DIR` — Root data directory; defaults to `/app/data` (contains uploads, outputs, sheets)
- `UPLOADS_DIR` — Temp directory for uploaded briefs; defaults to `{DATA_DIR}/uploads`
- `OUTPUTS_DIR` — Output CSV directory; defaults to `{DATA_DIR}/outputs`
- `SHEETS_DIR` — Legacy JSON sheets directory; defaults to `{DATA_DIR}/sheets` (used for migration)
- `EMERGENCY_TOKEN` — Bypass token for login when Azure AD unavailable (optional)
- `MAX_CONCURRENT_JOBS` — Max parallel background workers; defaults to 3
## API / Endpoints
**Key REST endpoints** (all prefixed `/api/`):
- `POST /auth/token` — Exchange Azure AD token for session
- `GET /me` — Fetch current user profile & permissions
- `POST /jobs` — Create a new AI extraction job
- `GET /jobs/{job_id}` — Get job status & results
- `POST /upload` — Upload document for processing
- `GET /spreadsheets/{sheet_id}` — Fetch spreadsheet data
- `PUT /spreadsheets/{sheet_id}` — Update spreadsheet cells
**Auth:**
- `POST /api/auth/login` — Azure AD token validation
- `POST /api/auth/logout` — Clear session
- `GET /api/auth/me` — Current user info
**Jobs (brief extraction):**
- `POST /api/jobs` — Upload brief file, create job
- `GET /api/jobs/{job_id}` — Fetch job status/metadata
- `GET /api/jobs/{job_id}/output` — Download generated CSV
**Sheets (activation calendars):**
- `GET /api/sheets` — List user's sheets
- `POST /api/sheets` — Create new sheet
- `GET /api/sheets/{sheet_id}` — Fetch sheet data + rows
- `PUT /api/sheets/{sheet_id}` — Update sheet metadata
- `PUT /api/sheets/{sheet_id}/rows` — Bulk update rows
- `POST /api/sheets/{sheet_id}/import` — Import job output into sheet
- `POST /api/sheets/{sheet_id}/export` — Export sheet as CSV
**AI Commands:**
- `POST /api/ai/command` — Apply natural-language mutation to sheet rows (e.g., "duplicate all rows where Media is Social")
**Admin:**
- `GET /api/admin/clients` — List clients
- `POST /api/admin/clients` — Create client
- `PUT /api/admin/clients/{client_id}` — Update client + category hierarchy
**WebSocket:**
- `GET /ws` — Connect for real-time job progress updates (emits JSON messages with job state, progress %, LLM responses)
- `GET /ws`Upgrade to WebSocket; receive real-time job progress (`job.created`, `job.accepted`, `job.completed`, `job.failed`)
## Known Issues
- **Handsontable rendering:** Dynamic column/row updates sometimes require manual sheet reload (workaround: ResizeObserver implemented, see commit 076675f)
- **Azure token expiry:** Auto-refresh token before every request (fixed in commit fb26d9a, but monitor for edge cases)
- **Model config alignment:** Verify env var names match backend expectations (recent fixes in commits d71a044 & ba9af5f)
- **PDF extraction fallback:** LlamaCloud parsing requires valid API key; local fallback used if absent (commit fc430cc)
- **Dark theme contrast:** Recently improved (commit 45c6b2e); report accessibility issues if found
- **No persistent database:** All data stored in `/app/data` volumes; ensure backups are configured for production
- Legacy JSON-based sheet storage migrated to PostgreSQL; migration runs at deploy time if old files exist. No rollback implemented yet.
- Emergency token login requires manual `.env` configuration; no UI toggle.
- Background job workers are in-memory (not persistent across container restart); jobs in progress will be lost if container crashes.
- CSV export template customization is per-client but lacks UI for managing templates (must be edited in database or code).
## Git
- **Remote:** `git@bitbucket.org:zlalani/ac-helper.git`
- **Default branch:** main (inferred)
- **Recent work:** UI fixes (dark theme,
- **Recent work:** PostgreSQL migration (8da149b), emergency token auth, AI commands, custom CSV export templates, client management, Azure AD token refresh fixes
- **Branch:** Assume `main` is production-ready
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,27 +1,171 @@
---
name: "Ai Cost Tracker"
client: "TBD"
client: Oliver Internal
status: active
server: optical-dev
tech: []
tech: [React, TypeScript, FastAPI, MongoDB, Redis, Celery, Azure AD]
local_path: /Volumes/SSD/Projects/Oliver/ai-cost-tracker
deploy:
url:
deploy: docker compose -f infra/docker-compose.yml up -d
url: https://cost-tracker.oliver.solutions
tags:
- project
created: 2026-04-27
port: 5174
db: MongoDB
---
## Overview
> New project — fill in during first session.
**ai-cost-tracker** is a shared AI cost tracking service for Oliver Agency projects that monitors LLM usage costs across workspaces, teams, and projects. It provides a REST API for recording AI calls and an admin dashboard for analytics, budgeting, and cost alerting. Built with FastAPI + React, it integrates Azure AD SSO for admin access and syncs pricing data from LiteLLM on a schedule.
## Tech Stack
- **Frontend:**
- **Backend:**
- **Infrastructure:**
- **Frontend:** React 18+, TypeScript, Vite, Recharts (analytics), CSS-in-JS design system
- **Backend:** FastAPI (Python), Pydantic, SQLAlchemy or similar ORM
- **Database:** MongoDB (primary), Redis (caching, background tasks)
- **Infrastructure:** Docker Compose, Celery (background jobs)
- **Auth:** Azure AD SSO, JWT tokens, dev login fallback
- **Integrations:** SendGrid (alerts), LiteLLM pricing sync (GitHub), OpenAPI/Swagger docs
- **Key libraries:** FastAPI, Uvicorn, Celery, Motor (async MongoDB), redis-py
## Architecture
**ai-cost-tracker** is a three-tier SaaS application:
1. **Frontend** (React + Vite, port 5174): Admin dashboard for viewing cost analytics, budgets, and alerts. Routes: dashboard, explorers (pivot/charts), budgets, users, audit logs. Authenticates via Azure AD or local dev login; stores JWT in localStorage.
2. **Backend** (FastAPI, port 8001): REST API for recording LLM usage events, querying costs, managing budgets/alerts, and user administration. Exposed on `/admin/` prefix in production. Background tasks (Celery) handle pricing sync, alert generation, and scheduled reports.
3. **Data layer** (MongoDB + Redis): MongoDB stores cost events, users, budgets, audit logs, and pricing tables. Redis caches data and queues Celery tasks.
```
┌─────────────────────────────────────────────────────────────────┐
│ Admin Dashboard (React) │
│ http://localhost:5174 (dev) → Azure AD SSO or dev login │
└────────────────────────┬────────────────────────────────────────┘
│ JWT auth
┌─────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (http://localhost:8001) │
│ /admin/api/ routes: events, users, budgets, alerts, auth │
└──────────────────────┬──────────────────────┬───────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ MongoDB │ │ Redis + Celery │
│ cost_tracker DB │ │ (async tasks) │
│ - events │ │ - pricing sync │
│ - users │ │ - alert jobs │
│ - budgets │ │ - caching │
└──────────────────┘ └────────────────────┘
┌─────────────────┐
│ SendGrid │
│ (email alerts)│
└─────────────────┘
```
**Key design decisions:**
- **Async first:** Celery handles background pricing syncs and alert generation to avoid blocking API requests.
- **Pricing abstraction:** LiteLLM pricing pinned by commit hash; stored in MongoDB for fast lookups.
- **User resolution:** User IDs (from API calls) resolved to names via admin user table for dashboard display.
- **Scoped analytics:** Costs tracked by workspace, team, project, source_app, and user; frontend filters via dropdowns.
- **Audit trail:** All admin actions logged for compliance.
## Dev Commands
```bash
# Copy and configure environment
cp .env.example .env
# Edit .env: set Azure AD credentials, JWT_SECRET, SendGrid API key, etc.
# Start all services (MongoDB, Redis, FastAPI, Celery worker)
docker compose -f infra/docker-compose.yml up -d
# Rebuild after code changes
docker compose -f infra/docker-compose.yml up -d --build
# Frontend development server (auto-reload on port 5174)
cd frontend && npm run dev
# Backend development server (auto-reload on port 8001)
cd backend && uvicorn app.main:app --reload --port 8001
# Frontend quality checks
cd frontend && npm run lint # ESLint
cd frontend && npm run type-check # TypeScript strict mode
cd frontend && npm run build # Production build
# View FastAPI interactive docs
# → http://localhost:8001/docs (Swagger UI)
# View logs
docker compose -f infra/docker-compose.yml logs -f backend
docker compose -f infra/docker-compose.yml logs -f frontend
```
## Deployment
- **Local path:** `/Volumes/SSD/Projects/Oliver/ai-cost-tracker`
- **Server:** optical-dev (`https://cost-tracker.oliver.solutions`)
- **Deploy:** `docker compose -f infra/docker-compose.yml up -d [--build]`
- **URL:** https://cost-tracker.oliver.solutions (production) | http://localhost:5174 (dev frontend) | http://localhost:8001 (dev API)
- **Port:** 5174 (frontend), 8001 (backend), 27017 (MongoDB), 6379 (Redis)
- **Service:** Docker Compose managed; no systemd unit
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/ai-cost-tracker`
- **In-place deploy:** Backend includes `update.sh` script for zero-downtime deployments on optical-dev
## Environment Variables
- `APP_ENV``dev` or `prod`; controls debug mode and feature flags
- `JWT_SECRET` — Sign key for JWT tokens; **must be generated** with `openssl rand -hex 32` in production (not left as `change-me`)
- `MONGODB_URI` — MongoDB connection string (includes credentials and auth source)
- `MONGODB_DB` — Database name (`cost_tracker`)
- `REDIS_URL` — Redis connection string for caching and Celery broker
- `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` — Azure AD app registration for SSO; omit for dev login fallback
- `AZURE_AUTHORITY` — Azure login endpoint
- `AZURE_REDIRECT_URI` — Callback URI after SSO (`https://cost-tracker.oliver.solutions/v1/auth/callback`)
- `SENDGRID_API_KEY` — SendGrid API key for sending cost alerts
- `EMAIL_FROM` — Sender email address (`no-reply@oliver.agency`)
- `ALERT_EMAIL_TO` — Comma-separated list of alert recipient emails
- `LITELLM_PRICES_URL` — GitHub raw URL to LiteLLM pricing JSON (BerriAI/litellm main branch)
- `LITELLM_COMMIT_HASH` — Git commit hash to pin pricing version; background task syncs pricing on schedule
- `CORS_ORIGINS` — Comma-separated whitelist of allowed origins (e.g., `http://localhost:5174,https://optical-dev.oliver.solutions`)
- `FRONTEND_CALLBACK_URL` — Redirect after Azure SSO callback; used by frontend to construct auth redirect
## API / Endpoints
**Base URL (local):** `http://localhost:8001` | **Production:** `https://cost-tracker.oliver.solutions/admin`
Interactive docs: `http://localhost:8001/docs` (Swagger) or `http://localhost:8001/redoc` (ReDoc)
**Key routes** (see `backend/app/api/`):
- `POST /v1/events` — Record LLM usage event (called by client apps)
- `GET /admin/api/events` — Query cost events with filters (dashboard)
- `GET /admin/api/analytics` — Aggregated cost metrics
- `POST /admin/api/budgets` — Create/update budget alerts
- `GET /admin/api/users` — List users; resolve IDs to names
- `POST /admin/api/users` — Add user
- `PATCH /admin/api/users/{id}` — Update user
- `DELETE /admin/api/users/{id}` — Delete user
- `GET /admin/api/audit-logs` — Audit trail
- `POST /v1/auth/callback` — Azure AD SSO callback
- `POST /v1/auth/dev-login` — Dev mode login (local only)
## Known Issues
- **No SSH to optical-dev** without explicit user instruction — hard constraint in `CLAUDE.md`
- **Pricing sync failures** not monitored; if Celery task fails silently, stale pricing may be used until next scheduled run
- **User name resolution** on dashboard requires pre-populated user table; IDs without matching records display as-is
- **Alert emails** depend on SendGrid availability; no fallback notification method
- **CORS misconfiguration** will block frontend-to-backend requests; all new origins must be explicitly added to `CORS_ORIGINS`
- **Local Azure AD testing:** Dev mode login available when Azure AD not configured; production always requires SSO
## Git
- **Remote:** `git@bitbucket.org:zlalani/ai-cost-tracker.git`
- **Latest commits:** Feature-rich dashboard (budgets, filters, redesigns), pivot explorer, pricing sync, user management, audit logs
- **Branch:** Implied default (main or develop; check remote)
## Sessions
### 2026-04-27 Asked | Done | Log
@ -348,4 +492,4 @@ created: 2026-04-27
| 2026-04-27 | MCP configuration and dev login setup | config.json with placeholder API key, backend startup fixes, Docker port conflict resolution | config.json, docker-compose configuration files |
| 2026-04-27 | Prepare for optival-dev | Code reviewed, architecture documented, pushed to repo | Backend (FastAPI/MongoDB/Redis/Celery), Frontend (React/Vite/nginx), SDK (oliver_cost_tracker) |
## Related
## Related

View file

@ -2,171 +2,175 @@
name: "Amazon Transcreation"
client: Amazon
status: active
server: local
tech: [Next.js 14, FastAPI, Python, PostgreSQL, Redis, Celery, Claude LLM]
server: optical-dev
tech: [Next.js 14, FastAPI, Celery, PostgreSQL 16, Redis 7, Claude API, Python 3.12, TypeScript]
local_path: /Users/ai_leed/Documents/Projects/Oliver/amazon-transcreation
deploy: docker compose up -d
url:
deploy: ./deploy.sh
url: https://optical-dev.oliver.solutions/amazon-transcreation
tags:
- project
created: 2026-04-15
port: 8040
db: PostgreSQL
db: PostgreSQL 16
---
## Overview
Amazon AI Transcreation Platform is an AI-powered web application that adapts Amazon marketing copy across 12 European locales using Claude LLM agents. It replaces a manual LibreChat workflow with a structured, one-click multi-locale processing system featuring real-time monitoring, in-app review capabilities, and proper job/file management. The platform orchestrates transcreation jobs through a Celery task queue, providing a dashboard for users to submit copy, track progress, review agent-generated translations, and manage translation memory (TM) and reference libraries.
**amazon-transcreation** is an AI-powered SaaS platform that adapts Amazon marketing copy across 12 European locales using Claude LLM agents. It replaces a manual LibreChat workflow with a structured job wizard, one-click multi-locale parallel processing, real-time monitoring, in-app review, and XLSX export. Built for Amazon via Oliver Agency, the platform runs in production on `optical-dev.oliver.solutions` and handles transcreation (cultural + linguistic adaptation) of marketing campaigns using a deterministic validation pipeline + single LLM agent call + deterministic formatting pipeline.
## Tech Stack
- **Frontend:** Next.js 14, React, TypeScript, Tailwind CSS
- **Backend:** FastAPI (Python), Pydantic, SQLAlchemy (async), Alembic migrations
- **Database:** PostgreSQL 16 (primary), Redis 7 (task queue & caching)
- **Infrastructure:** Docker Compose, Celery (4 concurrent workers)
- **AI/ML:** Anthropic Claude (default: `claude-sonnet-4-6`), LLM agent pipeline with multi-stage processing
- **Key libraries:** python-multipart, httpx, jose (JWT auth), Azure AD MSAL (SSO)
- **Frontend:** Next.js 14 (SSR/SPA), TypeScript, React 18
- **Backend:** FastAPI (uvicorn), Python 3.12, Pydantic ORM + SQLAlchemy async
- **Database:** PostgreSQL 16 (11 tables: jobs, locales, outputs, TM registry, users, etc.)
- **Infrastructure:** Docker Compose (dev & prod), Apache reverse proxy (prod), Redis 7 task broker
- **AI/ML:** Anthropic Claude API (single-agent V25 prompt, 899-line JSON system message)
- **Key libraries:** Celery (async tasks), Alembic (migrations), python-multipart (file upload), openpyxl (XLSX parsing/generation), anthropic SDK
## Architecture
The platform follows a **task-queue-driven microservices pattern**:
Three-tier monorepo: Next.js frontend (Node.js container) → FastAPI backend (Python 3.12 uvicorn) → Celery worker pool (4 concurrent tasks). PostgreSQL 16 holds all relational data (jobs, locale instances, outputs, user accounts, TM registry). Redis 7 serves dual purpose: Celery broker (task queue) and pub/sub for WebSocket progress updates. All AI processing runs asynchronously in isolated Celery tasks—each locale in a job spawns one task, up to 4 run in parallel.
**Pipeline state machine (single-agent default):**
1. **INIT** — Task starts, locale status → `processing`
2. **VALIDATE** — Deterministic: parse uploaded XLSX, load Translation Memory + reference files (glossary, blacklist, TOV guidelines), build `PipelineContext`
3. **SINGLE_AGENT** — One Claude API call with full V25 system prompt; handles TM matching, ranking, transcreation, compliance in one go (~24 min per locale)
4. **FORMAT** — Deterministic: generate output XLSX (Tab 1: transcreation output, Tab 2: linguistic summary with rationales)
5. **DONE/ERROR** — Terminal state; update `locale_instances.status`, write error logs if needed
**Legacy 6-agent pipeline** (feature-flagged via `USE_SINGLE_AGENT=false`): splits work across TM_RETRIEVE → RANK → TRANSCREATE → COMPLY agents, but single-agent is default and recommended.
**Storage:** Translation Memory (TM) and reference files live in `./storage/amazon/{tm,ref}/` (git-tracked). At runtime, uploaded source XLSX files are temporarily stored and parsed.
**Authentication:** JWT tokens (HS256, 8-hour expiry). Users authenticate via Azure AD MSAL SSO (token exchange) or local login. Roles: admin, linguist (reviewer), viewer.
```
┌─────────────────┐
│ Next.js 14 │ (http://localhost:3000)
│ (Dashboard UI) │
└────────┬────────┘
│ HTTP REST
│ (3-sec polling)
┌──────────────────────────────┐
│ FastAPI Backend │ (http://localhost:8040)
│ ┌──────────────────────────┐│
│ │ Auth / Jobs / Output API ││
│ └────────────┬─────────────┘│
└───────────────┼──────────────┘
┌───────────────┐
│ Redis Queue │
│ (Celery) │
└───────┬───────┘
┌─────────────────────┐
│ Celery Workers (×4) │
│ │
│ VALIDATE ──► │
│ SINGLE_AGENT ──► │
│ FORMAT ──► │
│ DONE │
└─────────────────────┘
┌───────────────┐
│ PostgreSQL │
│ (Job state) │
└───────────────┘
```
Browser
↓ HTTP REST / WebSocket poll
Next.js 14 (SSR, React)
↓ API calls
Apache reverse proxy (prod) / dev direct
FastAPI Backend (8000 internal, 8040 dev/127.0.0.1:8040 prod)
├─ JWT auth, job CRUD, output endpoints
├─ Celery task dispatch → Redis broker
└─ WebSocket heartbeat
Celery Worker Pool (4 concurrent)
├─ Import agent_single (main pipeline)
├─ Call Claude API (Anthropic SDK)
├─ Parse/generate XLSX (openpyxl)
└─ Write to PostgreSQL 16
PostgreSQL 16 (5432 internal, 5492 dev / 127.0.0.1:5492 prod)
└─ 11 tables: jobs, locale_instances, outputs, users, etc.
**Key Design Decisions:**
- **Single LLM Agent Pipeline:** Replaces multi-agent flow with one optimized Claude call using full V25 prompt (validation, transcreation, formatting in one go)
- **Async-first Backend:** Uses `asyncpg` for non-blocking database calls
- **Job/File Management:** Each transcreation request creates a job with associated input/output files stored on disk (`/storage`)
- **Real-time Monitoring:** Frontend polls every 3 seconds for job status updates
- **Auth:** JWT tokens with Azure AD SSO support (optional)
- **Locale Support:** 12 European locales (mapped to Amazon sales channels) + multi-channel TM routing
Redis 7 (6379 internal, 6389 dev / 127.0.0.1:6389 prod)
├─ Celery task queue
└─ Pub/sub progress updates
```
## Dev Commands
```bash
# Start all services (DB, Redis, Backend, Celery worker)
# Clone and initial setup
git clone git@bitbucket.org:zlalani/amazon-transcreation.git
cd amazon-transcreation
cp .env.example .env
# Edit .env: set ANTHROPIC_API_KEY=sk-ant-... and JWT_SECRET_KEY=random-string
# Start all services (db, redis, backend, celery_worker, frontend)
make up
# Stop all services
make down
# Rebuild containers
make build
# Run database migrations
# Run database migrations (Alembic)
make migrate
# Seed default client & test users
# Seed initial data (Amazon client, 3 test users)
make seed
# Run tests
make test
# Access dev servers
# Frontend: http://localhost:3000
# Backend API: http://localhost:8040/api/v1
# API docs: http://localhost:8040/docs
# View logs (all services)
make logs
# Access backend shell
make shell
# Direct DB access
make db-shell
# Direct Redis CLI
make redis-cli
# Restart backend & worker (after code changes)
make restart
# Other useful commands
make test # pytest tests/ -v
make shell # bash inside backend container
make logs # tail all container logs
make restart # restart backend + celery after code changes
make db-shell # psql shell
make redis-cli # Redis CLI
make build # rebuild Docker images (after requirements.txt or package.json change)
```
## Deployment
- **Server:** Local development only (docker-compose)
- **Deploy:** `docker compose up -d`
- **URL:** http://localhost:3000 (frontend), http://localhost:8040 (backend)
- **Port:** 8040 (FastAPI), 3000 (Next.js frontend)
- **Service:** Docker Compose (no systemd service configured)
- **Server:** optical-dev.oliver.solutions
- **Deploy command:** `./deploy.sh` (regular update) or `./deploy.sh --init` (first-time setup)
- **URL:** https://optical-dev.oliver.solutions/amazon-transcreation
- **Port:** 8040 (backend API, bound to 127.0.0.1 behind Apache)
- **Service:** Managed via Docker Compose, no systemd service
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/amazon-transcreation
**Deploy script:**
- `./deploy.sh --init` — creates `.env`, builds images, migrates DB, seeds data, configures Apache
- `./deploy.sh` — pulls latest code, rebuilds images (no-cache), restarts containers
- `./deploy.sh --rebuild` — full clean rebuild of all services
**Production notes:**
- All Docker ports bound to `127.0.0.1` only; Apache (host) is the public entry point
- Backend runs with `--workers 4` (vs `--reload` in dev)
- Frontend built with `NEXT_PUBLIC_BASE_PATH=/amazon-transcreation` at deploy time
- TM and reference files pulled from git; mounted as volumes
## Environment Variables
Key configuration in `.env.example`:
| Variable | Purpose | Example |
|----------|---------|---------|
| `DATABASE_URL` | PostgreSQL async connection | `postgresql+asyncpg://transcreation:transcreation@db:5432/transcreation` |
| `REDIS_URL` | Redis broker for Celery | `redis://redis:6379/0` |
| `ANTHROPIC_API_KEY` | **Required:** Anthropic Claude API key | `sk-ant-...` |
| `JWT_SECRET_KEY` | **Required:** HMAC signing key for JWT tokens | random string (e.g., `openssl rand -hex 32`) |
| `JWT_ALGORITHM` | JWT signing algorithm (default HS256) | `HS256` |
| `JWT_EXPIRY_HOURS` | Access token lifetime | `8` |
| `USE_SINGLE_AGENT` | Pipeline mode: `true` (single-agent) or `false` (legacy 6-agent) | `true` |
| `NEXT_PUBLIC_BASE_PATH` | Frontend URL prefix (prod only) | `/amazon-transcreation` |
| `NEXT_PUBLIC_API_BASE_URL` | Frontend API base URL | `/amazon-transcreation/api/v1` (dev/prod) |
- `DATABASE_URL` — PostgreSQL connection string (async driver required: `+asyncpg`)
- `REDIS_URL` — Redis connection for Celery task queue
- `ANTHROPIC_API_KEY` — Claude API key (required; format: `sk-ant-...`)
- `JWT_SECRET_KEY` — Secret for signing JWT tokens (change in production)
- `JWT_ALGORITHM` — Token algorithm (default: `HS256`)
- `JWT_EXPIRY_HOURS` — Token expiry window (default: 8 hours)
- `STORAGE_ROOT` — Path to job file storage (default: `/storage`)
- `LLM_MODEL` — Claude model version (default: `claude-sonnet-4-6`)
- `AZURE_AD_TENANT_ID` — Azure AD tenant (optional for SSO)
- `AZURE_AD_CLIENT_ID` — Azure AD client ID (optional for SSO)
- `AZURE_AD_SSO_ENABLED` — Enable/disable Azure AD authentication (default: `false`)
See `.env.example` for full list.
## API / Endpoints
## API Endpoints
Key routes (all on `http://localhost:8040`):
Key REST endpoints (all under `/api/v1`):
- `POST /api/auth/login` — User login (JWT)
- `POST /api/jobs` — Create transcreation job
- `GET /api/jobs/{job_id}` — Fetch job status & progress
- `GET /api/jobs/{job_id}/output` — Retrieve completed output file
- `POST /api/jobs/{job_id}/rerun_locale` — Re-run a failed locale
- `GET /api/tm-registry` — List translation memory entries
- `POST /api/tm-registry/upload` — Upload TM file
- `GET /api/reference-library` — List reference documents
- `POST /api/users` — Create/manage users (admin)
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/jobs` | POST | Create new transcreation job |
| `/jobs/{job_id}` | GET | Get job details + locale statuses |
| `/jobs/{job_id}/locales/{locale}/output` | GET | Download output XLSX |
| `/jobs/{job_id}/rerun-locale` | POST | Re-run a specific locale |
| `/outputs/{output_id}` | GET / PATCH | Fetch output, update review status |
| `/users/me` | GET | Authenticated user profile |
| `/users` | GET / POST | List users, create user (admin only) |
| `/tm-registry` | GET | List registered TM files |
| `/reference-library` | GET | List reference files (glossaries, blacklists, TOV) |
(Full OpenAPI docs at `GET /docs`)
WebSocket: `/ws/job/{job_id}` — real-time progress updates (progress %, status, error logs).
See `docs/project/api_spec.md` for full OpenAPI spec.
## Known Issues
- **ORM State:** Recent fix (commit 710d931) resolves 500 errors on user updates by refreshing ORM object after database flush
- **Dialog Warnings:** Fixed in latest commit; ensure frontend re-renders after async operations
- **Markdown Parser:** Fixed (commit d5fa4e4) to preserve backtranslations and rationales in TM output; verify model selection doesn't break parsing
- **TM Type Mismatch:** Fixed (commit 2c7677b) to accept both list and dict for `tm_entries_cited`
- **Azure AD SSO:** Optional feature; only enable if tenant/client IDs configured
- **Concurrency:** Limited to 4 Celery workers; scale up in `docker-compose.yml` if bottleneck detected
- None documented in source files. Latest commits (as of 2026-04-29) address:
- User management UI warnings and ORM refresh on updates
- Azure AD MSAL SSO integration
- Markdown table parser losing backtranslations (fixed in commit d5fa4e4)
- Confidence breakdown field mapping on job cards
## Git
- **Remote:** `git@bitbucket.org:zlalani/amazon-transcreation.git`
- **Branch:** (check current with `git branch`)
- **Latest commit:** `710d931` — Fix 500 on user update: refresh ORM object after flush, fix dialog warnings
- **Deployment branch:** (check with team; likely `main` or `develop`)
- **Remote:** git@bitbucket.org:zlalani/amazon-transcreation.git
- **Latest commits:** Focus on user management (admin role, viewer role), SSO (Azure AD), and pipeline refinements (single-agent feedback, TM registry w
## Sessions
### 2026-04-16 Fix 500 error when updating users

View file

@ -2,11 +2,11 @@
name: "Build-A-Squad"
client: Oliver Internal
status: active
server: optical-web-1
server: local
tech: [React 19, TypeScript, Vite, Tailwind CSS, Google Gemini, Lucide React]
local_path: /Users/ai_leed/Documents/Projects/Oliver/build-a-squad
deploy: deploy.sh script
url:
deploy: npm run build
url: http://localhost:3000
tags: [oliver, staffing, fte, calculator, gemini]
created: 2026-04-14
port: 3000
@ -14,116 +14,146 @@ port: 3000
## Overview
**Build-A-Squad** is a client-side staffing calculator for creative agencies that helps project managers instantly estimate team composition and FTE requirements. Users paste a project brief (or upload a screenshot), the app uses Google Gemini AI to match relevant creative assets from a catalog, and generates a detailed FTE breakdown by discipline and role. Built entirely in React with no backend — all calculations and AI analysis happen in the browser.
**Build-A-Squad** is a client-side React 19 + TypeScript staffing calculator designed for creative agencies. Users paste a project brief (or upload a screenshot), select deliverables from a 30+ asset catalog, and receive an instant FTE breakdown by discipline and role. The app leverages Google Gemini AI for scope analysis, image OCR, and operational auditing. With no backend required, all logic runs entirely in the browser, making it ideal for rapid prototyping and internal agency workflows.
## Tech Stack
- **Frontend:** React 19, TypeScript 5.8, Vite 6
- **Backend:** None (client-side only)
- **Database:** None (JSON data files in `data/`)
- **Infrastructure:** Vite dev server (port 3000), static bundle output to `dist/`
- **AI/ML:** Google Gemini 2.0 Flash via `@google/genai` SDK
- **Key libraries:** Lucide React (icons), Tailwind CSS (CDN-hosted styling), @azure/msal-react (SSO support)
- **Frontend:** React 19, TypeScript 5.8, Vite 6, Tailwind CSS (CDN)
- **Backend:** None (fully client-side)
- **Database:** None (all data in browser; scenarios saved to localStorage)
- **Infrastructure:** Vite dev server (port 3000)
- **AI/ML:** Google Gemini 2.0 Flash (`@google/genai`)
- **Key libraries:** Lucide React (icons), @azure/msal-react (SSO support), @azure/msal-browser
## Architecture
**Build-A-Squad** follows a monolithic React component architecture with three core pages (tabs) and persistent client-side state.
**Build-A-Squad** is a monolithic React application structured around three tabs and a data-driven catalog system:
```
┌─────────────────────────────────────────────┐
│ App.tsx (monolithic) │
├──────────┬──────────────┬──────────────────┤
│ Scoping │ Configurator │ Squad Projection │
│ Tab │ Tab │ Tab │
├──────────┴──────────────┴──────────────────┤
│ React Hooks (useState, useMemo) │
│ State: selected assets, volumes, FTE │
├──────────────────────────────────────────┤
│ Gemini AI Integration │
│ • Scope analysis (brief → asset matches) │
│ • Image OCR (screenshot → text) │
│ • Operational audit (squad → insights) │
├──────────────────────────────────────────┤
│ Data Layer (mockData.ts) │
│ ├─ ASSETS_DATA (30+ creative assets) │
│ ├─ STAFFING_ROUTES (asset → roles/hours) │
│ ├─ ROLE_DISCIPLINE_MAP (40+ roles) │
│ └─ Derived: ROLES, CATEGORIES, DISCIPLINES│
└──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ App.tsx │
│ (Monolithic component: all state + UI logic + AI calls) │
└────────────┬────────────────────────────────────────────────┘
┌────────┼────────┐
▼ ▼ ▼
Scoping Configurator Squad Projection
Tab Tab Tab
│ │ │
└────────┼───────────┘
┌─────────▼──────────────────┐
│ mockData.ts │
│ (Typed data re-exports) │
└─────────┬────────────────┬─┴────────┐
│ │ │
┌─────────▼──┐ ┌──────────▼──┐ ┌───▼──────────────┐
│ assets.json│ │staffingRoutes│ │roleDisciplineMap│
│ (30+) │ │ .json │ │ (40 roles) │
└────────────┘ └──────────────┘ └─────────────────┘
```
**Key Components & Data Flow:**
### Data Flow
1. **App.tsx** — Single monolithic component managing all UI, state, and business logic. No child components; all rendering conditional on active tab.
2. **State Management** — Pure React hooks (`useState`, `useMemo`). State includes:
- `selectedAssets[]` — array of assets with volume (quantity)
- `manualStaff[]` — user-added custom roles/FTE overrides
- `scenarios[]` — named snapshots for comparison
- `activeTab` — which tab is visible
3. **FTE Calculation Engine** — Core algorithm in App.tsx:
- Each asset maps to roles via `STAFFING_ROUTES`
- Hours = base hours × asset volume
- Total hours per role = sum of all matched assets
- FTE = total hours ÷ billable target (default 1600/year)
- Grouped by discipline (8 total) and role (40+)
4. **AI Integration** — Three Gemini API calls using structured JSON schema output:
- **Scope analysis:** Analyzes brief text, returns `{ assetIds: [...], confidence: [...] }`
- **Image OCR:** Extracts text from uploaded images
- **Operational audit:** Takes current squad, returns strategic insights
5. **Data Source**`data/*.json` files (generated by `scripts/parse-assets.ts` from `documents/`):
- `assets.json` — 30+ asset definitions (name, category, master/adapt type, Pencil Pro flag)
- `staffingRoutes.json` — Asset ID → role/hour mappings
- `roleDisciplineMap.json` — Role → discipline mapping
6. **UI Layer** — Neo-brutalist design: 4px borders, 6px offset shadows, yellow (#F5C518) focus states, Public Sans font (400/700/900).
7. **Auth** — Azure MSAL v5 integrated for SSO (MsalProvider in index.tsx).
1. **Asset Catalog** (`data/assets.json`) — 30+ creative assets with metadata (name, category, master/adapt type, complexity, Pencil Pro flag).
2. **Staffing Routes** (`data/staffingRoutes.json`) — Maps each asset to roles with base hours required (e.g., "Brand Audit" → Designer 120h, PM 40h, Strategist 80h).
3. **Role → Discipline Mapping** (`data/roleDisciplineMap.json`) — Defines 40+ roles across 8 disciplines (Design, Strategy, Project Management, etc.).
4. **FTE Calculation** — For each selected asset:
- Base hours per role (from staffing route) × asset volume (quantity selected)
- Sum hours by role, divide by billable hours target (default 1,600 h/year)
- Project FTE across disciplines
5. **AI Integration** — Three Gemini calls:
- **Scope Analysis:** Analyzes brief text → returns matching asset IDs + volumes
- **Image OCR:** Extracts text from screenshot → passes to scope analysis
- **Operational Audit:** Analyzes completed squad → returns strategic insights
### Key Components & State
- **App.tsx** — Single component managing:
- `selectedAssets`: array of assets with user-selected volumes
- `scenarios`: named snapshots of squads with timestamps
- `manualOverrides`: FTE adjustments per role
- `billableHoursTarget`: editable staffing productivity baseline
- Tab navigation and all UI rendering
- **mockData.ts** — Provides `ASSETS_DATA`, `STAFFING_ROUTES`, `ROLE_DISCIPLINE_MAP` as typed constants, plus computed arrays: `ROLES`, `CATEGORIES`, `DISCIPLINES`.
- **types.ts** — TypeScript interfaces: `Asset`, `SelectedAsset`, `RoleProjection`, `ManualStaff`, `Scenario`, `RoleHours`.
### Three-Tab Workflow
1. **Scoping Tab** — Paste a brief or upload a screenshot. Gemini analyzes and suggests matching assets. Users can bulk-add matches and set volumes.
2. **Configurator Tab** — Browse full asset catalog. Filter by category, master/adapt type, Pencil Pro flag. Manually add assets with quantity selection.
3. **Squad Projection Tab** — View FTE breakdown by discipline/role. Adjust billable hours target, override individual role FTE, add bespoke staff, save/compare scenarios, run operational audit, export CSV/PDF.
### Design System
- **Styling:** Tailwind CSS (CDN-hosted in `index.html`)
- **Aesthetic:** Neo-brutalist — 4px borders, 6px offset shadows, yellow (#F5C518) accents, Public Sans font (400/700/900)
- **Icons:** Lucide React
## Dev Commands
```bash
npm install # Install dependencies
npm run dev # Start Vite dev server (port 3000)
npm run build # Production build → dist/
npm run preview # Preview production bundle locally
npm run parse-catalog # Re-parse asset catalog: documents/ → data/*.json
# Install dependencies
npm install
# Start dev server (port 3000)
npm run dev
# Production build (outputs to dist/)
npm run build
# Preview production build locally
npm run preview
# Re-parse asset catalog from documents/ → data/*.json
npm run parse-catalog
```
## Deployment
- **Server:** (not specified — static hosting target TBD)
- **Deploy:** `./deploy.sh` script exists (exact behavior not documented in provided files)
- **URL:** (not specified)
- **Port:** 3000 (dev), static bundle for prod
- **Service:** (no systemd service configured)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/build-a-squad`
- **Server:** Local / Internal (no production server deployed)
- **Deploy:** `npm run build` → outputs to `dist/` (ready for static hosting)
- **URL:** http://localhost:3000 (dev only)
- **Port:** 3000
- **Service:** None (Vite dev server)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/build-a-squad
**Notes on deployment:**
- Bitbucket remote exists (`git@bitbucket.org:zlalani/build-a-squad.git`), but no production deploy workflow is documented.
- `deploy.sh` script exists (added in commit 89ab022) — check its contents for deployment instructions.
- Git log shows recent Azure SSO/MSAL integration (v5) and Gemini model updates.
## Environment Variables
- `GEMINI_API_KEY` — Google Gemini API key (required for scope analysis, image OCR, operational audit features). Set in `.env.local` for dev; injected by Vite at build time.
- `AZURE_REDIRECT_URI` — Azure SSO redirect URI for MSAL authentication (set in `.env.production`).
Create `.env.local` in the project root:
## Three-Tab Workflow
- **`GEMINI_API_KEY`** — Google Gemini API key (required for all AI features: scope analysis, image OCR, operational audit). Exposed to client (dev/internal use only; not suitable for production).
1. **Scoping** — Paste a project brief or upload a screenshot. Gemini analyzes content and suggests matching assets from the catalog. Users can bulk-add matched assets with configurable volume/complexity.
2. **Configurator** — Browse full asset catalog (30+ items) with filters: category, master/adapt type, Pencil Pro flag. Manually select assets and set quantity for each.
3. **Squad Projection** — View calculated FTE breakdown by discipline (8) and role (40+). Support for:
- Manual FTE overrides per role
- Bespoke staff additions (custom roles not in catalog)
- Scenario save/load with timestamps
- Scenario comparison (delta indicators)
- AI operational audit (generates strategic insights)
- CSV/PDF export
## API / Endpoints
**No backend API.** All AI interactions are client-side calls to Google Gemini via `@google/genai`:
- **Scope Analysis**`POST` to Gemini with brief text → JSON array of matched asset IDs + volumes
- **Image OCR**`POST` to Gemini with base64 image → extracted text (fed to scope analysis)
- **Operational Audit**`POST` to Gemini with squad FTE breakdown → strategic insights in JSON
All calls use JSON schema-based structured output for reliable parsing.
## Known Issues
- No test runner or linter configured.
- App is monolithic (all logic in `App.tsx` — no component decomposition).
- Asset catalog must be manually re-parsed (`npm run parse-catalog`) if source documents change.
- Deployment target/URL not documented.
- **Model ID mismatch** — README references `gemini-3.1-pro-preview`, but actual model used in `App.tsx` may differ (check source for authoritative value).
- **No test runner or linter configured** — add before production hardening.
- **API key exposed to client** — acceptable for internal/dev use only; not suitable for public deployment.
- **Tailwind CSS via CDN** — not a local PostCSS build; all styling depends on internet connectivity.
- **Single monolithic component**`App.tsx` contains all logic and UI; refactoring recommended for maintainability as features grow.
- **No error boundaries** — Gemini API failures may cause unhandled promise rejections.
- **Catalog updates require manual re-parse** — running `npm run parse-catalog` is necessary after editing source docs in `documents/`.
## Git
- **Remote:** `git@bitbucket.org:zlalani/build-a-squad.git`
- **Recent work:** SSO integration (MSAL v5), Gemini model updates, scoping expansion, role delete feature, deploy script added.
- **Recent activity:** SSO integration (MSAL), Gemini model updates, scoping expansion, role deletion feature
- **Deploy script:** `deploy.sh` added (commit 89ab022) — review for production deployment steps
## Sessions
### 2026-04-14 Project catalogued

View file

@ -2,22 +2,180 @@
name: "CC Dashboard"
client: Oliver Internal
status: active
server: optical-dev
tech: [Docker]
server: unknown
tech: [FastAPI, Python, PostgreSQL, SQLAlchemy, Pydantic, JWT, Vue.js]
local_path: /Users/ai_leed/Documents/Projects/Oliver/cc-dashboard
deploy: docker compose up --build
url:
deploy: docker compose up -d --build
url: unknown
tags: [oliver, dashboard, cc]
created: 2026-04-14
port: 8800
db: PostgreSQL 16
---
## Overview
CC Dashboard — internal reporting/analytics. Docker-based. No README — details to be filled in during working sessions.
CC Dashboard is a FastAPI + PostgreSQL personal productivity dashboard that ingests Claude Code session data via a stop hook, aggregates it into daily statistics, and displays KPIs, timeline charts, project breakdowns, tool usage, and session activity through a single-page web UI with JWT authentication. Built for Oliver (Vadym Samoilenko) to track coding sessions and project metrics in real time. The app exposes REST endpoints for session ingestion, dashboard aggregation, project management, and admin operations, all protected by JWT tokens.
## Tech Stack
- **Frontend:** Vue.js, Chart.js, HTML5/CSS3, vanilla JavaScript (SPA in `src/static/`)
- **Backend:** FastAPI 0.115, Python 3.x, uvicorn
- **Database:** PostgreSQL 16 (asyncio-enabled via sqlalchemy[asyncio] + asyncpg)
- **Infrastructure:** Docker Compose (app container + postgres:16-alpine)
- **Authentication:** JWT (python-jose + bcrypt)
- **Key libraries:** SQLAlchemy 2.0 (ORM + async), Pydantic 2.x (validation + settings), Alembic (migrations), httpx
## Architecture
**Data Flow:**
1. Claude Code sessions trigger a stop hook that POSTs to `/cc-dashboard/api/ingest/session` with raw session data
2. **Ingest router** (`src/routers/ingest.py`) validates and stores `Session` records in PostgreSQL
3. **Dashboard router** (`src/routers/dashboard.py`) aggregates sessions into `DailyStat` records per day with:
- **Overlap-union dedup:** session hours are merged via interval union to prevent double-counting parallel sessions
- Daily overhead hours tracked separately and added on top of union intervals
- KPI calculations: total hours, session count, project distribution
4. **Frontend** (Vue.js SPA) fetches aggregated data, displays charts (timeline, project breakdown, tool usage), session feeds
5. **Auth router** manages user login, JWT token refresh, 30-min access tokens + 7-day refresh tokens
6. **Project router** allows CRUD on projects with metadata (client, job#, repo URL)
7. **Admin/Keys routers** manage API keys and system configuration
**Key Design Decisions:**
- Async-first: all DB operations use asyncio-compatible SQLAlchemy + asyncpg
- Base path scoped to `/cc-dashboard` (all routes prefixed, SPA fallback handles routing)
- Static files bind-mounted in Docker for hot-reload without rebuilds
- PostgreSQL 16 specific: uses `to_char()`, `extract('dow')` for aggregations
- JWT strategy: short-lived access tokens (30 min) + longer refresh tokens (7 days)
```
┌─────────────────────────────────────┐
│ Claude Code (stop hook) │
└──────────────┬──────────────────────┘
│ POST /api/ingest/session
┌──────────────┐
│ Ingest Router│
└──────┬───────┘
│ validate & insert
┌──────────────────────┐
│ PostgreSQL 16 │
│ (Session, DailyStat) │
└──────┬───────────────┘
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Dashboard │ │ Admin/Auth │
│ Router │ │ Routers │
└─────────────┘ └──────────────┘
│ │
└──────────┬──────────┘
│ REST API
┌──────────────┐
│ Vue.js SPA │
│ (index.html) │
└──────────────┘
```
## Dev Commands
```bash
# Copy environment template and fill in DB_PASSWORD, SECRET_KEY
cp .env.example .env
# Start full stack (Docker)
docker compose up -d
# Rebuild after code changes
docker compose up -d --build
# View logs
docker compose logs -f app
# Stop stack
docker compose down
# Run migrations (Alembic)
docker compose exec app alembic upgrade head
# Run locally without Docker (requires local PostgreSQL on localhost:5432)
uvicorn src.main:app --host 0.0.0.0 --port 8800 --reload
```
## Deployment
- **Run:** `docker compose up --build`
- **Server:** unknown
- **Deploy:** `docker compose up -d --build` (after filling `.env` with production secrets)
- **URL:** unknown (base path is `/cc-dashboard`)
- **Port:** 8800
- **Service:** None (Docker Compose managed)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/cc-dashboard`
**Pre-deployment checklist:**
- Set `SECRET_KEY` to at least 32 random characters (never use placeholder)
- Change `DB_PASSWORD` from `change_me_strong_password` to a strong secret
- Set `DEBUG=false` in production
- Verify `BASE_PATH=/cc-dashboard` is correct for your deployment URL
## Environment Variables
| Variable | Description |
|----------|-------------|
| `DB_PASSWORD` | PostgreSQL password for `cc_app` user; must be changed before deployment |
| `DATABASE_URL` | Full PostgreSQL DSN; auto-constructed from `DB_PASSWORD` if not set (override for custom hosts) |
| `SECRET_KEY` | 32+ char random string for JWT signing; **never use default** |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | JWT access token TTL (default: 30) |
| `REFRESH_TOKEN_EXPIRE_DAYS` | JWT refresh token TTL (default: 7) |
| `BASE_PATH` | URL prefix for the app (default: `/cc-dashboard`) |
| `APP_TITLE` | Dashboard title in UI (default: `CC Dashboard`) |
| `DEBUG` | Enable FastAPI debug mode (default: `false`) |
## API / Endpoints
**Auth:**
- `POST /cc-dashboard/api/auth/login` — Login with credentials, returns access + refresh tokens
- `POST /cc-dashboard/api/auth/refresh` — Refresh access token
**Ingest:**
- `POST /cc-dashboard/api/ingest/session` — Claude Code stop hook endpoint; ingest session data
**Dashboard:**
- `GET /cc-dashboard/api/dashboard/stats` — Daily aggregated stats (KPIs, totals, by date range)
- `GET /cc-dashboard/api/dashboard/sessions` — Session list with filters (date, project)
- `GET /cc-dashboard/api/dashboard/timeline` — Timeline data for chart
- `GET /cc-dashboard/api/dashboard/projects` — Project breakdown and time allocation
**Projects:**
- `GET /cc-dashboard/api/projects` — List all projects
- `POST /cc-dashboard/api/projects` — Create project
- `PATCH /cc-dashboard/api/projects/{id}` — Update project metadata
**Keys:**
- `GET /cc-dashboard/api/keys` — List API keys
- `POST /cc-dashboard/api/keys` — Generate new API key
- `DELETE /cc-dashboard/api/keys/{id}` — Revoke API key
**Admin:**
- `GET /cc-dashboard/api/admin/health` — Health check
- `POST /cc-dashboard/api/admin/sync` — Force data sync/aggregation
All endpoints except `/auth/login` require valid JWT in `Authorization: Bearer {token}` header.
## Known Issues
- **Server/deployment URL unknown** — Deploy location and public URL not yet determined
- **Parallel session dedup** — Relies on interval union logic; edge cases with overlapping timezones or clock skew may exist
- **No tests documented** — No test suite referenced in project files
- **Static file hot-reload** — Only works with Docker bind mount; local dev without Docker requires manual rebuild
## Git
- **Remote:** `git@bitbucket.org:zlalani/cc-dashboard.git`
- **Recent fixes:** Accurate time tracking with interval union (no double-counting), project metadata fields, auto-detect repo URL from git remote, chart null-safety, async safety (selectinload, execute delete)
## Sessions
### 2026-04-27 Reviewed cc-dashboard logs showing incorrect calculations
**Asked:** Reviewed cc-dashboard logs showing incorrect calculations for 15.5 logged hours
@ -106,4 +264,4 @@ CC Dashboard — internal reporting/analytics. Docker-based. No README — detai
| 2026-04-14 | Initial setup | Note created | — |
## Related
- [[baic_dashboard/BAIC Dashboard]]
- [[baic_dashboard/BAIC Dashboard]]

View file

@ -1,196 +1,152 @@
---
name: "Enterprise AI Hub Nexus"
client: OLIVER Agency
client: Oliver Agency
status: production
tech: [Next.js 14, FastAPI, Python 3.11, PostgreSQL, Redis, Qdrant, Azure AD, Claude/GPT-4]
tech: [Next.js, FastAPI, PostgreSQL, Qdrant, Docker, Azure AD, OpenAI, Anthropic]
local_path: /Users/ai_leed/Documents/Projects/Oliver/enterprise-ai-hub-nexus
deploy: docker-compose up -d (backend); npm run build && npm start (frontend)
url: https://ai-sandbox.oliver.solutions
deploy: docker-compose -f docker-compose.prod.yml up -d
url: https://ai-sandbox.oliver.solutions/nexus
server: optical-web-1
tags: [oliver, ai, rag, nextjs, fastapi, azure-ad, m365, qdrant, firecrawl, production]
created: 2026-04-14
last_commit: 2026-04-01
commits: 216
port: 8000
port: 1222
db: PostgreSQL 16
---
## Overview
**Enterprise AI Hub Nexus** is a secure, multi-tenant AI platform built for OLIVER Agency that combines RAG (Retrieval-Augmented Generation) chat, Microsoft 365 productivity integration, and knowledge base management. Users ask natural language questions answered from company documents with source citations, read emails/calendars/SharePoint via Microsoft Graph, and admins manage document uploads with intelligent indexing. The platform runs production on GCE VM `optical-web-1` with Apache reverse proxy, supports multi-language queries, department/region scoping, and role-based access (super_admin, content_manager, user).
Enterprise AI Hub Nexus is an internal AI platform for Oliver Agency employees, offering three core capabilities: a RAG-powered knowledge-base chat (Oliver Process Helper), a Microsoft 365 Personal Assistant with delegated M365 access, and a configurable custom-agent builder. The system unifies Azure AD identity management, multi-model LLM orchestration (OpenAI, Anthropic, Google), vector-based document search, and sandboxed code execution in a single web application. Users authenticate via PKCE OAuth2 with Azure AD/Entra ID; feature access is governed by AD group membership. Deployed on GCE VM `optical-web-1` behind Apache reverse proxy at `https://ai-sandbox.oliver.solutions/nexus` in production.
## Tech Stack
- **Frontend:** Next.js 14, React, TypeScript, SSE streaming responses
- **Backend:** FastAPI, Python 3.11, Gunicorn + Uvicorn, Pydantic validation
- **Database:** PostgreSQL 16 (primary), Redis 7 (cache/queue), Qdrant (vector DB)
- **Infrastructure:** Docker Compose (local/staging), GCE VM + Apache (production), Google Cloud Run (async document processing)
- **AI/ML:** OpenAI (GPT-4), Anthropic (Claude Haiku for reranking), Google Gemini, LlamaParse (PDF extraction), Firecrawl (web scraping)
- **Auth:** Microsoft Entra ID (Azure AD) PKCE SPA flow, JWT HS256
- **Key libraries:** `python-jose`, `fastapi-jwt-extended`, `sqlalchemy`, `celery`, `qdrant-client`, `langchain`, `microsoft-graph-core`
- **Frontend:** Next.js 14+, TypeScript, Tailwind CSS, Zustand state management
- **Backend:** FastAPI, Pydantic, SQLAlchemy ORM, Celery (async tasks)
- **Database:** PostgreSQL 16 (metadata), Qdrant v1.12.1 (vectors), Redis 7 (broker/cache)
- **Infrastructure:** Docker Compose, Google Cloud (GCE VM + Cloud Run), Apache 2.4 reverse proxy
- **AI/ML:** OpenAI (embeddings: `text-embedding-3-large`, LLM: `gpt-5.2`), Anthropic (reranking/expansion: `claude-haiku-4-5`, assistant: `claude-sonnet-4-6`), Google Gemini API
- **Key libraries:** FastAPI-CORS, python-jose (JWT), httpx, alembic (migrations), LibreCodeInterpreter (sandboxed execution), Firecrawl (web scraping)
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Client (Next.js 14 SPA) │
│ (Azure AD PKCE login → JWT in localStorage) │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS
┌────────────────┴────────────────┐
│ Apache Reverse Proxy │
│ (optical-web-1) │
└────────────────┬────────────────┘
┌────────────────┴──────────────────────────┐
│ │
┌───────▼────────────────────┐ ┌─────────────▼───────┐
│ FastAPI Backend │ │ Cloud Run Async │
│ (Docker container) │ │ (Heavy doc proc) │
│ │ │ │
│ • /api/v1/chat (RAG) │ │ • PDF parsing │
│ • /api/v1/auth │ │ • Document chunking │
│ • /api/v1/documents │ │ • Embedding gen │
│ • /api/v1/m365 (Personal) │ │ • Vector indexing │
│ • /api/v1/admin │ │ │
│ │ └─────────────────────┘
└───────┬────────────────────┘
┌───┴──────┬──────────┬──────────┐
│ │ │ │
┌───▼──┐ ┌────▼────┐ ┌──▼───┐ ┌────▼─────┐
│ PG │ │ Redis │ │Qdrant│ │ uploads/ │
│ 16 │ │ 7 │ │ VDB │ │ files │
└──────┘ └─────────┘ └──────┘ └──────────┘
**Multi-tier SPA + FastAPI backend + satellite microservices:**
External APIs:
• Microsoft Graph (emails, calendar, SharePoint, OneDrive)
• OpenAI / Anthropic / Google
• LlamaParse / Firecrawl (web + PDF parsing)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Apache 2.4 Reverse Proxy │
│ (optical-web-1, europe-west1, n2d-standard-4 GCE VM) │
├──────────────────┬──────────────────────────────────┬───────────────────┤
│ │ │ │
│ ┌─────────────┐ │ ┌──────────────────────────────┐│ ┌───────────────┐ │
│ │ Next.js │ │ │ FastAPI + Celery ││ │ Code │ │
│ │ Static │ │ │ Docker Container ││ │ Interpreter │ │
│ │ Export │ │ │ (port 1222 → 80/443) ││ │ Container │ │
│ │ (/nexus) │ │ │ ││ │ (port 8877) │ │
│ └─────────────┘ │ │ ┌────────────────────────┐ ││ └───────────────┘ │
│ │ │ │ • Auth (Azure AD PKCE) │ ││ │
│ │ │ │ • 14 endpoint modules │ ││ │
│ │ │ │ • RAG retriever │ ││ │
│ │ │ │ • M365 Graph tools │ ││ │
│ │ │ │ • Agent builder │ ││ │
│ │ │ │ • Celery tasks (sync) │ ││ │
│ │ │ └────────────────────────┘ ││ │
│ │ └──────────────────────────────┘│ │
├──────────────────┴──────────────────────────────────┴───────────────────┤
│ Docker Bridge Network │
├──────┬──────────┬──────────┬──────────┬────────────┬──────────────────────┤
│ │ │ │ │ │ │
│ ┌────────┐ ┌─────────┐┌──────────┐┌──────────┐┌──────┐ ┌──────────────┐ │
│ │Postgres│ │ Redis ││ Qdrant ││ MinIO ││Redis ││ LibreCode │ │
│ │ :5432 │ │ :6379 ││ :6333 ││ :9000 ││:6379 ││ Interpreter │ │
│ │ │ │ ││ ││ ││ ││ :8000 │ │
│ └────────┘ └─────────┘└──────────┘└──────────┘└──────┘ └──────────────┘ │
│ (metadata) (Celery) (vectors) (files) (code) (sandboxed) │
└──────────────────────────────────────────────────────────────────────────┘
External Services:
├─ Azure AD / Entra ID (PKCE auth, user profile, AD group sync)
├─ Microsoft Graph API v1.0 (delegated M365 access: mail, calendar, files, teams, people, planner)
├─ Google Cloud Run: nexus-processor (CPU-intensive document extraction/chunking)
└─ OpenAI + Anthropic + Google APIs (LLMs, embeddings, reranking)
```
**Key Data Flow:**
1. **RAG Chat:**
- User query → multi-query expansion (3 variants: translated + UK/US English)
- Parallel Qdrant vector search (60 candidates max)
- Claude Haiku reranking (010 score) → top 5 chunks
- LLM response with SSE streaming + source citations
**Key data flows:**
2. **Document Upload:**
- File → deduplication (SHA-256) → Cloud Run async processor
- LlamaParse/Firecrawl extraction → LLM chunking + summarization
- Chunk embedding + storage in Qdrant
- Metadata (title, department, region, SharePoint link) in PostgreSQL
3. **Personal Assistant (M365):**
- User consent → delegated Microsoft Graph token
- Agentic loop (up to 5 rounds) calling tools in parallel:
- Read emails, summarize threads
- Read calendar events
- Search OneDrive / SharePoint
- Proactive token refresh + `needs_reconsent` flag if expired
1. **Auth:** Browser → PKCE login → Azure AD code → Backend JWT + MS Graph access token → AD group sync → feature flags set (`rag`, `assistant`, `admin`)
2. **Chat/RAG:** User message → Config loaded from DB → Query expansion (Haiku) → Vector search (Qdrant) → Reranking (Haiku) → LLM answer (GPT-5.2 or Claude) → SSE stream to client
3. **Document ingestion:** Upload → Firecrawl scrape/crawl → Cloud Run processor → Chunking → OpenAI embeddings → Qdrant indexing → Celery task tracks progress
4. **M365 tools:** User triggers action → Graph token refreshed (with consent check) → Delegated call to Graph API → Return result
5. **Async tasks:** Celery beat scheduler → SharePoint sync, knowledge processing, agent sync tasks → Results stored in DB/Redis
## Dev Commands
```bash
# Local development (all services)
cd /Users/ai_leed/Documents/Projects/Oliver/enterprise-ai-hub-nexus
docker-compose up -d
# Clone repo
git clone git@bitbucket.org:zlalani/enterprise-ai-hub-nexus.git
cd enterprise-ai-hub-nexus
# Backend only (if containers already running)
cd backend
python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
# Setup environment
cp backend/.env.example backend/.env
# Edit backend/.env: add OPENAI_API_KEY, ANTHROPIC_API_KEY, JWT_SECRET, ENTRA_CLIENT_ID, ENTRA_TENANT_ID
# Frontend (separate terminal)
# Start all local Docker services (Postgres, Redis, Qdrant, Code Interpreter, Backend)
docker-compose -f docker-compose.local.yml up -d
# Run database migrations
docker exec nexus-backend-local alembic upgrade head
# Start frontend dev server (separate terminal)
cd frontend
npm install
npm run dev # runs on http://localhost:3000
npm run dev
# Frontend: http://localhost:3000/nexus
# Run migrations
docker exec nexus-backend alembic upgrade head
# Verify backend health
curl http://localhost:1222/api/v1/health/live
curl http://localhost:1222/api/v1/health
# Dev login (no Azure AD needed for local dev)
curl -X POST http://localhost:1222/api/v1/auth/login/dev \
-H "Content-Type: application/json" \
-d '{"email":"dev@example.com","role":"super_admin","display_name":"Dev User"}'
# View logs
docker-compose logs -f backend
docker-compose logs -f db
docker logs nexus-backend-local -f
docker logs nexus-celery-worker -f
# Shut down
docker-compose down
# Stop all services
docker-compose -f docker-compose.local.yml down
# Production deployment (on optical-web-1)
# See "Deployment" section below
# Rebuild after code changes
docker-compose -f docker-compose.local.yml up -d --build nexus-backend-local
# Generate new migration
docker exec nexus-backend-local alembic revision --autogenerate -m "description"
# Swagger API docs
# http://localhost:1222/docs
```
## Deployment
- **Server:** `optical-web-1` (GCE VM, production)
- **Deploy:**
- Backend: `docker-compose -f docker-compose.prod.yml up -d` (or via CI/CD)
- Frontend: built as static bundle, served by Apache
- Database: PostgreSQL 16 managed on VM
- Reverse proxy: Apache 2 (SSL/TLS, CORS)
- **URL:** https://ai-sandbox.oliver.solutions
- **Port:** 8000 (backend), 443 (Apache HTTPS)
- **Service:** Docker Compose (no systemd service; managed by docker-compose)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/enterprise-ai-hub-nexus
- **Server:** `optical-web-1` (GCE VM, `europe-west1`, machine type `n2d-standard-4`: 4 vCPU, 16 GB RAM)
- **Deploy path:** `/opt/enterprise-ai-hub-nexus/`
- **Web root (frontend):** `/var/www/html/enterprise-ai-hub-nexus/`
- **Deploy command:**
```bash
cd /opt/enterprise-ai-hub-nexus
git pull origin main
./deploy.sh # builds frontend, pushes images, runs docker-compose.prod.yml
```
- **URL:** `https://ai-sandbox.oliver.solutions/nexus`
- **API base:** `https://ai-sandbox.oliver.solutions/nexus/api/v1`
- **Backend port:** `1222` (localhost bound, exposed via Apache reverse proxy)
- **Service:** Managed via Docker Compose (no systemd service; manual restart via `docker-compose -f docker-compose.prod.yml restart`)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/enterprise-ai-hub-nexus`
**Production Notes:**
- Apache acts as reverse proxy with SSL termination
- Backend runs in Docker Compose with health checks
- Qdrant, PostgreSQL, Redis persist data to volumes
- Cloud Run microservice (`nexus-doc-processor`) handles heavy async jobs
- Environment variables loaded from `.env` (not in version control)
- Gunicorn workers: 4 (2 * CPU + 1 rule)
## Environment Variables
**Core:**
- `ENVIRONMENT``production`, `staging`, or `development`
- `DEBUG``true`/`false` (development only)
- `BACKEND_BASE_URL` — public backend URL for CORS & webhooks (e.g., `https://ai-sandbox.oliver.solutions`)
**Database & Cache:**
- `DATABASE_URL` — PostgreSQL connection string (docker host is `db`)
- `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` — DB credentials
- `REDIS_URL` — Redis connection string
- `QDRANT_URL` — Qdrant vector DB HTTP endpoint
**Authentication (Azure AD):**
- `ENTRA_CLIENT_ID` — OAuth2 app client ID
- `ENTRA_CLIENT_SECRET` — OAuth2 app secret
- `ENTRA_TENANT_ID` — Azure tenant ID
- `ENTRA_REDIRECT_URI` — OAuth2 callback (e.g., `https://ai-sandbox.oliver.solutions/nexus/api/v1/auth/callback`)
**JWT:**
- `JWT_SECRET` — 32+ character secret (generate: `openssl rand -hex 32`)
- `JWT_ALGORITHM``HS256`
- `JWT_EXPIRATION_MINUTES` — token lifetime in minutes (default: 15)
- `REFRESH_TOKEN_EXPIRATION_DAYS` — refresh token lifetime (default: 7)
**LLM APIs:**
- `OPENAI_API_KEY` — GPT-4 queries
- `ANTHROPIC_API_KEY` — Claude Haiku reranking
- `GOOGLE_API_KEY` — Gemini fallback
- `LLAMAPARSE_API_KEY` — cloud PDF parsing (optional)
- `FIRECRAWL_API_KEY` — web scraping
**Logging & Rate Limiting:**
- `LOG_LEVEL``DEBUG`, `INFO`, `WARNING`, `ERROR`
- `LOG_FORMAT``json` (production) or `text` (dev)
- `RATE_LIMIT_PER_MINUTE` — requests per minute per user (default: 60)
**Upload:**
- `MAX_UPLOAD_SIZE_MB` — max file size (default: 100 MB)
**Networking:**
- `CORS_ORIGINS` — comma-separated allowed origins
- `TRUSTED_HOSTS` — comma-separated trusted hosts (empty = allow all)
- `WORKERS_COUNT` — Gunicorn worker count (default: 4)
## API / Endpoints
**Authentication:**
- `POST /api/v1/auth/login` — initiate Azure AD PKCE login
- `GET /api/v1/auth/callback` — OAuth2 callback (redirected from Azure AD)
- `POST /api/
**Production Docker services:**
- `nexus-postgres` (5 GB data), `nexus-redis`, `nexus-qdrant` (vector DB), `nexus
## Timeline / Git History
| Date | Change |

View file

@ -2,11 +2,11 @@
name: "Ford QC System"
client: Ford
status: active
tech: [Python, Pillow, Box SDK, JSON, HTML/Bootstrap, systemd]
tech: [Python, Box SDK, HTML/Bootstrap, JSON, ZIP/Pillow, systemd]
local_path: /Users/ai_leed/Documents/Projects/Oliver/ford_qc
deploy: sudo systemctl start ford-qc-hotfolder.service
url:
server: box-cli-01
url: Box folder 332861865120
server: box-cli
tags: [ford, qc, quality-control, box, bnp, systemd, hotfolder]
created: 2026-04-14
last_commit: 2026-04-29
@ -15,145 +15,138 @@ service: ford-qc-hotfolder.service
---
## Overview
Ford BnP QC is a comprehensive quality control system for Ford Build and Price (BnP) asset packs containing automotive imagery, metadata, and configuration files. It validates that asset packs meet Ford's quality standards before deployment to customer-facing applications. The system features 13 specialized validation checks, automated Box cloud folder integration for production processing, and rich Bootstrap-styled HTML reporting with detailed error guidance.
Ford QC is an automated quality control system that validates Ford Build & Price (BnP) asset packs—ZIP files containing automotive images, metadata, and configuration—against 20 specialized validation checks. It monitors a Box cloud folder for incoming asset packs, processes them through the QC engine, generates detailed HTML reports, and stores results back to Box. The system is production-deployed as a systemd daemon on the `box-cli` host and handles both MEC (Market Enabled Configuration) and BAU (Business as Usual) asset types.
## Tech Stack
- **Frontend:** HTML/Bootstrap (static HTML reports)
- **Backend:** Python 3
- **Database:** N/A (file-based, JSON results)
- **Infrastructure:** systemd service daemon
- **Frontend:** HTML/Bootstrap (static report generation)
- **Backend:** Python 3, Box SDK, Flask-adjacent (CLI + daemon pattern)
- **Database:** N/A (stateless processing; Box as storage)
- **Infrastructure:** systemd service, Box cloud integration, Python venv
- **AI/ML:** N/A
- **Key libraries:** Box SDK 3.13.0, Pillow 11.1.0, python-dotenv, systemd-python
- **Key libraries:** `boxsdk` (3.13.0), `pillow` (11.1.0), `python-dotenv` (1.0.1), `requests` (2.32.3)
## Architecture
The system comprises four main layers:
**1. QC Engine (`qc_engine.py`)**
- CLI script accepting JSON profile configurations and input zip files
- Loads and executes defined checks in sequence with comprehensive error handling
- Outputs JSON results that feed into reporting
The system comprises three primary execution paths:
**2. QC Module (`qc_module.py`)**
- Reusable Python module exposing `run_qc_checks()` and `run_qc_profile()` functions
- Enhanced module import and check execution error handling
- Used by both CLI and Box hotfolder daemon
1. **CLI Entry Point (`qc_engine.py`)**: Loads a JSON profile, executes checks in sequence, generates JSON results, and optionally produces HTML reports. Used for manual/ad-hoc testing.
**3. Box Hotfolder Daemon (`ford_qc_box_hotfolder_process.py`)**
- Production service monitoring Box folder 332861865120 for new asset zip files
- Downloads files, processes through QC system, uploads JSON+HTML reports to folder 332864939558
- Moves successfully processed files to folder 332861653811 with MD5 stamps
- Implements file locking, graceful shutdown, and comprehensive logging
- Runs as systemd service `ford-qc-hotfolder.service`
2. **QC Module (`qc_module.py`)**: Reusable Python module exposing `run_qc_checks()` and `run_qc_profile()` functions for programmatic check execution with enhanced error handling.
**4. Check Modules (13 total in `checks/` directory)**
- `zip_filename_check`: Validates filename ends with `_GPAS.zip`
- `unzip_and_verify_check`: Unzips packs, verifies linkingrecord.json exists
- `colour_existence_check`: Validates color chip files present and accessible
- `missing_images_check`: Cross-references linkingrecord.json with actual files
- `special_requirements_mec_bau`: Validates MEC vs BAU requirement compliance
- `mec_powertrain_validation`: Validates powertrain configs for MEC packs
- `lifestyle_inventory_validation`: Validates lifestyle inventory consistency
- `image_resolution_check`: Validates image dimensions per Ford specs
- `image_format_check`: Validates PNG/JPG/AVIF formats by viewtype
- `exterior_interior_pairing_check`: Validates angle/variant pairings
- `linkingrecord_header_validation`: Validates linkingrecord.json headers
- `carousel_image_check`: Validates carousel image requirements
- `base_assets_file_type_check`: Validates base asset file types
3. **Box Hotfolder Daemon (`ford_qc_box_hotfolder_process.py`)**: Production service monitoring Box folder `332861865120` for incoming `*_GPAS.zip` files. Downloads, processes through QC, uploads JSON/HTML reports to folder `332864939558`, and moves processed files to folder `332861653811` with MD5 stamps. Implements file locking, graceful shutdown, and systemd watchdog integration.
**Data Flow:**
**Check Modules** (13 in `checks/` directory):
- `zip_filename_check`: Enforces `_GPAS.zip` suffix (Ford 3rd-party requirement)
- `unzip_and_verify_check`: Extracts ZIP and validates `linkingrecord.json` presence
- `colour_existence_check`: Verifies color chip files exist and are accessible
- `missing_images_check`: Cross-references metadata vs. actual files
- `special_requirements_mec_bau`: Auto-determines asset type (MEC/BAU) and validates type-specific rules
- `mec_powertrain_validation`, `lifestyle_inventory_validation`, `image_resolution_check`, `image_format_check`, `exterior_interior_pairing_check`, `base_assets_check`, `carousel_images_check`, `linkingrecord_header_validation`: Specialized validations per asset type
- `html_reporter.py`: Converts JSON results to Bootstrap-styled HTML success reports
- `html_error_reporter.py`: Converts error data to HTML error reports
**Data Flow**:
```
Box Cloud Source ──[monitor]──> Hotfolder Daemon ──[zip]──> QC Engine
├─> Execute 13 Checks
└─> JSON Results ──> HTML Reporter
Upload to Box Reports Folder
Move to Box Processed Folder
Box Input Folder (332861865120)
[Hotfolder daemon polls every 60s]
Download ZIP to /working_dir
Extract + Run 13 QC checks → JSON results
Generate HTML reports (success & error)
Upload JSON/HTML to Box Report Folder (332864939558)
Move processed ZIP to Box Processed Folder (332861653811) + MD5 stamp
```
## Dev Commands
```bash
# Run QC checks on local asset pack (primary CLI usage)
# Install dependencies
pip install -r requirements.txt
# Run QC on a local ZIP file
python qc_engine.py profiles/ford_bnp.json --input_file path/to/asset_pack.zip --reports_dir reports
# Generate HTML report from JSON results
# Generate HTML report from existing JSON results
python -m checks.html_reporter results.json output_directory/
# Generate HTML error report
python -m checks.html_error_reporter <error_type> <filename> output_directory/
# Test individual check module
# Test a single check module
python -c "from checks.image_format_check import run_check; print(run_check({'working_dir': 'working'}))"
# Test Box hotfolder process (manual execution)
# Run all tests
python -m pytest tests/
# Start the Box hotfolder daemon (locally)
python ford_qc_box_hotfolder_process.py
# Start production service
sudo systemctl start ford-qc-hotfolder.service
# Check service status
# Verify systemd service status (production)
sudo systemctl status ford-qc-hotfolder.service
# View live service logs
sudo journalctl -u ford-qc-hotfolder.service -f
# View live production logs
journalctl -u ford-qc-hotfolder.service -f
# View logs from past hour
sudo journalctl -u ford-qc-hotfolder.service --since "1 hour ago"
# Restart production service
sudo systemctl restart ford-qc-hotfolder.service
```
## Deployment
- **Server:** box-cli-01
- **Deploy:** `sudo systemctl start ford-qc-hotfolder.service` (or `sudo systemctl restart` to reload)
- **URL:** N/A (backend daemon, no web interface)
- **Port:** N/A
- **Service:** `ford-qc-hotfolder.service` (systemd unit)
- **Server:** box-cli
- **Deploy:** `sudo systemctl start ford-qc-hotfolder.service` (or `restart`)
- **URL:** Box folder ID `332861865120` (input), `332864939558` (reports output)
- **Port:** N/A (daemon; no HTTP port)
- **Service:** `ford-qc-hotfolder.service` (systemd)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/ford_qc
- **Production path:** /home/box-cli/FORD_SCRIPTS/ford_qc_git_dev/ford_qc
- **Production path:** `/home/box-cli/FORD_SCRIPTS/ford_qc_git_prod/ford_qc`
**Service Setup:**
**Setup**:
1. Copy `ford-qc-hotfolder.service` to `/etc/systemd/system/`
2. Run `sudo systemctl daemon-reload`
3. Enable with `sudo systemctl enable ford-qc-hotfolder.service`
4. Start with `sudo systemctl start ford-qc-hotfolder.service`
2. Run `sudo systemctl daemon-reload && sudo systemctl enable ford-qc-hotfolder.service`
3. Configure `.env.prod` with Box credentials and folder IDs in the script directory
4. Start: `sudo systemctl start ford-qc-hotfolder.service`
## Environment Variables
Configuration via `.env.prod` or `.env.dev` (copy from `.env.example`):
**Environment:**
- `FORD_QC_ENV` — "production" or "development"
- `LOG_LEVEL` — Logging verbosity (DEBUG, INFO, WARNING, ERROR, CRITICAL)
All env vars are loaded from `.env.prod` (production) or `.env.dev` (dev). Key variables:
**Box Cloud Integration:**
- `BOX_SOURCE_FOLDER_ID` — Folder monitored for incoming asset zips (332861865120)
- `BOX_REPORT_FOLDER_ID` — Where QC reports uploaded (332864939558)
- `BOX_PROCESSED_FOLDER_ID` — Where successfully processed files moved (332861653811)
- `BOX_CONFIG_FILE` — Path to Box JWT config JSON
- `BOX_CONNECTION_TIMEOUT` — Seconds to wait for Box connection (default: 30)
- `BOX_READ_TIMEOUT` — Seconds to wait for Box API response (default: 90)
**File Paths:**
- `FORD_QC_SCRIPT_DIR` — Base QC system directory (auto-detected if unset)
- `DOWNLOAD_PATH` — Temp directory for Box downloads (relative or absolute)
- `WORKING_DIR` — Directory for file extraction (relative or absolute)
- `QC_PROFILE_PATH` — Path to QC profile JSON (relative or absolute)
- `FALLBACK_WORKING_DIR` — Fallback working directory if primary unavailable
**Resilience:**
- `MAX_RETRIES` — Max retry attempts for failed Box operations (default: 3)
- `RETRY_BACKOFF_BASE` — Exponential backoff base in seconds (default: 2 → 2s, 4s, 8s...)
- `WATCHDOG_INTERVAL` — Seconds between systemd watchdog notifications (default: 30)
- `FORD_QC_ENV` — Environment name (`production` or `development`)
- `BOX_SOURCE_FOLDER_ID` — Folder monitored for incoming ZIPs (e.g., `332861865120`)
- `BOX_REPORT_FOLDER_ID` — Folder where JSON/HTML reports are uploaded (e.g., `332864939558`)
- `BOX_PROCESSED_FOLDER_ID` — Folder where successfully processed files are moved (e.g., `332861653811`)
- `BOX_CONFIG_FILE` — Path to Box JWT JSON credentials file (default: `ford_box_config.json`)
- `FORD_QC_SCRIPT_DIR` — Base directory for QC system (auto-detected if not set)
- `DOWNLOAD_PATH` — Temp directory for Box downloads (default: `download_tmp`)
- `WORKING_DIR` — Directory for ZIP extraction and processing (default: `working`)
- `QC_PROFILE_PATH` — QC profile file (default: `profiles/ford_bnp.json`)
- `BOX_CONNECTION_TIMEOUT` — Timeout for Box API connection (seconds; default: 30)
- `BOX_READ_TIMEOUT` — Timeout for Box API read (seconds; default: 90)
- `MAX_RETRIES` — Max retry attempts for failed operations (default: 3)
- `RETRY_BACKOFF_BASE` — Base for exponential backoff (seconds; default: 2)
- `WATCHDOG_INTERVAL` — Systemd watchdog notification interval (seconds; default: 30)
- `LOG_LEVEL` — Log verbosity (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`; default: `INFO`)
## Known Issues
- Recent commit (eb3da2c) addresses exterior/interior pairing check with variant-signature approach for angle 21
- Previous issues resolved: authentication/timeout fixes (commits 77cb669, 6209d28), Box SDK version upgrade (a095419), path relocation (be9700f)
- Service requires user `box-cli` with proper directory permissions; use `sudo chown -R box-cli:box-cli /path/to/directory` if permission issues occur
- Fallback working directory required in case primary cannot be created
- **No SSH to production without explicit instruction** — security constraint
- **File locking enforced** — do not run multiple hotfolder instances simultaneously
- **ZIP filename enforcement** — files must end with `_GPAS.zip`; enforced by `zip_filename_check.py`
- **Box folder IDs hardcoded** — in `ford_qc_box_hotfolder_process.py`; changes require careful review
- **Python venv required** — production runs from `venv/bin/python`; system Python not supported
- **MEC vs BAU auto-detection** — logic in `special_requirements_mec_bau.py` determines asset type; ensure linkingrecord.json structure is valid
- **Ranger ptvl pattern configurable** — via profile; see recent git commits for changes
- **Exterior/interior pairing validation** — uses variant-signature approach; angle 21 support added recently
## Git
- **Remote:** git@bitbucket.org:zlalani/ford_qc.git
- **Last commit:** eb3da2c — Fix exterior/interior pairing check: variant-signature approach, angle 21
- **Key recent improvements:** Ranger/CV series permutation support, ABM-only validation, linkingrecord header validation, asset count summaries in reports, GPAS naming convention enforcement
- **Last 20 commits include**: exterior/interior pairing, CV ABM support, linkingrecord header validation, asset count summary, GPAS naming enforcement, carousel image checks, Box config debugging, environment configuration, systemd watchdog improvements, and boxsdk upgrade to 3.13.0
## Timeline / Git History
| Date | Change |

View file

@ -1,173 +1,172 @@
---
name: "GMAL Scope Builder"
client: Oliver Internal
client: Oliver Agency
status: active
tech: [React, TypeScript, FastAPI, PostgreSQL, Claude API, Docker, Vite]
tech: [React, TypeScript, FastAPI, PostgreSQL, Claude Opus, Docker, Apache]
local_path: /Users/ai_leed/Documents/Projects/Oliver/gmal-scope-builder
deploy: docker compose up -d
url: http://localhost:3010
server: local
deploy: sudo ./deploy.sh
url: https://optical-dev.oliver.solutions/gsb/
server: optical-dev
tags: [oliver, gmal, ratecard, fte, ai, claude, postgres, azure-sso]
created: 2026-04-14
last_commit: 2026-03-31
commits: 23
port: 3010
db: PostgreSQL
port: 8002
db: PostgreSQL 16
---
## Overview
**GMAL Scope Builder** is a Dockerized AI-powered web application that helps teams scope new client projects by matching client deliverables (uploaded Word/Excel documents) against a standardized GMAL (Global Master Asset List) database. It uses Claude Opus 4.6 to parse documents, match assets intelligently, generate ratecards with hourly rates per role, and calculate team FTE headcount models. The tool combines human expertise with AI to accelerate scoping workflows and ensure consistency across projects.
GMAL Scope Builder is an internal Oliver Agency tool for scoping new client ratecards against the Global Master Asset Library (GMAL) database. It enables teams to parse client briefs, match deliverables to standardised GMAL assets using Claude Opus 4.6, compute hours per role, and generate Excel/PDF exports for proposal creation. The system combines AI-powered matching with manual refinement, efficiency profiling, and team shape calculation to produce accurate scope documents.
## Tech Stack
- **Frontend:** React 18 + TypeScript + Vite + React Router + Axios
- **Backend:** FastAPI + SQLAlchemy (async) + asyncpg + Uvicorn
- **Database:** PostgreSQL 16 (async queries with asyncpg)
- **Infrastructure:** Docker + docker-compose (3 services: db, backend, frontend)
- **AI/ML:** Claude Opus 4.6 via Anthropic SDK (tool_use for structured output, per-project token tracking)
- **Document Parsing:** openpyxl (Excel), python-docx (Word)
- **Authentication:** Azure SSO (MSAL v5) with dev bypass option
- **Frontend:** React 18, TypeScript, Vite, Axios, MSAL (Azure AD auth)
- **Backend:** FastAPI, Python 3.12, SQLAlchemy async (asyncpg), Pydantic
- **Database:** PostgreSQL 16 (Alpine)
- **Infrastructure:** Docker Compose (3 containers: db, backend, frontend as static), Apache 2 reverse proxy
- **AI/ML:** Anthropic Claude Opus 4.6 (matching, descriptions, refinement)
- **Key libraries:** openpyxl (Excel parsing), python-pptx (doc extraction), Playwright (e2e tests)
## Architecture
GMAL Scope Builder follows a classic three-tier architecture with AI as a supplementary service tier.
```
┌─────────────────────────────────────────────────────────┐
│ React Frontend (port 3010) │
│ ┌─ Dashboard ─ NewProject ─ ProjectView ─ GmalBrowser ─┐
│ └────────────── Debug Panel + AI Cost Tracker ──────────┘
└────────────────────────┬────────────────────────────────┘
│ Axios + API Client
┌─────────────────────────────────────────────────────────┐
│ FastAPI Backend (port 8001/8002) │
│ ┌─ Routes ───────────────────────────────────────────┐ │
│ │ /api/gmal/* ─ GMAL catalog endpoints │ │
│ │ /api/ingest ─ Parse & load Excel file │ │
│ │ /api/projects/* ─ Project CRUD + workflow │ │
│ │ /api/matching/* ─ AI document matching │ │
│ │ /api/ratecard/* ─ Build ratecards & exports │ │
│ │ /api/health ─ Healthcheck │ │
│ └─ Services ────────────────────────────────────────┘ │
│ │ claude_client.py ─ AI calls + token tracking │ │
│ │ gmal_service.py ─ GMAL queries & caching │ │
│ │ project_service.py ─ Project state management │ │
│ │ matching_service.py ─ Asset matching logic │ │
│ │ ratecard_service.py ─ Ratecard building & export │ │
│ └─ Models (SQLAlchemy ORM) ────────────────────────┘ │
│ │ gmal.py ─ GmalAsset, Role, ServiceLine │ │
│ │ project.py ─ Project, ClientAsset, Match │ │
│ └────────────────────────────────────────────────────┘
└────────────────────────┬────────────────────────────────┘
│ asyncpg
┌─────────────────────────────────────────────────────────┐
│ PostgreSQL 16 (port 5433) │
│ ┌─ gmal_* tables (390 assets, categories) │
│ ├─ projects (scope definitions) │
│ ├─ client_assets (extracted deliverables) │
│ ├─ matches (AI-matched assets + confidence) │
│ └─ ratecard_lines (computed hours/rates) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Browser (React SPA) │
│ - Auth: Azure AD MSAL token │
│ - Routes: Dashboard, ProjectView, GmalBrowser, Ratecard │
└──────────────────────┬──────────────────────────────────────┘
│ Axios → /gsb/api/* with Bearer token
┌─────────────────────────────────────────────────────────────┐
│ Apache 2 (optical-dev.oliver.solutions) │
│ - ProxyPass /gsb/api/ → 127.0.0.1:8002 │
│ - Alias /gsb → /var/www/html/gmal-scope-builder (static) │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FastAPI (Uvicorn, port 8002 / Docker 8000) │
│ - JWT validation (Azure AD JWKS cache) │
│ - Role-based access control (Viewer/Editor/Admin) │
│ - Endpoints: /api/gmal/*, /api/projects/*, /api/matching/ │
│ /api/ratecard/*, /api/efficiency/*, /api/users/│
└──────────────────────┬──────────────────────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
PostgreSQL Claude Opus openpyxl/
(port 5433) (HTTPS API) file parsing
```
**Data Flow:**
1. User uploads client brief (Word/Excel) → Backend extracts deliverables via Claude
2. Extracted items matched against GMAL catalog → Claude ranks by confidence + rationale
3. Matched assets → Ratecard builder computes hours/role based on GMAL templates
4. Ratecard + FTE model exported as Excel with caveats
5. All AI calls tracked: per-project tokens, global cost, 50-call debug log
**Key Components:**
- **GMAL Ingestion:** `excel_parser.py` reads standardised GMAL workbook, populates `GmalAsset`, `Role`, `GmalHours`, `ServiceLine` tables.
- **Brief Analysis:** `doc_parser.py` extracts text from PDF/PPTX, Claude extracts structured asset list via tool call.
- **AI Matching:** `ai_matching.py` sends client assets + GMAL catalog to Claude, receives match suggestions (batched, cancellable).
- **Ratecard Build:** `ratecard_builder.py` looks up `GmalHours` for each matched asset per role, calculates delivery hours, applies efficiency curves, derives team FTE.
- **Match Refinement:** `match_refiner.py` allows users to refine matches via natural-language chat (e.g., "use Senior Producer instead of Producer").
- **Role-Based Access:** Middleware validates Azure AD token, middleware/auth.py checks RBAC on sensitive endpoints.
**Critical Invariant:** `model_type` (scope complexity tier: Small/Medium/Large) is immutable after Project creation—all GmalHours lookups depend on it.
## Dev Commands
```bash
# === Docker (primary workflow) ===
docker compose build # Build all images
docker compose up -d # Start services in background
docker compose logs backend --tail 50 # View backend logs
docker compose logs frontend --tail 20 # View frontend logs
docker compose down # Stop all services
# Clone
git clone git@bitbucket.org:zlalani/gmal-scope-builder.git
cd gmal-scope-builder
# === One-time setup (local dev) ===
cp .env.example .env # Create env file
# Edit .env and add ANTHROPIC_API_KEY
# Place "U-Studio GMAL Asset Job Routes Apr25 ForFranky.xlsx" in data/
# Local dev (requires .env with ANTHROPIC_API_KEY, AZURE_TENANT_ID, AZURE_CLIENT_ID)
cp .env.example .env
# Edit .env: fill secrets, set DEV_AUTH_BYPASS=1 to skip JWT locally
# Start all services
docker compose up -d
curl -X POST http://localhost:8001/api/gmal/ingest # Populate GMAL from Excel
# === Frontend only (without Docker) ===
# Backend logs
docker compose logs -f backend
# Frontend dev (separate terminal)
cd frontend
npm install
npm run dev # Vite dev server with HMR (port 5173)
npm run build # TypeScript compile + Vite bundle
npm run dev # Vite dev server (http://localhost:5173)
# === Backend only (without Docker) ===
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# Run e2e tests
npm install -g @playwright/test
cd e2e
npx playwright test
# === Database operations ===
# Backup
docker compose exec db pg_dump -U scope_user -d scope_builder > backups/scope_builder_$(date +%Y%m%d).sql
# Trigger GMAL ingest (if JWT validation enabled)
curl -X POST -H "Authorization: Bearer <token>" \
http://localhost:8002/api/gmal/ingest
# Restore
docker compose exec -T db psql -U scope_user -d scope_builder < backups/scope_builder_deploy.sql
# DB access
docker compose exec db psql -U scope_user -d scope_builder
# Check GMAL data loaded
docker compose exec db psql -U scope_user -d scope_builder -c "SELECT COUNT(*) FROM gmal_assets;"
# Stop
docker compose down
```
## Deployment
- **Server:** local (development) or custom server for production
- **Deploy:** `docker compose build && docker compose up -d` (with .env configured)
- **URL (dev):** http://localhost:3010
- **URL (prod):** Configure in `docker-compose.yml` and Azure SSO redirect URIs
- **Port (frontend):** 3010 (mapped from container port 3000)
- **Port (backend):** 8001 or 8002 (mapped from container port 8000)
- **Port (database):** 5433 (mapped from container port 5432)
- **Service:** None (Docker-managed; no systemd)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/gmal-scope-builder`
- **Server:** optical-dev (optical-dev.oliver.solutions)
- **Deploy Command:** `sudo ./deploy.sh` (idempotent, pulls from main, rebuilds Docker, builds frontend, reloads Apache)
- **URL:** https://optical-dev.oliver.solutions/gsb/
- **Port:** 8002 (backend, bound to 127.0.0.1 only; frontend served as static by Apache)
- **Service:** Not a systemd service; managed by Docker Compose + Apache
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/gmal-scope-builder
- **Server path:** /opt/gmal-scope-builder (or clone destination)
- **Static files:** /var/www/html/gmal-scope-builder/
- **Data dir:** /var/www/html/gmal-scope-builder/data/ (mounted into db/backend)
**Deployment Steps (to a server):**
1. Clone repo: `git clone git@bitbucket.org:zlalani/gmal-scope-builder.git`
2. Copy GMAL Excel and DB backup: `scp backups/scope_builder_deploy.sql user@server:/path/to/data/` and `scp "data/U-Studio GMAL...xlsx" user@server:/path/to/data/`
3. Create `.env` with `POSTGRES_PASSWORD`, `ANTHROPIC_API_KEY`, `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`
4. Optionally edit `docker-compose.yml` to change exposed ports
5. Build & start: `docker compose build && docker compose up -d`
6. Import DB: `docker compose exec -T db psql -U scope_user -d scope_builder < backups/scope_builder_deploy.sql`
7. Verify: `curl http://localhost:8001/api/health` and open frontend in browser
**Deployment Process:**
1. Pre-flight: check .env, Docker, required secrets
2. `git pull origin main`
3. `docker compose up -d --build --remove-orphans`
4. Wait for `/api/health` (90s timeout)
5. Build frontend: `docker run node:20-alpine npm ci && npm run build``/var/www/html/gmal-scope-builder/`
6. Add Apache ProxyPass/Alias blocks (idempotent check for marker)
7. `apache2ctl configtest && systemctl reload apache2`
Typical deploy time: 24 minutes.
## Environment Variables
- `POSTGRES_PASSWORD` — Database password (default: `scope_pass_2024`)
- `ANTHROPIC_API_KEY` — Anthropic SDK key for Claude Opus 4.6 calls (required for AI features)
- `AZURE_TENANT_ID` — Azure AD tenant ID for SSO (required in production)
- `AZURE_CLIENT_ID` — Azure AD application client ID for MSAL (required in production)
- `DEV_AUTH_BYPASS` — Set to any value to skip Azure SSO in local dev (NEVER use in production)
- `VITE_DEV_AUTH_BYPASS` — Frontend flag to match backend auth bypass
- `DATA_DIR` — Absolute path to directory containing GMAL Excel file (defaults to `./data` if not set)
- `DATABASE_URL` — Auto-set by backend container; `postgresql+asyncpg://scope_user:PASSWORD@db:5432/scope_builder`
- `DATABASE_URL_SYNC` — Sync variant for alembic migrations and initial setup
- `POSTGRES_PASSWORD` — PostgreSQL superuser password; used in `docker-compose.yml` to set `DATABASE_URL`
- `ANTHROPIC_API_KEY` — Anthropic API key (sk-ant-api03-…); required for Claude Opus calls
- `AZURE_TENANT_ID` — Azure AD tenant ID; for MSAL (frontend) and JWKS (backend)
- `AZURE_CLIENT_ID` — Azure AD app registration ID; for MSAL and JWKS
- `DEV_AUTH_BYPASS` — Set to `1` to skip JWT validation (local dev only); unset in production
- `DATA_DIR` — Override GMAL data directory (default: `./data`); Docker-mounted to both backend and frontend
- `DATABASE_URL` — Auto-generated from POSTGRES_PASSWORD; async psycopg3 connection string
- `DATABASE_URL_SYNC` — Auto-generated; sync version for openpyxl ingest (blocking)
## API Endpoints
**GMAL Catalog:**
- `GET /api/gmal/stats` — Asset counts by category
- `GET /api/gmal/assets` — List all GMAL assets (paginated)
- `GET /api/gmal/assets/{asset_id}` — Get single asset
- `GET /api/gmal/roles` — List all roles
- `POST /api/gmal/ingest` — Parse Excel file and populate catalog
**Projects:**
- `POST /api/projects` — Create new project
- `GET /api/projects` — List projects
- `GET /api/projects/{project_id}` — Get project details
- `PUT /api/projects/{project_id}` — Update project
- `DELETE /api/projects/{project_id}` — Delete project
**Matching & Extraction:**
- `POST /api/projects
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/health` | GET | Health check (no auth) |
| `/api/gmal/stats` | GET | GMAL asset counts |
| `/api/gmal/ingest` | POST | Trigger GMAL Excel ingest (admin only) |
| `/api/gmal/browse` | GET | List/search GMAL assets (query params: `q`, `service_line`, `role`) |
| `/api/gmal/describe/{asset_id}` | GET | Get asset details + AI description |
| `/api/projects` | GET/POST | List / create project |
| `/api/projects/{id}` | GET/PATCH/DELETE | Project CRUD |
| `/api/projects/{id}/upload` | POST | Upload client brief (PDF/PPTX); multipart form |
| `/api/projects/{id}/analyze` | GET | Extract brief structure (scope, risks, assumptions) |
| `/api/projects/{id}/matching/extract` | POST | Extract client assets from brief + run matching (normal/deep modes) |
| `/api/projects/{id}/matching/select` | POST | Lock user's match selections; send to ratecard build |
| `/api/projects/{id}/matching/refine` | POST | Refine matches via chat (e.g., "use Senior instead") |
| `/api/projects/{id}/ratecard/build` | GET | Build ratecard from selected matches + efficiency |
| `/api/projects/{id}/ratecard/export` | POST | Export ratecard as Excel or PDF |
| `/api/projects/{id}/ratecard/team-shape` | GET | Calculate FTE per role (hours / 1800) |
| `/api/efficiency/*` | GET/POST/PATCH | Efficiency profiles, tool efficiencies |
| `/api/users/me` | GET | Current user info |
| `/api/users` | GET | List users (admin only) |
| `/api/users/{id}/role` | PATCH | Update
## Timeline / Git History
| Date | Change |

View file

@ -1,27 +1,185 @@
---
name: "Hp Prod Tracker"
client: "TBD"
client: HP / Oliver Agency
status: active
server: optical-dev
tech: []
tech: [Next.js 16, React 19, TypeScript, PostgreSQL 17, Prisma 7, TanStack Query, Ollama, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/hp-prod-tracker
deploy:
url:
deploy: bash deploy.sh
url: https://optical-dev.oliver.solutions/hp-prod-tracker
tags:
- project
created: 2026-04-15
port: 3001
db: PostgreSQL 17
---
## Overview
> New project — fill in during first session.
HP CG Production Tracker is a unified web application that replaces fragmented workflows (Workfront, Excel, OMG platform) for the HP CG department at Oliver Agency. Production managers track a 10-stage CG render pipeline with dependency gates and deliverable deadlines; CG artists view assigned tasks, update statuses, and submit revisions. The system provides multiple project views (table, Kanban board, Gantt timeline, calendar), enforces stage approval gates, integrates an AI chat assistant for natural-language queries, and generates weekly executive reports.
## Tech Stack
- **Frontend:**
- **Backend:**
- **Infrastructure:**
- **Frontend:** Next.js 16 (App Router), React 19, TypeScript, TanStack Query v5, Zustand (UI state)
- **Backend:** Next.js Route Handlers (`/app/api/`), Node.js 22
- **Database:** PostgreSQL 17 + Prisma 7 ORM + pgvector (embeddings)
- **Infrastructure:** Docker + Docker Compose, Apache 2 reverse proxy, Ubuntu (Debian-based)
- **AI/ML:** Ollama (local primary) + Claude API (fallback), semantic search via pgvector embeddings
- **Auth:** Microsoft Entra ID (MSAL.js SPA + Auth.js v5) PKCE SSO
- **Key libraries:** TanStack Table, @xyflow/react (timeline), Zod v4 (validation), Framer Motion, Lucide Icons
## Architecture
The system is organized in clean layers:
- **Presentation:** Next.js App Router with server-rendered pages and interactive client components. Routing: `/hp-prod-tracker/dashboard`, `/projects/[id]/`, `/my-work`, `/settings`, etc.
- **API:** ~60 REST Route Handlers in `/app/api/` enforcing authentication middleware (RBAC, org-scoped access).
- **Business Logic:** 30+ service modules in `src/lib/services/` handling entity CRUD, pipeline state machine, automation rules, and AI tools.
- **State & Cache:** TanStack Query v5 for server state caching; Zustand for UI state (filters, sidebar collapse).
- **Database:** PostgreSQL 17 with Prisma 7. Schema includes: organizations, projects, deliverables, stages, revisions, team members, automation rules, chat messages, embeddings.
- **AI Integration:** Ollama (primary) running on internal GPU server (`10.24.42.219:11434`) for chat + embeddings; Claude API fallback.
- **Media Storage:** Docker volume `/data/uploads` persists revision images and HLS video segments; served via `/api/uploads/[...path]` route handler.
- **Authentication:** Microsoft Entra ID with MSAL.js browser flow (PKCE, no client secret). Local dev mode supports `DEV_BYPASS_AUTH=true` for testing.
**Data Flow:**
```
Browser (MSAL.js)
→ Next.js SSR pages (requireAuth middleware)
→ Route Handlers (/api/*) [RBAC checks]
→ Service Layer (entity logic + pipelines)
→ Prisma ORM
→ PostgreSQL (+ pgvector embeddings)
→ Docker volume (media files)
→ Ollama or Claude (AI chat)
```
**Pipeline State Machine:**
Each `DeliverableStage` progresses through states: `BLOCKED``NOT_STARTED``IN_PROGRESS``REVIEW``APPROVED``RELEASED`. Critical gates (Model Prep, Catalog Images) block downstream stages until approved. Orchestrated by `src/lib/pipeline/` and `src/lib/services/stage-service.ts`.
## Dev Commands
```bash
# Clone and install
git clone git@bitbucket.org:zlalani/hp-prod-tracker.git
cd hp-prod-tracker
nvm use 22
npm install
# Set up local PostgreSQL (macOS)
brew install postgresql@17
brew services start postgresql@17
createdb hp_prod_tracker
# Configure environment
cp .env.example .env
# Edit .env: set DATABASE_URL, DEV_BYPASS_AUTH=true, DEV_USER_ID, OLLAMA_HOST, etc.
# Initialize database
npx prisma migrate dev
npx prisma db seed
# Start development server
npm run dev
# → Open http://localhost:3000/hp-prod-tracker (auto-logged as dev admin)
# Build for production
npm run build
# Run production build locally
npm start
# Type check
npm run type-check
# Linting
npm run lint
# Format code
npm run format
# Database operations
npx prisma studio # GUI database browser
npx prisma migrate reset # Wipe and reseed
npm run db:seed-tracker # Sample tracker data
npm run db:seed-team # Sample team data
```
## Deployment
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/hp-prod-tracker`
- **Server:** optical-dev.oliver.solutions (Ubuntu, Docker Compose)
- **Deploy:** `bash deploy.sh` (runs locally; connects to prod server via SSH)
- **URL:** https://optical-dev.oliver.solutions/hp-prod-tracker
- **Port:** 3001 (host) → 3000 (container); 5491 (host PostgreSQL) → 5432 (container)
- **Service:** Docker Compose (not systemd). Services: `app` (Node 22 Alpine + FFmpeg), `db` (pgvector/pgvector:pg17)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/hp-prod-tracker
**Deploy script (`deploy.sh`) does:**
1. Install Docker, Docker Compose, Apache, git, ufw if missing
2. Pull latest code from Bitbucket
3. Validate `.env` file
4. `docker compose down --remove-orphans && docker compose up -d --build`
5. Wait for PostgreSQL healthcheck (30s timeout)
6. Wait for app healthcheck at `/api/health` (120s timeout)
7. Inject Apache config snippet into vhost (idempotent)
8. Configure UFW firewall rules (allow 22, 80; deny incoming by default)
**Apache reverse proxy:**
- Config: `/etc/apache2/sites-available/optical-dev.oliver.solutions.conf` includes `apache/hp-prod-tracker.conf`
- Routes `/hp-prod-tracker/*` to `http://127.0.0.1:3001`
- API `/hp-prod-tracker/api/chat` has 300s timeout (Ollama can take 60180s)
- Supports WebSocket for real-time features
- `LimitRequestBody` set to 500 MB for video uploads
- Modules required: `proxy`, `proxy_http`, `proxy_wstunnel`, `headers`, `rewrite`
## Environment Variables
**Critical (both local and prod):**
- `DATABASE_URL` — PostgreSQL connection string (e.g., `postgresql://user@localhost:5432/hp_prod_tracker?schema=public`)
- `AUTH_SECRET` — Random string for Auth.js session encryption
- `AZURE_TENANT_ID` — Microsoft Entra ID tenant ID
- `AZURE_CLIENT_ID` — Azure AD app registration client ID
- `AZURE_REDIRECT_URI` — OAuth redirect (e.g., `https://optical-dev.oliver.solutions/hp-prod-tracker/api/auth/callback/azure`)
**AI & Search:**
- `OLLAMA_HOST` — Ollama server URL (e.g., `http://10.24.42.219:11434`)
- `OLLAMA_CHAT_HOST` — Same or different Ollama endpoint
- `OLLAMA_CHAT_MODEL` — Model name (e.g., `gemma4:latest`)
- `OLLAMA_EMBED_MODEL` — Embedding model (e.g., `nomic-embed-text`)
- `ANTHROPIC_API_KEY` — Claude API key (fallback if Ollama unavailable)
**Local Development Only:**
- `DEV_BYPASS_AUTH` — Set to `true` to skip authentication
- `DEV_USER_ID` — Dev user ID (e.g., `dev-user-001`)
**Optional:**
- `LOG_LEVEL` — Logging verbosity (default: `info`)
- `NEXT_PUBLIC_API_BASE` — API base URL (default: derived from `AUTH_URL`)
See `.env.example` for complete list.
## API / Endpoints
~60 Route Handlers in `/app/api/`. Key examples:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/health` | GET | Health check (used by Docker healthcheck & Apache) |
| `/api/auth/callback/azure` | GET/POST | OAuth token exchange (Entra ID callback) |
| `/api/projects` | GET, POST | List/create projects |
| `/api/projects/[id]/deliverables` | GET, POST | List/create deliverables for project |
| `/api/deliverables/[id]/stages/[stageId]` | PATCH | Update stage state (transition, approve) |
| `/api/deliverables/[id]/revisions` | POST | Upload revision (image/video) |
| `/api/chat` | POST | AI chat (websocket-like; 300s timeout) |
| `/api/uploads/[...path]` | GET | Serve revision files from `/data/uploads` volume |
| `/api/automation/rules` | GET, POST | Automation rules CRUD |
| `/api/reports/weekly` | GET | Generate PDF weekly report |
All endpoints require authentication (via session cookie or `DEV_BYPASS_AUTH`). RBAC enforced: org-level access, team permissions, project-scoped roles.
## Known Issues
- **Ollama response time:** Chat can take 60180 seconds; Apache timeout set to 300s to accommodate.
- **WebSocket
## Sessions
### 2026-04-16 Configure SSO with Azure credentials and
@ -237,4 +395,4 @@ created: 2026-04-15
| 2026-04-15 | SSO redirect URI mismatch | AUTH_URL basePath missing, redirectProxyUrl construction | .env, src/lib/auth.ts |
| 2026-04-15 | SSO redirect URI mismatch | Review .env configuration and git history | .env, auth config files |
## Related
## Related

View file

@ -3,9 +3,9 @@ name: "LUSA Back Planner"
client: L'Oréal USA
status: active
server: optical-web-1
tech: [React, TypeScript, Vite, Tailwind CSS, pdfjs-dist, jsPDF, Azure AD (MSAL)]
tech: [React, TypeScript, Vite, Tailwind CSS, shadcn/ui, pdfjs-dist, jsPDF, Azure AD / MSAL]
local_path: /Users/ai_leed/Documents/Projects/Oliver/lusa-back-planner
deploy: Apache virtual host + scp
deploy: bash deploy.sh
url: https://ai-sandbox.oliver.solutions/lusa-Back-Planner
tags: [lusa, timeline, planner, pdf, react, typescript]
created: 2026-04-14
@ -15,101 +15,144 @@ service: apache2
## Overview
LUSA Back Planner is a client-side React SPA that generates production timelines for L'Oréal USA brand teams. Users upload a PDF brief, select a production lane (Rush/Express/Production/Specialist), and receive an instant workback timeline with milestones tied to the deadline. The app supports PDF brief parsing to auto-extract dates and brands, change request workflows, and PDF export. Authentication is enforced via Azure AD SSO (MSAL), making it accessible only to authorized L'Oréal team members.
LUSA Back Planner is a client-side React SPA that generates production timelines for L'Oréal USA creative teams. Users upload a PDF brief (or manually enter details), select a production lane (Rush/Express/Production/Specialist), and the app instantly generates a workback timeline with milestones calculated from the deadline by subtracting business days. It supports PDF parsing, inline timeline editing, change request workflows, and PDF export—all running entirely in the browser with no backend.
## Tech Stack
- **Frontend:** React 18 + TypeScript + Vite
- **Backend:** None (client-side only)
- **Database:** None (no persistence)
- **Infrastructure:** Apache 2.4 on Ubuntu Server; deployed as static SPA with FallbackResource fallback
- **AI/ML:** None
- **Key libraries:** pdfjs-dist (PDF parsing), jsPDF + html2canvas (PDF export), @azure/msal-react (Azure AD SSO), shadcn/ui (Radix UI components), date-fns (date math), react-router-dom
- **Frontend:** React 18 + TypeScript, Vite (build tool)
- **Backend:** None (fully client-side)
- **Database:** None (all logic in browser)
- **Infrastructure:** Apache httpd on Ubuntu server (optical-web-1); static site deployment
- **AI/ML:** N/A
- **Key libraries:**
- `@azure/msal-react` — Azure AD SSO authentication
- `pdfjs-dist` — client-side PDF parsing (brief & timeline uploads)
- `jsPDF` + `html2canvas` — PDF export
- `react-router-dom` — client-side routing
- `date-fns` — date/time utilities
- `shadcn/ui` — accessible UI components (built on Radix UI)
- `tailwindcss` — styling
## Architecture
The app is a client-side SPA with no backend. Authentication wraps the entire application via `AuthGuard` (MSAL redirect flow); unauthenticated users see a login page with "Sign in with Microsoft" button.
**Data Flow:**
1. User authenticates via Azure AD SSO → MSAL handles redirect/popup flow
2. User uploads PDF brief → `parse-brief.ts` extracts due date, brand, and lane suggestion using pdfjs-dist + regex/proximity scoring
3. User confirms production lane + deadline → `templates.ts:generateTimeline()` creates milestones by subtracting business days (accounting for federal holidays and weekends via `workdays.ts`)
4. `TimelineView` displays milestones, supports inline editing, and exports to PDF via jsPDF + html2canvas
5. Change requests: upload existing timeline PDF → `parse-timeline-pdf.ts` extracts milestones → user picks update reason → new PDF exported
**Component Tree:**
```
main.tsx
└─ AuthGuard (MSAL auth check; wraps entire app)
└─ MsalProvider
└─ App (BrowserRouter with routes)
├─ Index.tsx (main page; form state orchestrator)
├─ TimelineView.tsx (timeline display & PDF export)
└─ ChangeRequestTab.tsx (change request workflow)
└── AuthGuard (MSAL login page if unauthenticated)
└── MsalProvider
└── App (BrowserRouter with basename)
├── Index.tsx (main form & state orchestration)
├── TimelineView.tsx (render & edit milestones, PDF export)
├── ChangeRequestTab.tsx (upload existing timeline, re-export)
└── shadcn/ui components (form, dialogs, inputs, etc.)
```
**Key Modules:**
- `src/lib/templates.ts` — Lane definitions (4 types) + `generateTimeline()` logic
- `src/lib/workdays.ts` — Business day math: federal holidays, `subtractWorkdays()`, `countWorkdays()`
- `src/lib/parse-brief.ts` — PDF text extraction + keyword proximity + multi-regex date parsing
- `src/lib/sla-data.ts` — Asset type → duration mappings (90+ types)
- `src/lib/brands.ts` — 38 L'Oréal brand definitions
- `src/lib/msal-config.ts` — MSAL instance & login config (env-based)
**Data Flow:**
1. User lands on app → `AuthGuard` checks MSAL session state
- If unauthenticated: shows login page with "Sign in with Microsoft" button (popup flow)
- If authenticated: renders `App`
2. User uploads PDF brief → `parse-brief.ts` extracts due date, brand, and lane suggestion using keyword proximity scoring and multi-regex date detection
3. User confirms deadline + lane → `templates.ts:generateTimeline()` subtracts workdays (via `workdays.ts`) from deadline to create milestones
4. `TimelineView` renders milestones with inline editing; user exports as PDF via `jsPDF` + `html2canvas`
5. Change request flow: upload existing timeline PDF → `parse-timeline-pdf.ts` extracts existing milestones → user selects update reason → generate new PDF
**Key Files & Responsibilities:**
| File | Purpose |
|------|---------|
| `src/pages/Index.tsx` | Main page; owns form state (lane, deadline, revision count), orchestrates child components |
| `src/lib/templates.ts` | Lane definitions (Rush/Express/Production/Specialist); `generateTimeline()` function creates milestones |
| `src/lib/workdays.ts` | Business day math: federal holiday set, `subtractWorkdays()`, `countWorkdays()`, weekend/holiday detection |
| `src/lib/parse-brief.ts` | Extracts due date, brand, project name from uploaded PDF briefs using pdfjs-dist |
| `src/lib/parse-timeline-pdf.ts` | Parses existing timeline PDFs for change request workflow |
| `src/lib/sla-data.ts` | SLA durations for 90+ asset types |
| `src/lib/brands.ts` | 38 L'Oréal brand definitions with metadata |
| `src/lib/msal-config.ts` | MSAL instance config; reads tenant, client ID, and redirect URI from env vars |
| `src/components/TimelineView.tsx` | Renders timeline, handles inline milestone editing, PDF export |
| `src/components/ChangeRequestTab.tsx` | Change request tab: upload PDF, select update reason, re-export |
| `src/components/AuthGuard.tsx` | Wraps entire app; enforces Azure AD login, shows login page to unauthenticated users |
| `src/components/ui/*` | shadcn/ui primitives (do not edit manually) |
## Dev Commands
```bash
npm install # Install dependencies
npm run dev # Dev server at http://localhost:5173
npm run build # Production build → dist/
npm run build:dev # Development build
npm run preview # Preview production build
npm run lint # ESLint check
npm run test # Vitest (single run)
npm run test:watch # Vitest (watch mode)
npm run test -- src/test/example.test.ts # Run single test file
# Start dev server (http://localhost:5173)
npm run dev
# Build for production (outputs to dist/)
npm run build
# Build in development mode
npm run build:dev
# Preview production build locally
npm run preview
# Run linter
npm run lint
# Run tests (single run)
npm run test
# Run tests in watch mode
npm run test:watch
# Run a specific test file
npm run test -- src/test/example.test.ts
```
## Deployment
- **Server:** optical-web-1 (Ubuntu Server with Apache 2.4)
- **Deploy:** Manual via `scp` + Apache config; see README.md for detailed steps
- **Server:** optical-web-1 (Apache httpd)
- **Deploy:** `bash deploy.sh` (run on server at `/opt/lusa-back-planner`; script builds, clears Vite cache, copies dist/ to `/var/www/html/lusa-Back-Planner`)
- **URL:** https://ai-sandbox.oliver.solutions/lusa-Back-Planner
- **Port:** 80 (HTTP) / 443 (HTTPS with Let's Encrypt)
- **Service:** Apache 2 (`apache2`)
- **Port:** 443 (HTTPS via Apache)
- **Service:** Apache httpd (no custom systemd service)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/lusa-back-planner
**Deploy Steps:**
1. `npm install && npm run build` (creates `dist/`)
2. `scp -r dist/ user@optical-web-1:/var/www/backplanner`
3. Create Apache VirtualHost at `/etc/apache2/sites-available/backplanner.conf` (see README.md)
4. `sudo a2ensite backplanner.conf && sudo systemctl reload apache2`
5. (Optional) Enable HTTPS: `sudo certbot --apache -d ai-sandbox.oliver.solutions`
**Deploy steps:**
1. SSH to optical-web-1
2. Navigate to `/opt/lusa-back-planner`
3. `git pull origin main` (or your branch)
4. `bash deploy.sh`
5. Script builds, clears `.vite/` cache, copies `dist/` to `/var/www/html/lusa-Back-Planner`
**Subdirectory Deploy:** If hosting under a path (e.g., `/backplanner/`), set `base: "/backplanner/"` in `vite.config.ts` before building, then use Apache `Alias` instead of VirtualHost.
**Apache config** (in `apache.conf`):
- VirtualHost on port 443 (HTTPS)
- DocumentRoot: `/var/www/html/lusa-Back-Planner`
- SPA fallback: `FallbackResource /index.html` (routes client-side via `BrowserRouter` with `basename="/lusa-Back-Planner"`)
- Cache headers: immutable for JS/CSS/images
## Environment Variables
All vars must use `VITE_` prefix to be accessible in the browser.
All env vars use the `VITE_` prefix to be accessible in the browser at build time.
- `VITE_AZURE_TENANT_ID` — Azure AD tenant ID (L'Oréal's tenant)
- `VITE_AZURE_CLIENT_ID` — Azure AD application client ID; different for dev vs. production
- `VITE_AZURE_REDIRECT_URI` — Post-auth redirect URL; must match Azure AD app registration exactly (e.g., `https://ai-sandbox.oliver.solutions/lusa-Back-Planner` for production, `http://localhost:8888/lusa-Back-Planner` for local dev with port 8888)
| Variable | Description | Default / Example |
|----------|-------------|-------------------|
| `VITE_AZURE_TENANT_ID` | Azure AD tenant ID | `e519c2e6-bc6d-4fdf-8d9c-923c2f002385` |
| `VITE_AZURE_CLIENT_ID` | Azure AD app registration client ID (public) | `9079054c-9620-4757-a256-23413042f1ef` (production) or `15c0c4e2-bac0-4564-a3a6-c2717f00a6d9` (local dev) |
| `VITE_AZURE_REDIRECT_URI` | OAuth redirect URI after SSO | `https://ai-sandbox.oliver.solutions/lusa-Back-Planner` (production) or `http://localhost:8888/lusa-Back-Planner` (dev) |
Copy `.env.example` to `.env.local` and fill in values. Dev and production use different client IDs but same tenant.
**Note:** Tenant and client IDs are public (embedded in built JS); the sensitive secret is the Azure app registration's client secret, which is NOT in this repo.
## API / Endpoints
None. This is a fully client-side SPA with no backend API. All data processing (PDF parsing, timeline generation, date math) runs in the browser.
## Known Issues
- **MSAL redirect URI mismatch:** The `VITE_AZURE_REDIRECT_URI` must match exactly in Azure AD app registration. Recent commits (4d04d4b, 51bfd5b) fixed redirect URI bugs by adding `basename` to `BrowserRouter` and explicitly setting `redirectUri` in `loginRequest`.
- **localStorage key iteration crashes:** Fixed in f1061f5; MSAL now safely iterates localStorage.
- **interaction_in_progress errors:** Resolved in 03fd4be by clearing MSAL state on reload; also switched from redirect flow to popup flow (2e3a72c).
- No documented limitations on timeline generation, PDF parsing, or asset type coverage.
- **No SSH to optical-web-1 without explicit user instruction** — always ask before connecting
- **PDF parsing limitations:** `parse-brief.ts` uses keyword proximity scoring, which can fail on non-standard brief layouts. Multi-regex date detection handles common US date formats but may not catch all edge cases.
- **PDF export:** avoid DOM mutations during canvas capture in `TimelineView` that could break `html2canvas` rendering
- **No offline support:** requires network access for Azure AD authentication
- **MSAL interaction_in_progress errors:** historically resolved in git commits 03fd4beed638ff; uses popup flow instead of redirect to avoid localStorage iteration issues
## Git
- **Remote:** `git@bitbucket.org:zlalani/lusa-back-planner.git`
- **Last 20 commits:** Focus on MSAL authentication fixes, deploy script improvements, and initial app setup
- **Current branch:** Presumed `main` (no branch info in git log)
- **Remote:** git@bitbucket.org:zlalani/lusa-back-planner.git
- **Branch:** main (production-ready)
- **Recent commits:** Fix MSAL SSO, Apache config, deploy script executable bit, BrowserRouter basename for server subdirectory
## Sessions
### 2026-04-14 Project catalogued

View file

@ -2,82 +2,205 @@
name: "Mod Comms"
client: Barclays
status: active
tech: [Python, FastAPI, React, Vite, TypeScript, PostgreSQL, Gemini, Docker]
tech: [React, TypeScript, FastAPI, Python, PostgreSQL, Docker, Google Gemini, Alembic]
local_path: /Users/ai_leed/Documents/Projects/Oliver/modcomms
deploy: docker compose up --build
url:
server: baic (web-03)
deploy: ./deploy.sh
url: https://baic.oliver.solutions/modcomms/
server: baic
tags: [barclays, ai, compliance, proof-review, multi-agent, gcp]
created: 2026-04-14
last_commit: 2026-04-15
commits: 203
port: 8000
db: PostgreSQL
---
## Overview
AI-powered proof review tool for Barclays marketing materials. Multi-agent system analyzes uploaded proofs (images/PDFs) for:
- Legal compliance
- Brand adherence
- Tone of voice
- Channel suitability
4 specialist AI agents run in parallel, lead agent synthesizes the final verdict. Real-time WebSocket streaming (switched to REST polling due to GCP LB 30s timeout).
Backup: `modcomms_backup/`
ModComms is an AI-powered marketing proof review tool built by OLIVER Agency (BAIC team) for Barclays and Barclaycard. Users upload marketing assets (images, PDFs) and a five-agent AI system analyzes each proof in parallel for legal compliance, brand adherence, tone of voice, and channel suitability. Results return as Red/Amber/Green (RAG) verdicts with actionable feedback within seconds. The system is live in production and serves as internal tooling for Barclays' compliance and marketing teams.
## Tech Stack
- **Frontend:** React 18 + Vite + TypeScript (port 3000)
- **Backend:** Python + FastAPI + Uvicorn (port 8000)
- **Database:** PostgreSQL 16 (Alembic migrations)
- **AI:** Google Gemini Pro (primary) + Flash (fallback)
- **Auth:** Azure AD (MSAL)
- **Infrastructure:** Docker + docker-compose
- **Deployed on:** GCP (Google Cloud Platform)
- **Frontend:** React 18+ with TypeScript, Vite, MSAL (Azure AD auth)
- **Backend:** FastAPI with Python 3.12+, Uvicorn, asyncio
- **Database:** PostgreSQL 16 (Alpine Docker image)
- **Infrastructure:** Docker Compose, Apache HTTP Server (reverse proxy), Linux
- **AI/ML:** Google Gemini 2.5 Pro (primary), Gemini 2.5 Flash (fallback), LlamaParse for KB ingestion
- **Key libraries:** SQLAlchemy async ORM, Alembic migrations, PyMuPDF (PDF rasterization), Pydantic, python-multipart
## Architecture
ModComms uses a **multi-agent parallel pipeline** with REST polling (replaced WebSocket to fix GCP load balancer 30s timeout). The frontend is a React SPA served by Apache; the backend is a FastAPI service in Docker that orchestrates four specialist AI agents (Legal, Brand, ChannelBestPractices, ChannelTechSpecs) concurrently, then synthesizes results via a Lead Agent.
**Data flow:**
1. User uploads file → frontend base64-encodes → POST `/api/analyze`
2. Backend creates job ID, returns immediately, spawns asyncio background task
3. `_run_analysis()` loads reference docs (brand guidelines, channel specs), deduplicates via MD5 hash
4. Four agents call Gemini 2.5 Pro concurrently (each builds system prompt + reference context)
5. Lead Agent combines four SubReviews into single overallStatus (Passed/Failed/Requires Manual Legal Review)
6. File stored to `/app/storage` volume, thumbnail generated, proof_version inserted to PostgreSQL
7. Frontend polls `/api/jobs/{job_id}` until complete
**Model fallback:** Rate limits trigger automatic retry with Gemini 2.5 Flash; sets `job.model_fallback = true`.
**Timeout:** Hard 300-second limit per analysis.
```
Frontend (React/Vite :3000)
↓ REST polling (switched from WebSocket due to GCP 30s LB timeout)
Backend (FastAPI :8000)
4 AI Agents (Gemini) → Lead Agent → Final Verdict
PostgreSQL + File Storage
Browser (React SPA)
↓ HTTPS/REST polling
Apache (baic.oliver.solutions)
├─ /modcomms/ → frontend/dist/
└─ /back/ → proxy to :8000
FastAPI Backend (Docker :8000)
├─ Auth (Azure AD JWT)
├─ /api/analyze, /api/jobs, /api/campaigns, /api/proofs, /api/knowledge-base, /api/export
├─ asyncio agents:
│ ├─ LegalAgent → Gemini 2.5 Pro
│ ├─ BrandAgent → Gemini 2.5 Pro
│ ├─ ChannelBestPracticesAgent → Gemini 2.5 Pro
│ ├─ ChannelTechSpecsAgent → Gemini 2.5 Pro
│ └─ LeadAgent (synthesis)
└─ Services:
├─ AnalysisService (orchestration)
├─ ReferenceDocsService (brand/channel KB)
├─ KnowledgeBaseService (LlamaParse + distillation)
├─ StorageService (/app/storage)
└─ PDFService (PyMuPDF render)
PostgreSQL (Docker :5432)
└─ 12 tables: campaigns, proofs, proof_versions, users, flagged_items, etc.
```
## Dev Commands
```bash
# Clone repo
git clone git@bitbucket.org:zlalani/modcomms.git
cd modcomms
# Backend setup
cd backend
cp .env.example .env
# Edit .env: set GEMINI_API_KEY, DISABLE_AUTH=true
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# Start PostgreSQL (Docker)
docker compose up -d postgres
# Run migrations
alembic upgrade head
# Start backend
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Check health: curl http://localhost:8000/health
# Frontend setup (new terminal)
cd frontend
npm install
# Create .env.local
cat > .env.local << 'EOF'
VITE_BACKEND_URL=http://localhost:8000
VITE_AZURE_CLIENT_ID=your_client_id
VITE_AZURE_TENANT_ID=your_tenant_id
VITE_AZURE_REDIRECT_URI=http://localhost:3000/
EOF
npm run dev
# Frontend at http://localhost:3000
# Build frontend for production
npm run build
```
## Deployment
- **Run:** `docker compose up --build`
- **Local:** Frontend :3000, Backend :8000
- **Server:** GCP (Google Cloud Platform)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/modcomms`
### Environment Variables
**Frontend (`frontend/.env.local`)**
```
GEMINI_API_KEY=
VITE_BACKEND_WS_URL=ws://localhost:8000/ws/analyze
VITE_BACKEND_URL=http://localhost:8000
```
- **Server:** baic.oliver.solutions (Apache + Docker Compose)
- **Deploy:** `./deploy.sh` (full pipeline: git pull → npm build → Docker build → migrations → restart)
- **URL:** https://baic.oliver.solutions/modcomms/ (frontend), https://baic.oliver.solutions/back/ (backend)
- **Port:** 8000 (backend container, proxied via Apache)
- **Service:** Docker Compose (no systemd service)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/modcomms
**Deploy process** (on server):
1. Copy `.env.deploy.example``.env.deploy`, fill secrets (GEMINI_API_KEY, Azure tenant/client IDs, DB passwords)
2. Run `./deploy.sh`
- `git pull`
- `npm install && npm run build` in frontend
- Copy `frontend/dist/` to `/var/vhosts/baic.oliver.solutions/htdocs/modcomms`
- `docker compose build` backend
- Start PostgreSQL container
- `alembic upgrade head` (migrations)
- `docker compose up -d --force-recreate backend`
- Wait for `/health` 200 response
**Alternative (dev server):** `./deploy-dev.sh` (uses `sudo docker`, fixes dist permissions)
## Environment Variables
**Backend (`backend/.env`)**
```
GEMINI_API_KEY=
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/modcomms
AZURE_TENANT_ID=
AZURE_CLIENT_ID=
CORS_ORIGINS=http://localhost:3000
DISABLE_AUTH=true # local dev only
```
### Dev Commands
```bash
# Frontend
cd frontend && npm install && npm run dev
- `GEMINI_API_KEY` — Google Gemini API key (required)
- `DATABASE_URL` — PostgreSQL async connection string; default: `postgresql+asyncpg://modcomms:modcomms_dev@localhost:5432/modcomms`
- `AZURE_TENANT_ID` — Azure AD tenant ID (required in production)
- `AZURE_CLIENT_ID` — Azure AD app registration ID (required in production)
- `DISABLE_AUTH` — Set `true` to skip JWT validation (local dev only); default: `false`
- `CORS_ORIGINS` — Comma-separated allowed origins; default: `http://localhost:3000`
- `HOST` — Server bind address; default: `0.0.0.0`
- `PORT` — Server port; default: `8000`
- `REFERENCE_DOCS_PATH` — Path to brand/channel guideline documents (local or DB)
# Backend
cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
**Docker Compose (`.env`)**
# Database migrations
cd backend && alembic upgrade head
```
- `POSTGRES_USER` — default: `modcomms`
- `POSTGRES_PASSWORD` — default: `modcomms_dev`
- `POSTGRES_DB` — default: `modcomms`
- `BACKEND_PORT` — host port for backend container; default: `8000`
- `POSTGRES_PORT` — host port for PostgreSQL; default: `5432`
**Frontend (`frontend/.env.local`)**
- `VITE_BACKEND_URL` — Backend API URL; default: `http://localhost:8000`
- `VITE_AZURE_CLIENT_ID` — Azure AD client ID
- `VITE_AZURE_TENANT_ID` — Azure AD tenant ID
- `VITE_AZURE_REDIRECT_URI` — Auth redirect; default: `http://localhost:3000/`
## API Endpoints
**Analysis workflow**
- `POST /api/analyze` — Submit proof for analysis; returns `{ job_id, status }`
- `GET /api/jobs/{job_id}` — Poll analysis status; returns full ProofVersion when complete
- `GET /api/jobs` — List all jobs (paginated)
**Campaign & proof management**
- `GET /api/campaigns` — List campaigns (user's org)
- `POST /api/campaigns` — Create campaign
- `GET /api/campaigns/{campaign_id}/proofs` — List proofs in campaign
- `GET /api/proofs/{proof_id}` — Get proof and all versions
- `GET /api/proofs/{proof_id}/versions/{version_id}` — Get specific version
**Knowledge base**
- `POST /api/knowledge-base/upload` — Upload KB document (LlamaParse ingestion)
- `GET /api/knowledge-base` — List knowledge base documents
- `DELETE /api/knowledge-base/{kb_id}` — Remove KB document
**Export**
- `GET /api/export/campaign/{campaign_id}/csv` — Export campaign data as CSV (super_admin, oversight_admin only)
**Health**
- `GET /health` — Unauthenticated health check
## Known Issues
- **WebSocket timeout (resolved):** Replaced WebSocket with REST polling to fix G
## Timeline / Git History
| Date | Change |
@ -110,4 +233,4 @@ cd backend && alembic upgrade head
## Related
- [[enterprise-ai-hub-nexus/Enterprise AI Hub Nexus]] (similar AI platform)
- [[semblance/Semblance]] (same GCP deployment issues)
- [[semblance/Semblance]] (same GCP deployment issues)

View file

@ -3,9 +3,9 @@ name: "OliVAS — Visual Attention Software"
client: OLIVER
status: active
server: optical-dev
tech: [React 18, TypeScript, FastAPI, Python 3.12, PostgreSQL, DeepGaze, Claude Sonnet, Docker]
tech: [React 19, FastAPI, Python 3.12, PostgreSQL 16, Docker, Google Cloud Run, PyTorch, Azure AD]
local_path: /Users/ai_leed/Documents/Projects/Oliver/olivas
deploy: docker compose up --build
deploy: bash deploy.sh
url: https://optical-dev.oliver.solutions/olivas
tags: [oliver, visual-attention, ai, heatmap, saliency, design-analysis]
created: 2026-04-14
@ -15,170 +15,176 @@ db: PostgreSQL 16
## Overview
**OliVAS** (OLIVER Visual Attention Suite) is an open-source web application that predicts where humans will look in an image during the first 35 seconds of viewing, without requiring physical eye-tracking hardware. Built for creative teams, designers, and marketers at OLIVER, it delivers saliency heatmaps, gaze sequence predictions, hotspot analysis, and AI-powered design insights. Users can analyze images via deep learning models (DeepGaze I/IIE/III), draw Areas of Interest (AOI) for detailed attention measurement, and generate professional PDF reports with both rule-based and optional Claude Sonnetpowered recommendations.
**OliVAS** (OLIVER Visual Attention Suite) is an open-source web application that predicts where humans look during the first 35 seconds of viewing an image using DeepGaze ML models. It generates saliency heatmaps, gaze sequence predictions, hotspot analysis, and a Design Effectiveness Score (0100) to provide actionable design insights without physical eye-tracking hardware. Optional integration with Claude Sonnet 4.6 adds AI-powered design critique. The application serves OLIVER staff through a React SPA backed by a FastAPI server, with optional offloading of ML inference to Google Cloud Run.
## Tech Stack
- **Frontend:** React 18, TypeScript, Vite, Tailwind CSS, Zustand (state), React Router
- **Backend:** FastAPI, Python 3.12, SQLAlchemy (async ORM), Pydantic v2
- **Database:** PostgreSQL 16 (async driver: asyncpg)
- **Infrastructure:** Docker Compose, deployed to Google Cloud Run (optional offloading), Azure AD SSO
- **AI/ML:** DeepGaze I/IIE/III (saliency models), Anthropic Claude Sonnet 4.6 (optional design analysis), OpenAI CLIP (optional)
- **Key Libraries:**
- `deepgaze-pytorch` — visual attention prediction
- `ReportLab` — PDF generation with Montserrat typography
- `Alembic` — database migrations
- `MSAL` — Azure AD authentication
- **Frontend:** React 19, TypeScript, Vite, Azure AD MSAL (Microsoft SSO)
- **Backend:** FastAPI, Python 3.12, Uvicorn, Alembic (migrations)
- **Database:** PostgreSQL 16 (Docker container on port 5453)
- **Infrastructure:** Docker Compose, Apache 2 (reverse proxy), Google Cloud Run (optional offload)
- **AI/ML:** DeepGaze IIE + III (saliency), PyTorch, Anthropic Claude Sonnet 4.6 (optional)
- **Key libraries:** Pillow (image processing), matplotlib (heatmap overlay), ReportLab (PDF generation), sqlalchemy (ORM)
## Architecture
OliVAS follows a **layered architecture** with optional cloud offloading:
```
┌─────────────────────────────────────────────────────────────┐
│ User Browser │
│ React SPA (Vite) — /olivas subpath, Azure AD SSO login │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS (CORS: optical-dev.oliver.solutions)
┌─────────────────────────────────────────────────────────────┐
│ FastAPI Backend (8000) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │Auth (Azure) │ │Analysis API │ │Project Mgmt │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
│ │ │ Saliency │ CRUD │
│ └──────────────┼─────────────────┴──────────────┐ │
│ ▼ │ │
│ ┌─────────────────────────────┐ │ │
│ │ ML Processing Service │ │ │
│ │ • DeepGaze (CPU/GPU) │ │ │
│ │ • Image preprocessing │ │ │
│ │ • PDF generation (ReportLab)│ │ │
│ │ • Optional: Cloud Run offload│ │ │
│ └─────────────────────────────┘ │ │
│ │ │ │
└────────────────────────┼────────────────────────────────┼───┘
│ │
┌────────────────┼────────────────┐ │
▼ ▼ ▼ │
┌─────────┐ ┌──────────────┐ ┌──────────┐ │
│ Local │ │ PostgreSQL │ │File │ │
│GPU/CPU │ │(5432→5453) │ │Storage │ │
└─────────┘ │ (olivas DB) │ │/uploads │ │
└──────────────┘ └──────────┘ │
┌───────────────────────────────────────────────────┐ │
│ Optional: Google Cloud Run Saliency Service │ │
│ (CLOUD_RUN_SALIENCY_URL env var) │ │
└───────────────────────────────────────────────────┘ │
▲ │
└────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Browser (React 19 SPA) │
│ - Azure AD MSAL login → JWT token │
│ - Upload images, view results, download PDF reports │
└───────────────────────────────┬─────────────────────────────────┘
│ REST API
┌───────────v───────────┐
│ Apache 2 (LB) │
│ - TLS termination │
│ - /olivas → SPA │
│ - /api/ → FastAPI │
└───────────┬───────────┘
┌──────────────────────┴──────────────────────┐
│ │
┌────v────────────┐ ┌──────────v────────┐
│ FastAPI Backend │ │ PostgreSQL 16 │
│ - Auth validation │ - Projects │
│ - Analysis pipeline │ - Analyses │
│ - Orchestration │ - AOIs │
│ - Report generation │ - Comparisons │
└────┬────────────┘ └───────────────────┘
├─► Local DeepGaze (in-process) OR
│ ┌────────────────────────────────────────┐
│ │ Google Cloud Run (optional offload) │
│ ├─ Saliency service (GPU-free inference) │
│ └─ Processing service (heatmap/gaze gen) │
└─► File Storage
├─ /app/data/uploads/{id}/
│ ├─ original.png
│ ├─ thumbnail.png
│ ├─ heatmap_overlay.png
│ ├─ heatmap_standalone.png
│ ├─ gaze_sequence.png
│ └─ report.pdf
└─► Optional Claude Sonnet 4.6
(design insights via Anthropic API)
```
**Data Flow:**
1. User logs in via Azure AD (MSAL on frontend)
2. Uploads image → backend stores in `/app/data/uploads`
3. Backend queues saliency analysis (local GPU/CPU or Cloud Run)
4. DeepGaze model generates attention heatmap + fixation points
5. Backend optionally calls Claude Sonnet for design insights
6. Frontend displays interactive heatmap, hotspots, AOI tools
7. User generates PDF report → ReportLab merges visuals + metrics + insights
8. Reports stored in PostgreSQL and file system
**Analysis Pipeline Flow:**
1. User uploads image → validated, stored, Analysis record created (status=pending)
2. **Saliency inference** → DeepGaze model predicts attention map (local or Cloud Run)
3. **Post-processing** → heatmap overlay, gaze sequence image, hotspot detection
4. **Scoring** → Design Effectiveness Score computed from saliency metrics
5. **Storage** → all artefacts saved to filesystem under `data/uploads/{id}/`
6. **AI insights** (optional) → Claude critique generated
7. **PDF report** → ReportLab compiles visuals, metrics, and insights
## Dev Commands
```bash
# Clone and navigate
# Prerequisites: Python 3.12+, Node.js 18+, Docker, Docker Compose v2
# Clone and initial setup
git clone git@bitbucket.org:zlalani/olivas.git
cd olivas
# Full setup (Python 3.12, Node 18 required)
make setup
cp .env.example .env
# Edit .env: set ANTHROPIC_API_KEY if needed; AZURE_AUTH_ENABLED=false for dev
# Start database
make db-up
make db-up # PostgreSQL 16 on port 5453
# Run migrations
make db-migrate
# Backend setup and run
make setup-backend # Create .venv, install deps (~200 MB model weights)
make db-migrate # Run Alembic migrations
make dev-backend # Start FastAPI on http://localhost:8000 (with --reload)
# Start all three services in separate terminals
make dev-backend # Backend on 8000
make dev-frontend # Frontend on 1577
# Frontend setup and run
make setup-frontend # npm install
make dev-frontend # Vite dev server on http://localhost:1577
# Or use single command (runs in background)
make dev
# Full Docker Compose (dev with hot reload)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
# Docker deployment
make docker-up # Full stack via docker-compose
make docker-down # Stop stack
# Full Docker Compose (prod-like, no hot reload)
docker compose up --build
# Testing & linting
make test
make lint
make lint-fix
# Cleanup
docker compose down # Stop all services
docker compose down -v # Stop and remove volumes (destructive)
# Clean build artifacts
make clean
# API documentation
# Open http://localhost:8000/docs (Swagger UI)
```
## Deployment
- **Server:** optical-dev (subpath `/olivas`)
- **Deploy:** `docker compose up --build` (or see production deploy script)
- **Server:** optical-dev.oliver.solutions
- **Deploy:** SSH to production server, run `cd /opt/olivas && bash deploy.sh`
- **URL:** https://optical-dev.oliver.solutions/olivas
- **Frontend Port:** 1577 (internal); served via Apache reverse proxy on :443
- **Backend Port:** 8000
- **Service:** Docker Compose (no systemd service)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/olivas`
- **Port:** 1577 (frontend via Apache), 8000 (backend internal), 5453 (PostgreSQL)
- **Service:** None (Docker Compose managed, not systemd)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/olivas
**Cloud Offloading (optional):**
- Saliency inference can offload to Google Cloud Run via `CLOUD_RUN_SALIENCY_URL` and `CLOUD_RUN_PROCESSING_URL`
- Requires `GOOGLE_CLOUD_PROJECT` and `CLOUD_RUN_SECRET` env vars
**Deploy script details:**
- Pulls latest from `main` branch (`git pull --ff-only`)
- Builds backend Docker image, starts PostgreSQL, runs migrations
- Builds frontend with Vite, deploys dist to `/var/www/html/olivas/`
- Health check: `curl http://localhost:8000/api/health`
- Idempotent and safe to re-run
**Production paths:**
- Repo: `/opt/olivas`
- Frontend dist: `/var/www/html/olivas/`
- Apache config: `/opt/olivas/olivas-apache.conf`
- Logs: `/var/log/apache2/olivas-{error,access}.log`
## Environment Variables
### Backend (`.env`)
- `DATABASE_URL` — PostgreSQL connection string (e.g., `postgresql+asyncpg://olivas:olivas@localhost:5453/olivas`)
- `UPLOAD_DIR` — Path to store uploaded images (default: `./data/uploads`)
- `DEVICE` — ML inference device: `auto` | `cpu` | `cuda` (default: `auto`)
- `BACKEND_HOST` — Bind address (default: `0.0.0.0`)
- `BACKEND_PORT` — Port (default: `8000`)
- `CORS_ORIGINS` — Comma-separated CORS whitelist
- `ANTHROPIC_API_KEY` — Claude API key (optional; leave empty to disable AI insights)
- `AZURE_AUTH_ENABLED` — Enable Azure AD SSO (default: `true`)
- `AZURE_TENANT_ID` — Azure tenant ID
- `AZURE_CLIENT_ID` — Azure app registration client ID
- `CLOUD_RUN_SALIENCY_URL` — Google Cloud Run endpoint for saliency (optional)
- `CLOUD_RUN_PROCESSING_URL` — Google Cloud Run endpoint for image processing (optional)
- `CLOUD_RUN_SECRET` — Secret for Cloud Run authentication (optional)
- `GOOGLE_CLOUD_PROJECT` — GCP project ID (default: `optical-414516`)
`AZURE_AUTH_ENABLED` — Enable Azure AD SSO (default: true; set false for local dev without Microsoft account)
### Frontend (Vite env vars in `.env`)
- `VITE_AZURE_TENANT_ID` — Azure tenant ID
- `VITE_AZURE_CLIENT_ID` — Azure app registration client ID
- `VITE_AZURE_REDIRECT_URI` — OAuth2 redirect URI (e.g., `https://optical-dev.oliver.solutions/olivas`)
`ANTHROPIC_API_KEY` — Anthropic API key for Claude Sonnet 4.6 design insights (optional; if empty, AI insights disabled)
### Docker Compose
- All above env vars can be injected into `docker-compose.yml` via shell environment or `.env` file
`DATABASE_URL` — PostgreSQL connection string (default: `postgresql://olivas:olivas@postgres:5432/olivas`)
`CLOUD_RUN_SALIENCY_URL` — (Optional) URL of Cloud Run saliency inference service; if set, uses remote inference instead of local
`CLOUD_RUN_PROCESSING_URL` — (Optional) URL of Cloud Run heatmap/gaze processing service; if set, uses remote processing
`AZURE_AD_CLIENT_ID` — Microsoft Entra app registration ID (required if `AZURE_AUTH_ENABLED=true`)
`AZURE_AD_TENANT_ID` — Microsoft Entra tenant ID (required if `AZURE_AUTH_ENABLED=true`)
`AZURE_AD_CLIENT_SECRET` — Microsoft Entra client secret (required if `AZURE_AUTH_ENABLED=true`)
`VITE_API_BASE_URL` — Frontend API base URL (default: `http://localhost:8000` in dev; `/api` in prod via Apache proxy)
## API / Endpoints
Backend (FastAPI) exposes REST endpoints. Key areas (not exhaustively documented in source):
- **Auth:** Azure AD MSAL integration — token validation on all protected routes
- **Images:** `POST /api/images/upload` → store and return metadata
- **Analyses:** `POST /api/analyses/saliency` → trigger DeepGaze model
- **Hotspots:** `GET /api/analyses/{id}/hotspots` → top 5 attention regions
- **AOI:** `POST /api/aoi/create` → define Areas of Interest, measure attention
- **Projects:** CRUD operations for organizing analyses
- **Reports:** `GET /api/reports/{id}` → download PDF
- **Comparison:** Side-by-side analysis comparison endpoint
**Base URL:** `http://localhost:8000/api` (dev) or `https://optical-dev.oliver.solutions/api` (prod)
(Full OpenAPI docs available at `/docs` when backend is running)
**Key endpoints:**
- `POST /projects` — Create project
- `POST /projects/{id}/analyses` — Upload image and trigger analysis
- `GET /projects/{id}/analyses/{analysis_id}` — Get analysis results (saliency, hotspots, gaze sequence)
- `GET /projects/{id}/analyses/{analysis_id}/report` — Download PDF report
- `GET /health` — Health check
See `docs/project/api_spec.md` for full endpoint documentation.
## Known Issues
- **Frontend auth:** Images must be loaded via authenticated fetch (not bare `<img src>`) due to CORS and SSO — recent commits (e.g., `e9e19fb`) fixed this
- **Subpath deployment:** Requires careful Apache rewrite configuration and React Router basename — see commits `91a0104`, `8484aab`
- **Database migrations:** Alembic PYTHONPATH must be set correctly in production (`c6cd329`)
- **Cloud Run offloading:** Not required but optional for scaling — incomplete in documentation
- **Model loading latency:** First backend startup takes 3060 seconds (DeepGaze weights load into memory)
- **Upload size limit:** 50 MB per image (set in Apache `LimitRequestBody` and FastAPI config)
- **Cloud Run offloading:** Optional; if not configured, all ML inference runs locally (requires GPU or sufficient CPU)
- **Azure AD required in production:** Local dev should disable `AZURE_AUTH_ENABLED`
## Git
- **Remote:** git@bitbucket.org:zlalani/olivas.git
- **Branch:** main
- **Recent activity:** Authentication (Azure AD SSO), Cloud Run integration, Design Effectiveness Score (replaced entropy), Apache rewrite fixes for `/olivas/` subpath deployment
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,177 +1,173 @@
---
name: "Oliver Sales Ops Platform"
client: OLIVER
client: Oliver Internal
status: active
tech: [Python, FastAPI, React, TypeScript, PostgreSQL, Redis, Claude API, Docker]
tech: [React, FastAPI, PostgreSQL, Claude API, Docker, TypeScript, SQLAlchemy]
local_path: /Users/ai_leed/Documents/Projects/Oliver/oliver-sales-ops-platform
deploy: ./deploy.sh
url: http://optical-dev/oliver-sales-ops-platform
deploy: deploy.sh (auto port selection + Apache proxy)
url: https://optical-dev.oliver.solutions/oliver-sales-ops-platform/
server: optical-dev
tags:
- project
created: 2026-04-28
port: 8003
db: PostgreSQL
db: PostgreSQL 16
---
## Overview
**oliver-sales-ops-platform** is an end-to-end RFP-to-mobilization pipeline for OLIVER's commercial team. It automates the 17-stage process of transforming a brief into a defensible proposal: intake, qualification, asset matching, ratecard generation, delivery model recommendation, team shaping, validation, and pitch material generation. Users drop in a brief and walk it through stages that combine Claude AI agents, human decisions, and pure-Python calculations to produce structured proposals with full cost tracking.
The OLIVER Sales Ops Platform is an end-to-end RFP-to-mobilization pipeline that automates the commercial proposal process. An account team uploads a client brief, and the platform walks it through a 17-stage state machine, with Claude AI agents driving key decision points: intake, diagnosis, qualification, Q&A pack generation, asset normalization, GMAL matching, ratecard building, team shaping, pitch deck generation, and post-win delivery planning. It produces defensible, fully-costed proposals backed by AI synthesis and human approvals. This is Phase 1 of the GMAL Scope Builder v2, deployed on optical-dev and used internally by Oliver's commercial and sales operations teams.
## Tech Stack
- **Frontend:** React 18, TypeScript, Vite, TailwindCSS
- **Backend:** FastAPI (Python 3.11+), SQLAlchemy ORM, Alembic migrations
- **Database:** PostgreSQL 16 + Redis 7 (for sessions/queuing)
- **Infrastructure:** Docker Compose, Apache (prod), systemd (optional)
- **AI/ML:** Anthropic Claude API (multiple specialized agents per stage)
- **Key libraries:** `anthropic`, `pydantic`, `asyncpg`, `mailgun-python`, Azure AD/MSAL
- **Frontend:** React 18 + TypeScript, Vite (HMR dev), TailwindCSS, Lucide React icons
- **Backend:** FastAPI (Python 3.11+), async SQLAlchemy ORM, Alembic migrations
- **Database:** PostgreSQL 16 (Alpine), async driver via `asyncpg`
- **Infrastructure:** Docker Compose, Apache 2.x reverse proxy (optical-dev), volume-based file uploads
- **AI/ML:** Anthropic Claude (claude-opus-4-5 / claude-4.7), nine distinct agent services
- **Key libraries:** Pydantic v2 (settings validation), Mailgun (approval notifications), Redis (future Celery queue)
## Architecture
The platform follows a **stage-machine** pattern: each Opportunity moves through 17 immutable stages. Stages are strictly ordered (`/stages/{n}/complete` enforces progression). The flow is:
**Component map:**
- Browser (MSAL SSO or dev auth bypass) → Apache reverse proxy (optical-dev) → FastAPI backend on port 8003 → PostgreSQL 16 + Redis 7 → Anthropic Claude API + Mailgun
**State machine:** Every Opportunity has 17 `stage_states` rows. Stage 1 starts `in_progress`; stages 217 start `not_started`. Stage advancement requires `/stages/{n}/complete`; stages 3 and 14 additionally gate on `Approval` rows being marked `approved`. Agent runs (intake, diagnosis, etc.) are independent of stage state — users iterate freely. Destructive re-runs cascade invalidate downstream stages.
**Data model:**
- `Opportunity` (root entity) → `StageState` (1 per stage) → `Approval` (gated stages)
- Client assets normalized into `ClientAsset` rows
- `Match` rows (Stage 7) link opportunities to GMAL capabilities
- `RatecardLine` (Stage 8) and `TeamShape` (Stage 11) compute delivery cost/resource plan
- Artifacts (pitch markdown, implementation plan) stored as JSON on stage record or exported as files
**Local dev adds Vite HMR frontend on port 3011** (activated with `COMPOSE_PROFILES=dev`). Production serves pre-built static SPA from Apache.
```
Brief (Intake)
↓ (Claude Diagnosis Agent)
↓ (Human Qualification + Approval Gate)
↓ (Asset Matching + Ratecard Calculation)
↓ (Delivery Model + Team Shape + Gaps)
↓ (Human Efficiency Tuning + Approval Gate)
↓ (Pitch Materials + Post-Win Planning)
→ [Phase 2: Salesforce/SharePoint push deferred]
┌─ Browser (React SPA) ─────────────────────┐
│ localhost:3011 (dev) or Apache (prod) │
└────────────────┬──────────────────────────┘
│ /api/
┌─ FastAPI Backend ─────┐
│ localhost:8003 │
│ async SQLAlchemy │
└────────┬──────────────┘
┌────────────┼────────────┐
▼ ▼ ▼
PostgreSQL Redis Anthropic
(5435) (6380) Claude API
```
**Key design decisions:**
1. **Immutable stage artifacts:** Each stage produces a JSON `stage_artifact` record; artifacts are never overwritten, only new ones created.
2. **Approval gates:** Stages 3 and 14 require explicit `Approval` rows (human review/sign-off). Approvals fan out via in-app notifications and Mailgun email.
3. **Cost tracking:** Every Claude call records tokens (in/out) and USD cost on the artifact; per-stage spend is visible in the UI.
4. **Pure-Python calculations:** Ratecard (stage 8) and Team Shape (stage 11) use deterministic Python logic, not AI.
5. **Async-first backend:** FastAPI with `asyncpg` for I/O; Redis for caching and session management.
6. **Dev auth bypass:** For local dev, `DEV_AUTH_BYPASS=true` skips Azure AD; production uses Azure AD + allowlist (`allowed_users.yaml`).
**Data model:**
- `Opportunity` (the brief/project container)
- `Stage` (immutable records: stage number, status, timestamps)
- `StageArtifact` (JSON output from each stage, includes cost data)
- `Approval` (sign-off records for gated stages)
- `User` (SSO identity, role-based access)
- `Notification` (in-app alerts; Mailgun for email)
## Dev Commands
```bash
# Clone and setup
# Clone & setup
git clone git@bitbucket.org:zlalani/oliver-sales-ops-platform.git
cd oliver-sales-ops-platform
cp .env.example .env
# Edit .env: set ANTHROPIC_API_KEY, optional AZURE_* for SSO, MAILGUN_* for emails
$EDITOR .env
# Edit .env: set ANTHROPIC_API_KEY, DEV_AUTH_BYPASS=true
# Start all services (db, redis, backend, frontend)
# Start full dev stack (Vite HMR + backend)
COMPOSE_PROFILES=dev docker compose up -d
# Or backend only (no frontend container)
docker compose up -d
# Frontend only: Vite dev server (for hot-reload)
COMPOSE_PROFILES=dev docker compose up -d frontend
# Verify backend health
curl http://localhost:8003/api/health
# Backend logs
# View logs
docker compose logs -f backend
# Database migrations (run inside backend container or via CLI)
# Restart a service
docker compose restart backend
# Run tests (in isolated venv)
python3 -m venv /tmp/osop_test_venv
/tmp/osop_test_venv/bin/pip install -r backend/requirements-dev.txt psycopg2-binary
cd backend && /tmp/osop_test_venv/bin/pytest tests/ -v
# Connect to DB
docker compose exec db psql -U osop_user -d oliver_sales_ops
# Manual migration
docker compose exec backend alembic upgrade head
# Run tests (pytest — backend only)
docker compose exec backend pytest tests/ -v
# Stop all services
docker compose down
# Rebuild after code changes (e.g., new dependencies)
docker compose build backend
docker compose up -d backend
```
## Deployment
- **Server:** `optical-dev` (Apache + systemd)
- **Deploy script:** `./deploy.sh` (builds SPA static files, syncs to server, restarts service)
- **URL:** `http://optical-dev/oliver-sales-ops-platform` (or per `APP_PUBLIC_URL`)
- **Backend port:** 8003 (internal Docker; proxied via Apache)
- **Server:** optical-dev (internal Oliver dev server)
- **Deploy:** `deploy.sh` (auto-selects free ports, renders Apache conf from template, persists to `.env`)
- **URL:** `https://optical-dev.oliver.solutions/oliver-sales-ops-platform/`
- **Port:** 8003 (backend); Apache proxies `/oliver-sales-ops-platform/api/``127.0.0.1:8003`
- **Service:** None (Docker Compose managed directly; no systemd wrapper)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/oliver-sales-ops-platform`
**Deploy procedure:**
```bash
./deploy.sh
```
This script:
1. Builds the React SPA (`npm run build` in `frontend/`)
2. Finds free ports for backend/frontend (or uses configured ones)
3. Renders Apache config from template (`deploy/apache-conf.tpl`)
4. Syncs code + Docker images to `optical-dev`
5. Restarts backend service (systemd or docker-compose on remote)
**First-time server setup (optical-dev):**
1. Clone repo to `/opt/oliver-sales-ops-platform/`
2. Copy `.env.example``.env`, set `ANTHROPIC_API_KEY` and `APP_PUBLIC_URL`
3. Run `deploy.sh` — auto-selects ports (5435, 6380, 8003), builds images, starts containers, renders Apache conf
4. Manually add include to Apache vhost: `Include /opt/oliver-sales-ops-platform/deploy/apache-osop.conf`
5. `sudo apachectl configtest && sudo systemctl reload apache2`
**Static SPA path:** `/var/www/html/oliver-sales-ops-platform/` (built during deploy, served by Apache)
## Environment Variables
**Backend (`backend/.env` or docker-compose):**
- `DATABASE_URL` — PostgreSQL async connection string (asyncpg)
- `DATABASE_URL_SYNC` — PostgreSQL sync string (Alembic migrations)
- `REDIS_URL` — Redis connection (default: `redis://redis:6379/0`)
- `ANTHROPIC_API_KEY` — Claude API key (required)
- `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` — Azure AD SSO (leave blank to disable)
- `DEV_AUTH_BYPASS` — Set to `true` for local dev to skip SSO (NEVER in production)
- `DEV_AUTH_EMAIL`, `DEV_AUTH_NAME`, `DEV_AUTH_ROLE` — Dev user identity (if `DEV_AUTH_BYPASS=true`)
- `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_FROM`, `MAILGUN_REGION` — Email config (leave key blank in dev to log instead)
- `APP_PUBLIC_URL` — Base URL for approval email links (e.g., `http://localhost:3011`)
- `APP_PATH_PREFIX` — URL path prefix (e.g., `/oliver-sales-ops-platform`)
- `ALLOWED_USERS_PATH` — Path to `allowed_users.yaml` (default: `/app/config/allowed_users.yaml`)
- `DATA_DIR` — Path to reference data (GMAL Excel, etc.; default: `./data`)
**Frontend (`frontend/.env` or Vite config):**
- `VITE_DEV_AUTH_BYPASS` — Same as backend; must match for dev auth to work
- `ANTHROPIC_API_KEY` — Claude API key (required, no default)
- `DEV_AUTH_BYPASS` — Set `true` to skip Azure SSO; uses dev identity below (default: `true` in `.env.example`)
- `DEV_AUTH_EMAIL` — Dev bypass user email (default: `dev@localhost`)
- `DEV_AUTH_NAME` — Dev bypass display name (default: `Dev User`)
- `DEV_AUTH_ROLE` — Dev bypass role: `admin` | `editor` | `viewer` (default: `editor`)
- `OSOP_DB_PORT` — Postgres host port (auto-selected, default 5435, range 54355535)
- `OSOP_REDIS_PORT` — Redis host port (auto-selected, default 6380, range 63806480)
- `OSOP_BACKEND_PORT` — FastAPI host port (auto-selected, default 8003, range 80038099)
- `OSOP_FRONTEND_PORT` — Vite dev server port (default 3011, dev profile only)
- `APP_PUBLIC_URL` — Public app URL (e.g., `https://optical-dev.oliver.solutions`, used in emails)
- `MAILGUN_API_KEY` — Mailgun key for approval notifications (required for Stage 3/14 emails)
- `MAILGUN_DOMAIN` — Mailgun domain
## API / Endpoints
**Key REST routes (backend):**
- `GET /api/opportunities` — List all opportunities
- `POST /api/opportunities` — Create new opportunity
- `GET /api/opportunities/{id}` — Get opportunity + stages
- `POST /api/opportunities/{id}/stages/{stage_num}/complete` — Advance to next stage
- `GET /api/opportunities/{id}/stages/{stage_num}/artifact` — Fetch stage output
- `POST /api/approvals` — Submit approval (gated stages)
- `GET /api/approvals?opportunity_id={id}` — List approvals for opportunity
- `GET /api/me` — Current user info
- `GET /api/health` — Health check
Key FastAPI endpoints (all under `/api/`):
**WebSocket (if applicable):**
- Real-time notifications (approval updates, stage completion)
- `GET /health` — Backend + DB health check
- `GET /users/me` — Current authenticated user
- `POST /opportunities` — Create new opportunity
- `GET /opportunities/{id}` — Fetch opportunity + all stages
- `GET /stages/{stage_id}` — Get stage detail + state
- `POST /stages/{stage_id}/run-agent` — Execute agent for a stage (intake, diagnosis, etc.)
- `POST /stages/{stage_id}/complete` — Mark stage complete (checks approval gates)
- `POST /approvals` — Create approval request for Stage 3 or 14
- `PATCH /approvals/{approval_id}` — Approve/reject
- `POST /opportunities/{id}/export-qa-pack` — Export Stage 4 Q&A as Excel/Word
## The 17 Stages (Reference)
| # | Stage | AI Agent | Output | Gated? |
|---|-------|----------|--------|--------|
| 1 | Intake | Intake Agent | Opportunity metadata (scope, team, budget) | No |
| 2 | Read & Diagnose Brief | Diagnosis Agent | Key findings, clarifications, risks | No |
| 3 | **Qualification Assessment** | Human + TROWLS form | Qualification score | **Yes** |
| 4 | Generate Client Q&A Pack | Pure-Python | Excel/Word export | No |
| 5 | Ingest Client Answers | Human edit | Updated JSON | No |
| 6 | Normalize Asset List | Asset Normalizer | Standardized asset taxonomy | No |
| 7 | Match Assets to Job Routes | Match Agent | Asset-to-route mappings + hours | No |
| 8 | Build Asset-Level Rate Card | Pure-Python (GMAL) | Hourly rates × volume → cost | No |
| 9 | Recommend Delivery Model | Delivery Model Agent | Model choice (FTE, SOW, hybrid) + rationale | No |
| 10 | Apply Efficiency Logic | Human (UI sliders) | Efficiency profile (cost savings %) | No |
| 11 | Create Draft Team Shape | Pure-Python (FTE calc) | Org chart, roles, headcount | No |
| 12 | Identify Capability Gaps | Capability Gap Agent | Gaps + training/hiring recommendations | No |
| 13 | Generate Support Docs | Support Docs Agent | SLAs, KPIs, governance, caveats | No |
| 14 | **Validation & Approval Gates** | Human review | Final sign-off | **Yes** |
| 15 | Build Pitch Materials | Pitch Deck Agent | Markdown + Claude-generated slides | No |
| 16 | Delivery Planning (post-win) | Implementation Plan Agent | Timeline, milestones, dependencies | No |
| 17 | Trigger Downstream Systems | ⏳ Phase 2 | Push to Salesforce / SharePoint | N/A |
Full spec: `docs/project/api_spec.md`
## Known Issues
- **Stage 17 deferred:** Salesforce/SharePoint integration planned for Phase 2.
- **Email in dev:** Set `MAILGUN_API_KEY=""` to log
- **Stage 7 (GMAL Matching):** Runs as FastAPI background task in Phase 1; no dedicated Celery worker deployed yet
- **Stage 17 (Salesforce/SharePoint):** Deferred to Phase 2; stub only
- **SSO:** Azure MSAL integration exists; fallback dev-auth-bypass for local development
- **File uploads:** Stored in Docker volume `/app/data/uploads/opp_<id>/`; no cloud bucket integration yet
## Documentation
Full architecture, requirements, database schema, and ADRs are in `docs/project/` and `docs/reference/`:
- `docs/project/architecture.md` — System design, state machine, security
- `docs/project/requirements.md` — Functional workflows per stage
- `docs/project/database_schema.md` — Table definitions + indexes
- `docs/project/infrastructure.md` — Ports, volumes, environment
- `docs/project/runbook.md` — Local setup, testing, deployment
- `docs/reference/README.md` — Architectural decision records
## Git
- **Remote:** `git@bitbucket.org:zlalani/oliver-sales-ops-platform.git`
- **Branch:** `main` (deployed to optical-dev)
- **Last commits:** User menu UX fixes, SSO allowlist, avatar + sign-out button, port auto-selection, Apache template deploy
## Sessions
### 2026-04-29 Add SPA redirect URI to Azure

View file

@ -1,12 +1,12 @@
---
name: "PDF Accessibility Checker"
client: Oliver
client: Oliver Internal
status: active
tech: [Python, PHP, JavaScript, PostgreSQL, Redis, Docker, Anthropic Claude, Google Cloud Vision]
tech: [Python, PHP, JavaScript, PostgreSQL, Redis, Docker, Claude API, Google Cloud Vision]
local_path: /Users/ai_leed/Documents/Projects/Oliver/pdf-accessibility
deploy: docker-compose up -d (production: docker-compose -f docker-compose.prod.yml up -d)
url: https://ai-sandbox.oliver.solutions/pdf-accessibility
server: optical-web-1
deploy: docker-compose up or docker-compose -f docker-compose.prod.yml up -d
url: http://localhost:8000
server: local
tags: [oliver, pdf, accessibility, wcag, ai, php, redis, postgresql]
created: 2026-04-14
last_commit: 2026-03-18
@ -16,167 +16,153 @@ db: PostgreSQL
---
## Overview
pdf-accessibility is an **AI-powered PDF accessibility validation system** that checks documents against WCAG 2.1 Level A & AA standards, achieving ~95% automated coverage. It combines traditional PDF analysis (pypdf, pdfplumber) with cutting-edge AI models (Anthropic Claude 3.5 Sonnet, Google Cloud Vision) to validate accessibility across 30+ criteria. The system serves enterprise users via a modern web UI with drag-and-drop uploads, a RESTful API with authentication, and a CLI tool for batch processing. Branded for Oliver with Montserrat font and black/#FFC407 color palette.
PDF Accessibility is an AI-powered validation tool that checks PDF documents against WCAG 2.1 Level A & AA standards using Claude 3.5 Sonnet for image/alt-text analysis and Google Cloud Vision for OCR. It combines traditional PDF analysis with machine learning to achieve ~95% automated coverage of accessibility requirements, offering auto-remediation, a visual page inspector, and three interfaces: Web UI, REST API, and CLI. Built for Oliver Internal, it enables organizations to audit and fix PDF accessibility issues at scale with minimal manual intervention.
## Tech Stack
- **Frontend:** Vanilla JavaScript, HTML5/CSS3 (drag-drop file upload, visual inspector, dark mode, responsive design)
- **Backend:** Python 3 (core engine), PHP (REST API and authentication layer)
- **Database:** PostgreSQL 16 (job tracking, audit logging, results storage)
- **Infrastructure:** Docker Compose (development & production stacks), Redis (job queue), Structured logging with rotation
- **AI/ML:** Anthropic Claude 3.5 Sonnet (image alt text validation), Google Cloud Vision (OCR, text-in-images detection)
- **Key libraries:** pypdf (PDF parsing), pdfplumber (text extraction), pdf2image (rasterization), Pillow (image processing), pytesseract (OCR), textblob (readability), weasyprint (PDF report generation), veraPDF (PDF/UA-1 validation)
- **Frontend:** Vanilla JavaScript + HTML5/CSS3, drag-drop UI with visual inspector, dark mode support
- **Backend:** PHP (REST API) + Python (core engine, worker daemon, CLI)
- **Database:** PostgreSQL 16 (jobs tracking, audit logs), Redis (job queue)
- **Infrastructure:** Docker Compose (dev & prod stacks), Azure AD/MSAL for SSO auth
- **AI/ML:** Anthropic Claude 3.5 Sonnet (image analysis, alt-text validation), Google Cloud Vision (OCR, text detection), veraPDF (PDF/UA-1 validation)
- **Key libraries:** pypdf, pdfplumber, Pillow, pdf2image, pytesseract, textblob, anthropic, google-cloud-vision
## Architecture
The system uses a **three-interface architecture** with a centralized asynchronous job queue backend:
The system comprises three parallel interfaces (Web UI, REST API, CLI) feeding into a unified core engine:
```
┌─────────────────────────────────────────────────────────────┐
│ Three User Interfaces │
├─────────────────────┬──────────────────┬────────────────────┤
│ Web UI │ REST API │ CLI │
│ (index.html) │ (api.php) │ (enterprise_pdf_ │
│ Vanilla JS │ PHP endpoints │ checker.py) │
│ Drag-drop │ Bearer/Key auth │ Direct Python │
└─────────────────────┴──────────────────┴────────────────────┘
┌──────────────┐
│ api.php │
│ (Router) │
└──────┬───────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────────┐ ┌──────────────┐
│ File │ │ Redis Queue │ │ PostgreSQL │
│ Upload │ │ (pdf:queue) │ │ (jobs, audit)│
│uploads/│ │ │ │ │
└────────┘ └──────┬───────┘ └──────────────┘
┌──────────────────────────────────────────────────────────────┐
│ THREE INTERFACES │
├──────────────────┬───────────────────────┬──────────────────┤
│ Web UI │ REST API │ CLI │
│ (index.html) │ (api.php) │ (Python direct) │
│ Drag-drop │ POST /upload │ python ...py │
│ Visual inspector │ GET /status │ --output JSON │
└────────┬─────────┴──────────┬────────────┴────────┬─────────┘
│ │ │
└────────────────────┼─────────────────────┘
┌──────────────────┐
│ worker.py │
│ (Daemon process) │
└──────┬───────────┘
┌──────────────────────────────┐
│ EnterprisePDFChecker │
│ • 30+ WCAG checks │
│ • AI image analysis (Claude) │
│ • OCR (GCV) │
│ • Contrast analysis │
│ • Readability metrics │
└──────┬───────────────────────┘
┌──────────────────┐
│ results/ │
│ {job_id}. │
│ result.json │
└──────────────────┘
│ Job Orchestration│
├──────────────────┤
│ auth.php │ Auth gate (Bearer/X-API-Key)
│ db_manager.py │ PostgreSQL ORM
│ redis_queue.py │ Job queue (pdf:queue)
└────────┬──────────┘
┌───────────────────────────────────┐
│ worker.py (Background Daemon) │
│ Pops jobs → Runs checks → Stores │
│ results/{job_id}.result.json │
└────────┬────────────────────────┘
╔═════════════════════════════════════════╗
║ enterprise_pdf_checker.py ║
║ CORE ENGINE: 30+ WCAG checks ║
╠═════════════════════════════════════════╣
│ 1. Metadata & structure (pypdf) │
│ 2. Text extraction & readability │
│ 3. Color contrast (Pillow analysis) │
│ 4. Image alt-text (Claude 3.5 Sonnet) │
│ 5. OCR for text-in-images (GCV) │
│ 6. Heading hierarchy & tagging │
│ 7. Form field labels │
│ 8. PDF/UA-1 validation (veraPDF) │
╚═════════════════════════════════════════╝
├─→ pdf_remediation.py (Auto-fix)
├─→ report_generator.py (Format results)
└─→ retry_helper.py (Exponential backoff)
```
**Request Flow (Production Docker Stack):**
1. User uploads PDF via web UI, REST API, or CLI
2. `api.php` validates auth (Bearer token or API key via `auth.php`), saves file to `uploads/`
3. Job created in PostgreSQL, queued to Redis (`pdf:queue`)
4. `worker.py` daemon (background process) pops job, invokes `EnterprisePDFChecker.check_all()`
5. All external API calls (Claude, GCV) wrapped with exponential backoff retry logic (`retry_helper.py`)
6. Results written to `results/{job_id}.result.json`, PostgreSQL updated with completion status
7. Client polls `api.php?action=status` for progress, fetches final results when ready
8. Automatic cleanup (`cleanup.py`) removes uploads after 24h, results after 30 days
**Data Flow (Production/Docker):**
1. Client uploads PDF via Web UI → `api.php` validates auth & saves to `uploads/`
2. Job pushed to Redis queue; PostgreSQL job record created with status `pending`
3. `worker.py` daemon polls Redis, pops job, runs `EnterprisePDFChecker.check_all()`
4. Results written to `results/{job_id}.result.json`; DB updated with status `complete`
5. Client polls `api.php?action=status` for progress; retrieves results when ready
6. Optional: User triggers `api.php?action=remediate` for auto-fix → new PDF created
**Key Source Files:**
| File | Purpose |
|------|---------|
| `enterprise_pdf_checker.py` | Core validation engine — 30+ WCAG checks, AI image analysis, scoring logic |
| `api.php` | REST API router — upload/check/status/result/remediate/download endpoints, CORS headers |
| `auth.php` | Authentication middleware — Bearer token, X-API-Key, dev mode localhost bypass |
| `worker.py` | Background daemon — Redis queue consumer, graceful shutdown on signals |
| `db_manager.py` | PostgreSQL ORM — jobs CRUD, audit logging, connection pooling |
| `redis_queue.py` | Redis operations — job enqueue/dequeue, status tracking, rate limiting |
| `pdf_remediation.py` | Auto-remediation — metadata fixing, language tagging, structural repairs |
| `retry_helper.py` | Exponential backoff — retries for Claude API, GCV API, transient failures |
| `report_generator.py` | Result formatting — JSON reports, HTML export, compliance summaries |
| `logger_config.py` | Structured logging — JSON output, file rotation (10MB max), syslog integration |
| `cleanup.py` | Scheduled task — file retention enforcement (24h uploads, 30d results) |
| `index.html` | Web UI root — loads CSS/JS, sets up drag-drop zone, result viewer |
| `js/app.js` | Frontend logic — API calls, progress polling, DOM updates, dark mode |
| `css/style.css` | Branding — Oliver palette (black, #FFC407), Montserrat font, responsive layout |
**Key Design Decisions:**
- **Async queue:** Redis + background worker allows long-running AI checks without blocking HTTP
- **PostgreSQL:** Persistent job history, audit logging, user isolation (MSAL integration)
- **Caching:** AI responses cached by content hash in `.cache/` to reduce API costs (~$0.015/image)
- **Graceful degradation:** CLI mode works standalone; API mode requires Docker stack
- **Environment-aware:** `DEV_MODE=true` bypasses auth for localhost; production requires valid keys
## Dev Commands
```bash
# Activate virtual environment
source venv/bin/activate
# Install dependencies
# Setup
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
brew install php tesseract poppler verapdf # macOS system deps
# Run all tests (31 automated tests)
pytest tests/ -v
# Run with coverage report
pytest tests/ --cov=. --cov-report=html
# Run single test file
pytest tests/test_checker.py -v
# Skip integration tests (faster local runs)
pytest tests/ -m "not integration"
# Start development server (PHP)
php -S localhost:8000
# CLI: Full accessibility check
python enterprise_pdf_checker.py document.pdf --output report.json
# CLI: Quick check (skip AI image analysis)
python enterprise_pdf_checker.py document.pdf --quick
# CLI: Auto-remediate all fixable issues
python pdf_remediation.py document.pdf --output fixed.pdf --all
# Development (PHP dev server + Python worker in separate terminals)
source venv/bin/activate
php -S localhost:8000 # Terminal 1: Start dev server
python worker.py # Terminal 2: Start background worker
# Docker development stack (all services)
docker-compose up
# Docker production stack
# Docker production stack (detached)
docker-compose -f docker-compose.prod.yml up -d
# Run tests inside Docker container
docker-compose exec worker pytest tests/ -v
# Testing
pytest tests/ -v # All 31 tests
pytest tests/ --cov=. --cov-report=html # With coverage (34% current)
pytest tests/test_checker.py -v # Single file
pytest tests/ -m "not integration" # Skip slow tests
# View worker logs
docker-compose logs -f worker
# CLI Usage (direct Python)
python enterprise_pdf_checker.py document.pdf --output report.json # Full check
python enterprise_pdf_checker.py document.pdf --quick # Skip AI (faster)
python pdf_remediation.py document.pdf --output fixed.pdf --all # Auto-remediate
```
## Deployment
- **Server:** optical-web-1
- **Deploy:**
- **Development:** `docker-compose up`
- **Production:** `docker-compose -f docker-compose.prod.yml up -d` OR via `deploy.sh` (runs git pull, restarts Apache)
- **Manual:** Push to `git@bitbucket.org:zlalani/pdf-accessibility.git` main branch; server auto-deploys via webhook
- **URL:** https://ai-sandbox.oliver.solutions/pdf-accessibility
- **Port:** 8000 (development), 80/443 (production via Apache reverse proxy)
- **Service:** None (Docker Compose manages container lifecycle; Apache may use systemd)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/pdf-accessibility
- **Server:** Local development (can deploy to optical-web-1 or Cloud Run)
- **Deploy:** `docker-compose -f docker-compose.prod.yml up -d` (Docker) or `bash deploy.sh` (Git-based)
- **URL:** `http://localhost:8000` (dev) | Production URL in `.env` (CLOUD_RUN_URL)
- **Port:** 8000 (HTTP), 1220 (Redis in prod), 1221 (PostgreSQL in prod)
- **Service:** None (containerized); can be wrapped in systemd if needed
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/pdf-accessibility`
**Production Notes:**
- Set `DEV_MODE=false` before deploying
- Rotate default API key `dev_key_12345` → generate secure key
- Store `ANTHROPIC_API_KEY` and `GOOGLE_API_KEY` in secrets manager
- MongoDB/backup strategy for PostgreSQL recommended
- veraPDF requires Java; included in Docker image
## Environment Variables
All configured in `.env` (copy from `.env.example`):
- `ANTHROPIC_API_KEY` — Anthropic Claude API key (required; get from https://console.anthropic.com/)
- `GOOGLE_API_KEY` — Google Cloud Vision API key (optional; for OCR and text-in-images detection)
- `GOOGLE_APPLICATION_CREDENTIALS` — Path to GCP service account JSON (alternative to API key)
- `DEV_MODE` — Set to `true` for localhost auth bypass (development only)
- `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` — PostgreSQL connection (docker-compose provides defaults)
- `CLOUD_RUN_URL` — Optional Cloud Run service URL for distributed PDF processing; defaults to local Python
- `GCP_SA_KEY_PATH` — Path to GCP service account key for Cloud Run authentication
- `GCS_BUCKET_NAME` — Google Cloud Storage bucket for page images (default: `optical-pdf-images`)
- `RETENTION_HOURS` — Keep uploaded PDFs for N hours before deletion (default: 24)
- `RESULTS_RETENTION_HOURS` — Keep result/meta JSON for N hours before deletion (default: 720 = 30 days)
- `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_
- `ANTHROPIC_API_KEY` — Anthropic Claude API key (required for AI image analysis). Get from https://console.anthropic.com/
- `GOOGLE_API_KEY` — Google Cloud Vision API key (optional; for enhanced OCR). Alternative: `GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json`
- `GOOGLE_CLOUD_PROJECT` — GCP project ID (if using service account file)
- `DEV_MODE=true|false` — If `true`, localhost bypasses authentication. **Never enable in production.**
- `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` — PostgreSQL connection (Docker: use service name `postgres`)
- `REDIS_URL` — Redis connection string (Docker: `redis://redis:6379`)
- `RETENTION_HOURS` — Delete uploaded PDFs after N hours (default 24)
- `RESULTS_RETENTION_HOURS` — Keep result JSON files for N hours (default 720 = 30 days)
- `CLOUD_RUN_URL` — Deployed Cloud Run endpoint for remote PDF processing
- `GCP_SA_KEY_PATH` — Path to GCP service account key JSON
- `GCS_BUCKET_NAME` — Google Cloud Storage bucket for page images
- `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_REDIRECT_URI` — Azure AD (MSAL) SSO configuration
## API / Endpoints
**REST API (`api.php` base URL: `http://localhost:8000`)**
| Method | Endpoint | Auth | Body/Params | Returns |
|--------|----------|------|-------------|---------|
| POST | `/api.php` (action=upload) | Bearer/X-API-Key | `file` (multipart) | `{"job_id": "...", "status": "pending"}` |
| GET | `/api.php?action=status&job_id=...` | Bearer/X-API-Key | N/A | `{"status": "processing\|complete\|failed", "progress": 45}` |
| GET | `/api.php?action=result&job_id=...` | Bearer/X-API-Key | N/A | `{"result": {...}}` (full WC
## Timeline / Git History
| Date | Change |

View file

@ -2,27 +2,152 @@
name: "PIMCO Chart Generator"
client: PIMCO
status: active
server: optical-web-1
tech: [Python, FastAPI, Docker, SVG, Claude API]
server: ai-sandbox.oliver.solutions
tech: [Python, FastAPI, Claude Opus, SVG/PNG/PDF, Azure AD, Playwright, Pydantic]
local_path: /Users/ai_leed/Documents/Projects/Oliver/pimco-charts
deploy: docker compose up --build
url:
deploy: Docker Compose
url: https://ai-sandbox.oliver.solutions/Pimco-charts
tags: [pimco, charts, svg, ai, publication-quality]
created: 2026-04-14
port: 8569
---
## Overview
Publication-quality SVG chart generator matching PIMCO's InDesign visual style. Upload Excel/CSV + plain-English description → pixel-perfect SVG. Iterate with natural language commands.
**pimco-charts** is a FastAPI web application that transforms Excel/CSV data and natural-language briefs into publication-quality financial charts matching PIMCO's InDesign visual style. Users upload data, describe their chart in plain English, and Claude Opus 4.6 generates a structured ChartSpec JSON that the renderer converts to pixel-perfect SVG, with optional PNG/PDF export via Playwright. The system supports iterative refinement—users can ask Claude to adjust colors, fonts, axes, or data series using natural language until the chart is production-ready.
## Tech Stack
- **Backend:** Python + FastAPI
- **AI:** Claude API
- **Output:** SVG
- **Infrastructure:** Docker + docker-compose
- **Frontend:** HTMX + Jinja2 templates, client-side MSAL.js for Azure AD SSO
- **Backend:** FastAPI + Uvicorn, Python 3.x
- **Database:** None (in-memory session dict; stateless design)
- **Infrastructure:** Docker Compose, Playwright for SVG→PNG/PDF conversion
- **AI/ML:** Anthropic Claude Opus 4.6 (tool-use via JSON schema for ChartSpec generation)
- **Key libraries:** pandas, openpyxl (data loading), pydantic (schema validation), drawsvg (SVG generation), python-dotenv (config)
## Architecture
The application follows a **data → AI → validation → render** pipeline:
1. **Data Loading** (`app/data/loader.py`): Accepts Excel/CSV uploads, parses with pandas/openpyxl, and generates a summary (column names, types, value ranges).
2. **Brief Interpretation** (`app/ai/brief_interpreter.py`): Sends the data summary + plain-English brief to Claude Opus 4.6 with a detailed system prompt (`app/ai/prompts.py`) that includes PIMCO style guidelines, ChartSpec JSON schema, and few-shot examples. Claude outputs a structured ChartSpec via tool use.
3. **Validation & Column Matching** (`app/models/chart_spec.py`): Pydantic validates the ChartSpec; fuzzy matching resolves user-provided column names to actual data columns.
4. **SVG Rendering** (`app/renderer/engine.py`): Deterministic Python code (no AI) renders the ChartSpec into SVG:
- Layouts (single/dual Y-axis, stacked)
- Axes, grids, ticks
- Series glyphs (lines, bars, filled areas)
- Legend, annotations, typography
- Embeds Roboto font as base64 for portability
5. **Export** (`app/renderer/svg_export.py`): Uses Playwright + Chromium to convert SVG → PNG/PDF on demand.
6. **Session Management** (`app/main.py`): In-memory `_sessions` dict stores user's data and ChartSpec for the current session; lost on restart.
**Style Constants** (`app/models/style.py`): Centralized PIMCO colors, font sizes, margins, and layout defaults.
**Authentication** (`app/auth/`): Azure AD SSO via MSAL.js (browser-side PKCE flow); JWT validation on protected routes.
```
┌─────────────────────────────────────────────────────────────────┐
│ PIMCO CHART GENERATOR FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. User Upload 2. AI Interpretation │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ Excel/CSV │──────────▶ │ Data Summary │ │
│ │ + Brief Text │ │ + Claude │ │
│ └────────────────┘ │ Opus Prompt │ │
│ └────────┬────────┘ │
│ │ │
│ 3. Validation 4. Deterministic Render │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ ChartSpec JSON │◀─────────│ SVG Renderer │ │
│ │ Fuzzy match │ │ (layout, axes, │ │
│ │ columns │ │ series, fonts) │ │
│ └────────────────┘ └──────┬──────────┘ │
│ │ │
│ 5. Export (Optional) 6. Iterate │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ Playwright │ │ "Make lines │ │
│ │ SVG→PNG/PDF │ │ thicker..." │ │
│ └────────────────┘ │ Re-prompt Claude│ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Dev Commands
```bash
# Setup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
playwright install chromium
# Configuration
cp .env.example .env
# Edit .env: add ANTHROPIC_API_KEY and SESSION_SECRET_KEY
# Run locally
uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload
# Docker
docker-compose up --build
# Generate SESSION_SECRET_KEY
python -c "import secrets; print(secrets.token_hex(32))"
```
## Deployment
- **Run:** `docker compose up --build`
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/pimco-charts`
- **Server:** ai-sandbox.oliver.solutions (path: `/Pimco-charts`)
- **Deploy:** Docker Compose (`docker-compose up -d`)
- **URL:** https://ai-sandbox.oliver.solutions/Pimco-charts
- **Port:** 8569 (internal); routed via reverse proxy
- **Service:** Docker container `pimco-charts`
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/pimco-charts
Health check runs every 30s via `curl http://localhost:8569/`.
## Environment Variables
- `ANTHROPIC_API_KEY` — Anthropic API key for Claude Opus 4.6 calls; required for AI brief interpretation
- `AZURE_TENANT_ID` — Azure AD tenant ID (`e519c2e6-bc6d-4fdf-8d9c-923c2f002385`); used for SSO
- `AZURE_CLIENT_ID` — Azure AD app registration client ID (`9079054c-9620-4757-a256-23413042f1ef`)
- `AZURE_REDIRECT_URI` — OAuth callback URL (`https://ai-sandbox.oliver.solutions/Pimco-charts`); must match Azure registration
- `SESSION_SECRET_KEY` — Random 32-byte hex string for session signing; generate with `python -c "import secrets; print(secrets.token_hex(32))"`; **never hard-code**
## API / Endpoints
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/` | Home page (requires auth) |
| `POST` | `/upload` | Receive Excel/CSV file and brief text |
| `POST` | `/generate` | Send data summary + brief to Claude; return ChartSpec |
| `POST` | `/refine` | Accept natural-language adjustment; re-prompt Claude with prior ChartSpec |
| `GET` | `/chart/svg` | Render current ChartSpec as SVG |
| `GET` | `/chart/png` | Export SVG to PNG via Playwright |
| `GET` | `/chart/pdf` | Export SVG to PDF via Playwright |
| `GET` | `/auth/login` | Redirect to MSAL login (Azure AD) |
| `GET` | `/auth/callback` | OAuth callback; validates JWT and sets session |
| `GET` | `/static/*` | Serve CSS/JS (exempted from auth) |
## Known Issues
- **In-memory sessions lost on restart:** No persistence layer; users' charts disappear if the container restarts. Consider adding Redis or database persistence for production.
- **Playwright Chromium required:** SVG→PNG/PDF export fails if `playwright install chromium` is not run; no graceful fallback.
- **No SSH access rule:** Hard constraint from client; all server interactions via HTTP/Docker only.
- **Canvas default 2560×1440px:** Non-configurable in some endpoints; may need aspect-ratio tweaks for mobile/web viewing.
- **Font embedding:** Roboto font is base64-encoded in every SVG; increases file size; consider external CDN for large batches.
## Git
- **Remote:** git@bitbucket.org:zlalani/pimco-charts.git
- **Branch:** main (implied)
- **Last commit:** d52f088 (Fix bar charts, fonts, axis controls, donut support, and Playwright export)
## Sessions
### 2026-04-28 Fix bar chart rendering failures and
@ -55,4 +180,4 @@ Publication-quality SVG chart generator matching PIMCO's InDesign visual style.
| 2026-04-28 | Chart fixes | Bar chart rendering, axis controls application, legend and gridline rendering | Chart.tsx, ChartRenderer.tsx, AxisControls.tsx |
| 2026-04-28 | Export backend | New Playwright renderer, font handling, removed cairosvg dependency | app/renderer/export.py, app/main.py |
| 2026-04-28 | Export backend | Add Playwright renderer, remove cairosvg, fix font handling and stroke drift | app/renderer/export.py, app/main.py, and 12 other files |
| 2026-04-14 | Initial setup | Note created | — |
| 2026-04-14 | Initial setup | Note created | — |

View file

@ -1,196 +1,208 @@
---
name: "Oliver DeckForge"
client: Oliver
client: Oliver Internal / Multi-tenant
status: active
server: optical-dev
tech: [Next.js 14, FastAPI, PostgreSQL, Redis, Docker, React, Python, Puppeteer]
tech: [Next.js 14, FastAPI, PostgreSQL, Redis, SQLModel, Anthropic/OpenAI/Google APIs, Puppeteer]
local_path: /Users/ai_leed/Documents/Projects/Oliver/ppt-tool
deploy: make dev
deploy: make dev (local) | Apache2 reverse proxy (prod)
url: https://optical-dev.oliver.solutions/ppt-tool/
tags: [oliver, presentation, ai, nextjs, multi-tenant, rbac]
created: 2026-04-14
port: 80
db: PostgreSQL
port: 80/443
db: PostgreSQL 16
---
## Overview
**ppt-tool** (Oliver DeckForge) is an AI-powered enterprise presentation generator with multi-tenant architecture supporting custom template upload, role-based access control, and SSO authentication. Users upload documents, URLs, or topics and receive fully formatted slide decks in PPTX/PDF format. The system uses Claude/Gemini for content generation, Puppeteer for exports, and provides admin panels for user/client/team management with audit logging and analytics.
**ppt-tool** is an AI-powered enterprise presentation generator that transforms user-provided topics, URLs, or documents into complete branded PPTX/PDF slide decks. It serves Oliver Internal and multi-tenant Oliver Agency clients with features including Azure AD SSO, RBAC, client isolation, review workflows, and custom master deck template support. The system combines LLM-driven content generation with enterprise-grade security, image generation, and export capabilities.
## Tech Stack
- **Frontend:** Next.js 14 (App Router), TypeScript, Redux Toolkit, Shadcn UI, react-i18next
- **Backend:** FastAPI, SQLModel (SQLAlchemy ORM), Python 3.10+, Alembic (migrations)
- **Database:** PostgreSQL 16 with asyncpg driver
- **Infrastructure:** Docker Compose (6 services), nginx reverse proxy, shared app_data volume
- **Job Queue:** arq (async Redis queue) for background AI generation tasks
- **AI/ML:** Google Gemini 3.1 Pro (presentations), Gemini 2.5 Flash Lite (parsing), Anthropic Claude (fallback), OpenAI (fallback), Ollama (local)
- **Image Generation:** Gemini Flash (primary), Pexels/Pixabay APIs (fallback)
- **Export:** Puppeteer (headless Chromium) for PDF/PPTX generation
- **Key libraries:** pptx (python-pptx), PyMuPDF (fitz), sqlalchemy, fastapi, alembic, arq, msal.js (Azure AD)
- **Frontend:** Next.js 14 (App Router), React, Redux Toolkit, Shadcn UI, TypeScript
- **Backend:** FastAPI, SQLModel (ORM), Alembic (migrations), arq (async queue), Python 3.11
- **Database:** PostgreSQL 16
- **Infrastructure:** Docker Compose, Apache2 reverse proxy, nginx (dev only)
- **AI/ML:** Anthropic Claude, OpenAI GPT, Google Gemini, multi-provider image generation
- **Key libraries:** python-pptx, PyMuPDF, Puppeteer (PDF export), MSAL (Azure AD), aiohttp
## Architecture
**ppt-tool** follows a three-tier microservices architecture:
```
┌─────────┐
│ nginx │ :80
└────┬────┘
┌─────┴─────────────┐
┌────┴───┐ ┌────┴────┐
│ Next.js │ │ FastAPI │
│ (web) │ │ (api) │
│ :3000 │ │ :8000 │
└────┬────┘ └───┬─────┘
│ │
│ ┌──────┴──────┐
│ │ │
│ ┌───┴───┐ ┌────┴─────┐
│ │Worker │ │Postgres │
│ │(arq) │ │ :5432 │
│ └───┬───┘ └──────────┘
│ │
│ ┌───┴──────┐
│ │ Redis │
│ │ :6379 │
│ └──────────┘
└─────────────────────── app_data volume
(shared storage)
┌─────────────────────────────────────────────┐
│ Apache2 Reverse Proxy (Production) │
│ /ppt-tool/* → web:3000 | /api/* → api:8000
└────────────────┬────────────────────────────┘
┌───────┴───────┐
┌────┴─────┐ ┌─────┴────┐
│ Next.js │ │ FastAPI │
│ (web:3000)│ │ (api:8000)
└────┬─────┘ └────┬─────┘
│ │
│ ┌────┴──────┐
│ ┌────┴──┐ ┌─────┴──────┐
└────│ PostgreSQL │ Redis 7 │
│ (5432) │ (6379) │
└─────────────┴──────────────┘
```
**Service breakdown:**
- **web (Next.js):** SPA frontend with Puppeteer-based local PDF/PPTX export, Redux state, SSE for real-time updates
- **api (FastAPI):** REST API (v1), auth/RBAC, SSE streaming endpoints, template parsing, client/user management
- **worker (arq):** Consumes Redis jobs from API, runs AI generation tasks in background, writes results to app_data
- **postgres:** Primary data store for users, clients, presentations, slides, templates, audit logs
- **redis:** Job queue for arq worker tasks, caching layer
- **nginx:** Routes /api/v1/* → FastAPI, / → Next.js, serves static app_data files
**Component breakdown:**
**Data flow:** User creates presentation → API enqueues arq job → Worker generates slides via Claude/Gemini → Results stored in app_data + DB → Frontend polls SSE or fetches results → User exports via Puppeteer (web) or downloads from API.
| Component | Role |
|-----------|------|
| **web (Next.js 14)** | React SPA with Redux state, Shadcn UI components, PDF export via Puppeteer API route |
| **api (FastAPI)** | REST API (routers in `backend/api/v1/`), auth (JWT + Azure AD), RBAC, SSE streaming for job progress |
| **worker (arq)** | Background async job processor running on Redis queue — handles long-running LLM calls and content generation |
| **postgres** | Primary database: users, clients, presentations, templates, OAuth tokens, generated metadata |
| **redis** | Job queue for arq + pub/sub for real-time progress streaming |
**Core data flow (presentation generation pipeline):**
1. **Input** → Frontend posts topic/URL/files to `/api/v1/ppt/presentation/create`
2. **Summarization** → LLM condenses raw content into structured sections
3. **Outline generation** → LLM generates per-slide outlines (title + bullets) via streaming
4. **Layout selection** → LLM/rules map each outline to a slide layout template
5. **Slide content generation** → Concurrent batches of 10 slides — LLM fills layout fields with content
6. **Image generation** → Optional AI images inserted per slide
7. **Export** → python-pptx creates PPTX; Puppeteer converts to PDF
8. **Storage** → Files saved to `app_data/exports/`; references stored in database
**Backend layers:**
| Layer | Path | Purpose |
|-------|------|---------|
| Routers | `backend/api/v1/` | FastAPI route definitions, request validation |
| Services | `backend/services/` | Business logic: auth, image gen, brand enforcement, PPTX creation |
| Workers | `backend/workers/` | arq task definitions for background jobs |
| Models (SQL) | `backend/models/sql/` | SQLModel ORM table definitions |
| Models (Pydantic) | `backend/models/` | Request/response schemas |
| Utils | `backend/utils/` | LLM wrappers, export helpers, asset management |
**Key services:**
- `AuthService` → Azure AD MSAL + dev-bypass JWT creation
- `ImageGenerationService` → Multi-provider image gen (Anthropic, OpenAI, Google)
- `BrandEnforcementService` → Per-client brand context injection for LLM prompts
- `PptxPresentationCreator` → python-pptx slide generation and layout enforcement
## Dev Commands
```bash
# Start all services (build + up)
# Clone and setup (first time)
cd /Users/ai_leed/Documents/Projects/Oliver/ppt-tool
cp .env.example .env
# Edit .env: set ANTHROPIC_API_KEY, JWT_SECRET_KEY, DEV_AUTH_PASSWORD
# Start all services (Docker, recommended)
make dev
# Build images only (no start)
make build
# Start containers in background
make up
# Stop all containers
make down
# Run database migrations
# Run database migrations (first time)
make migrate
# Seed database with sample data
# Seed initial data (first time)
make seed
# Run API tests
make test
# Open app
open http://localhost
# Run E2E tests (Cypress)
make test-e2e
# Run all tests
make test-all
# View logs from all services
# View logs
make logs
# Open bash shell in API container
# Stop services
make down
# Backend tests
make test
# E2E tests (Cypress)
make test-e2e
# Shell into API container
make shell-api
# Open psql shell in Postgres container
# Connect to database
make shell-db
# Fast code-only update (skips rebuild)
./update.sh
# Database operations
make shell-api
alembic upgrade head # Run migrations
alembic revision --autogenerate -m "message" # Create migration
alembic downgrade -1 # Rollback
```
**Local access:**
- App: http://localhost (nginx) or http://localhost:3000 (direct)
- API: http://localhost:8000
- Postgres: localhost:5432
- Redis: localhost:6379
**Without Docker:**
```bash
# Backend
cd backend
python3.11 -m venv venv
source venv/bin/activate
pip install -e .
export DATABASE_URL="postgresql+asyncpg://deckforge:deckforge@localhost:5432/deckforge"
export REDIS_URL="redis://localhost:6379/0"
export JWT_SECRET_KEY="..."
uvicorn api.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev # Proxies /api/v1/* to http://localhost:8000
```
## Deployment
- **Server:** optical-dev (optical-dev.oliver.solutions)
- **Deploy:** `make dev` (local) or custom Docker stack (production uses subpath `/ppt-tool/`)
- **URL:** https://optical-dev.oliver.solutions/ppt-tool/
- **Port:** 80 (nginx) / 8000 (API internal) / 3000 (Next.js internal)
- **Service:** None (Docker Compose manages services)
- **Server:** optical-dev.oliver.solutions
- **Deploy:** Apache2 reverse proxy (`apache/deckforge.conf`); Docker services managed via systemd or manual `docker compose` on host
- **URL:** `https://optical-dev.oliver.solutions/ppt-tool/`
- **Port:** 80 (HTTP) / 443 (HTTPS, via Apache); internal services: api:8000, web:3000, postgres:5432, redis:6379
- **Service:** Manual deployment; use `./update.sh` for fast code-only pushes or rebuild full stack with `docker compose up`
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/ppt-tool
**Deployment notes:**
- App runs under subpath `/ppt-tool/` on production (basePath hardcoded in Next.js config)
- Docker build args pass Azure AD credentials: `AZURE_AD_TENANT_ID`, `AZURE_AD_CLIENT_ID`
- Shared `app_data` volume persists uploads, exports, and generated files across containers
- Alembic migrations auto-run on API startup (or `make migrate`)
**Apache proxy rules:**
| Rule | Target |
|------|--------|
| `/ppt-tool/api/v1/` | http://127.0.0.1:API_PORT/api/v1/ (basePath stripped) |
| `/ppt-tool/` | http://127.0.0.1:WEB_PORT/ppt-tool/ (basePath intact) |
| `/ppt-tool/app_data/`, `/ppt-tool/static/` | FastAPI static serve |
**Key Apache settings:** SSE enabled (no-gzip, proxy-sendchunked), 100 MB upload limit, security headers (X-Content-Type-Options, X-Frame-Options, etc.).
## Environment Variables
**Auth & Security:**
- `AZURE_AD_TENANT_ID` — Azure AD tenant ID (leave blank for dev bypass mode)
- `AZURE_AD_CLIENT_ID` — Azure AD app registration client ID
- `AZURE_AD_CLIENT_SECRET` — Azure AD app registration secret
- `AZURE_AD_REDIRECT_URI` — OAuth2 callback URL (e.g., https://yourdomain.com/api/v1/auth/callback)
- `JWT_SECRET_KEY` — Secret for signing JWT tokens (must be 256-bit, change in production)
- `DEV_AUTH_PASSWORD` — Password for dev auth bypass (only used when Azure AD is disabled)
- `ALLOWED_ORIGINS` — CORS whitelist (comma-separated; local: http://localhost:3000,http://localhost)
**AI & Models:**
- `GOOGLE_API_KEY` — Google AI (Gemini) API key from https://aistudio.google.com/app/apikey
- `GOOGLE_MODEL` — Primary model for presentation generation (default: gemini-3.1-pro-preview)
- `PARSING_MODEL` — Fast model for master deck parsing (default: gemini-2.5-flash-lite)
- `IMAGE_PROVIDER` — Image generation provider (default: gemini_flash)
- `PEXELS_API_KEY` — Fallback image source (optional)
- `PIXABAY_API_KEY` — Fallback image source (optional)
- `DISABLE_IMAGE_GENERATION` — Set to "true" to disable all image generation
**Database & Storage:**
- `POSTGRES_PASSWORD` — Postgres user password (default: deckforge)
- `DATABASE_URL` — Full connection string (auto-set in docker-compose)
- `REDIS_URL` — Redis connection string (default: redis://redis:6379/0)
- `APP_DATA_DIRECTORY` — Path for uploads/exports (default: /app_data)
- `TEMP_DIRECTORY` — Temp file path (default: /tmp/deckforge)
**Connection Pooling:**
- `DB_POOL_SIZE` — SQLAlchemy pool size (default: 20)
- `DB_MAX_OVERFLOW` — Max overflow connections (default: 40)
- `DB_POOL_RECYCLE` — Recycle connections after N seconds (default: 3600)
**App Settings:**
- `CAN_CHANGE_KEYS` — Allow runtime API key updates (default: false)
- `DISABLE_ANONYMOUS_TRACKING` — Disable telemetry (default: true)
- `MAX_REQUEST_SIZE` — Max upload size in bytes (default: 104857600 = 100MB)
| Variable | Description |
|----------|-------------|
| `POSTGRES_PASSWORD` | PostgreSQL password (default: `deckforge`) |
| `DATABASE_URL` | Full asyncpg connection URL; auto-derived if not set |
| `DB_POOL_SIZE` | Connection pool size (default: 20) |
| `REDIS_URL` | Redis connection URL (default: `redis://localhost:6379/0`) |
| `JWT_SECRET_KEY` | **Required** — Random 256-bit string for JWT signing |
| `DEV_AUTH_PASSWORD` | **Required** — Bypass auth password for dev login (any email) |
| `ANTHROPIC_API_KEY` | Anthropic Claude API key |
| `OPENAI_API_KEY` | OpenAI API key for GPT models |
| `GOOGLE_API_KEY` | Google Gemini API key |
| `AZURE_AD_CLIENT_ID` | Azure AD app registration client ID |
| `AZURE_AD_TENANT_ID` | Azure AD tenant ID |
| `PUPPETEER_EXECUTABLE_PATH` | Path to Chromium/Chrome for PDF export |
## API / Endpoints
**Core endpoints** (base: http://api:8000/api/v1):
- `POST /auth/callback` — Azure AD OAuth2 callback
- `POST /presentations` — Create new presentation
- `GET /presentations` — List user presentations
- `GET /presentations/{id}` — Get presentation details
- `POST /presentations/{id}/export` — Export to PDF/PPTX
- `GET /presentations/{id}/events` — SSE stream for real-time generation updates
- `POST /templates` — Upload master PPTX deck
- `GET /templates` — List templates
- `POST /clients` — Create client (admin only)
- `GET /users` — List users (admin/client admin)
- `POST /audit-logs` — Fetch audit logs (admin only)
**Key endpoints (base: `/api/v1/`):**
**Frontend internal communication:**
- API calls use `apiFetch()` helper (prefixes `/ppt-tool/api/v1/` basePath for subpath deployment)
- SSE via EventSource with URL: `/ppt-tool/api/v1/presentations/{id}/events`
## Known Issues
- **Azure AD:** Dev bypass mode requires manual password; change `DEV_AUTH_PASSWORD` in `.env` or enable proper Azure AD config
- **Image generation:** Gemini image generation occasionally fails; fallback to Pexels/Pixabay if needed (set API keys)
- **PyMuPDF text extraction:** Some complex PDFs may not extract cleanly; fallback to DOCX upload recommended
- **Large
| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/auth/login` | Dev/Azure AD login — returns JWT |
| POST | `/ppt/presentation/create` | Create presentation from topic/URL/files |
| GET | `/ppt/presentation/{id}` | Fetch presentation metadata + progress |
| GET | `/ppt/presentation/{id}/stream` | SSE stream for real-time generation progress |
| POST | `/ppt/presentation/{id}/regenerate-slide` | Regenerate single slide content |
| GET | `/ppt/presentation/{id}/export/{format}` | Download PPTX or PDF |
| POST | `/clients/{client_id}/master-decks` | Upload custom
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,187 +1,226 @@
---
name: "Presenton"
client: Oliver Internal
client: Open-source (presenton.ai)
status: active
tech: [Node.js, Python, Docker, OpenAI API, Gemini, React, Tailwind CSS]
tech: [Python 3.11, FastAPI, Next.js 14, SQLModel, python-pptx, OpenAI/Google/Anthropic LLMs, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/presenton
deploy: docker-compose up
url:
deploy: docker compose up production
url: http://localhost:5000
tags: [oliver, presentation, ai, open-source]
created: 2026-04-14
server: local
port: 5000
db: PostgreSQL
db: SQLite
---
## Overview
Presenton is an open-source AI presentation generator that creates professional presentations locally on your device using AI models. It serves as an alternative to Gamma, Beautiful AI, and Decktopus, offering users complete control over their data and privacy. The platform supports multiple LLM providers (OpenAI, Google Gemini, Anthropic Claude, Ollama) and image generation sources (DALL-E 3, Gemini Flash, Pexels, Pixabay, ComfyUI), allowing users to generate presentations from prompts, documents, or existing PPTX templates. Key capabilities include custom HTML/Tailwind CSS templates, PPTX/PDF export, MCP server integration, and flexible deployment options.
Presenton is an open-source, self-hosted AI presentation generator that transforms prompts, documents, or existing PPTX files into fully designed presentations (PPTX/PDF format). It supports multiple LLM providers (OpenAI, Google, Anthropic, Ollama, custom endpoints) and image providers (DALL-E, Gemini, Pexels, ComfyUI), includes a built-in MCP server for AI agent integration, and exposes a REST API for programmatic access. The project is production-ready, actively maintained, and Apache 2.0 licensed.
## Tech Stack
- **Frontend:** React, Tailwind CSS, HTML5
- **Backend:** Node.js, Python
- **Database:** PostgreSQL
- **Infrastructure:** Docker, Docker Compose (with optional GPU support via NVIDIA)
- **AI/ML:** OpenAI API, Google Gemini, Anthropic Claude, Ollama (local models), custom OpenAI-compatible endpoints, ComfyUI
- **Key libraries:** Model Context Protocol (MCP), various image generation APIs (DALL-E 3, Gemini Flash, Pexels, Pixabay, NanoBanana)
- **Frontend:** Next.js 14 (App Router), React, Redux Toolkit, TypeScript
- **Backend:** FastAPI (Python 3.11), SQLModel (ORM), uvicorn
- **Database:** SQLite (default, can override with PostgreSQL/MySQL via `DATABASE_URL`)
- **Infrastructure:** Docker (single container), nginx reverse proxy, LibreOffice (PDF export), Puppeteer/Chromium
- **AI/ML:** OpenAI (GPT-4, DALL-E, GPT-Image-1.5), Google Gemini, Anthropic Claude, Ollama (local models), custom OpenAI-compatible endpoints
- **Key libraries:** python-pptx (PPTX generation), docling/pdfplumber (document extraction), fastmcp (MCP server), Pillow (image processing)
## Architecture
Presenton follows a containerized microservices approach with Docker Compose orchestration:
**Core Components:**
1. **Frontend UI** — React-based web interface for users to input prompts, upload documents/PPTX files, and configure settings
2. **Backend API** — Node.js/Python API service that orchestrates presentation generation
3. **LLM Integration Layer** — Abstracts multiple LLM providers (OpenAI, Gemini, Claude, Ollama, custom endpoints)
4. **Template Engine** — Loads and processes HTML/Tailwind CSS templates; can generate templates from existing PPTX files
5. **Image Generation Layer** — Multi-provider image generation (DALL-E 3, Gemini Flash, Pexels, Pixabay, ComfyUI, NanoBanana)
6. **Export Engine** — Converts generated presentations to PPTX and PDF formats
7. **MCP Server** — Built-in Model Context Protocol server for generating presentations programmatically
8. **Database Layer** — PostgreSQL for persistence (user settings, templates, generation history)
Presenton runs as a single Docker container with four coordinated processes spawned by `start.js`:
- **nginx** (port 80) — reverse proxy and static file server
- **FastAPI** (port 8000) — backend REST API and core logic
- **Next.js** (port 3000) — frontend SSR
- **MCP Server** (port 8001) — AI agent integration via Claude Desktop
- **Ollama** (port 11434) — optional local LLM serving
**Data Flow:**
User Input (prompt/document/PPTX) → Backend API → LLM Provider → Content Generation → Image Generation → Template Rendering → PPTX/PDF Export → User Download
All processes are internally networked; external access is exclusively through nginx on port 80 (mapped to host port 5000).
**Deployment Options:**
- Development: `docker-compose` with Dockerfile.dev (hot reload)
- Production: `docker-compose` with Dockerfile (optimized image)
- GPU-enabled: Production service with NVIDIA GPU reservation
- API Deployment: Can be hosted as a standalone service for team use
### Presentation Generation Pipeline
```
┌─────────────┐
│ Frontend │ (React/Tailwind)
└──────┬──────┘
┌─────────────────────────────────┐
│ Backend API (Node.js/Python) │
│ ┌──────────────────────────┐ │
│ │ LLM Abstraction Layer │ │
│ │ (OpenAI/Gemini/Claude) │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Template Engine + Parser │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Image Generation Layer │ │
│ │ (DALL-E/Gemini/etc) │ │
│ └──────────────────────────┘ │
└──────┬───────────────────────────┘
├─► PostgreSQL (persistence)
└─► PPTX/PDF Export
User Input (prompt/files/PPTX)
[Outline Generation] — LLM streams slide outlines (SSE)
[Structure Assignment] — LLM assigns layout index per slide
[Slide Content] — Batch of 10 slides: content generation
[Asset Fetching] — Images fetched/generated concurrently per slide
[Export] — python-pptx builds PPTX; LibreOffice converts to PDF
[Webhook] — presentation.generation.completed event fired
```
### Data Model
- **PresentationModel** — metadata (title, template, status, content type)
- **SlideModel[]** — per-slide content (outline, title, bullet points, speaker notes, layout index)
- **ImageAsset[]** — images per slide (URL, provider, generation prompt, alt text)
All persisted in SQLite at `/app_data/fastapi.db`.
## Dev Commands
### FastAPI Backend
```bash
# Start development server (with hot reload)
docker-compose up development
cd servers/fastapi
# Start production server (CPU)
docker-compose up production
# Install uv package manager
pip install uv
# Start production server (with GPU support)
docker-compose up production-gpu
# Install dependencies
uv sync
# Build Docker image
docker build -t presenton:latest .
# Run development server (requires APP_DATA_DIRECTORY env var)
APP_DATA_DIRECTORY=./app_data uv run python server.py --port 8000
# Build development image
docker build -f Dockerfile.dev -t presenton:dev .
# Run tests
uv run pytest
# Lint/format
uv run ruff check .
```
### Next.js Frontend
```bash
cd servers/nextjs
# Install dependencies
npm install
# Dev mode with HMR (connects to FastAPI at localhost:8000)
npm run dev
# Production build
npm run build
npm run start
# Lint
npm run lint
# E2E tests (Cypress)
npx cypress open # interactive
npx cypress run # headless
```
### Full Stack (Docker)
```bash
# Development with hot-reload
docker compose up development
# Production
docker compose up production
# GPU-accelerated (NVIDIA)
docker compose up production-gpu
# View logs
docker-compose logs -f
docker compose logs -f production
# Access web interface
# Navigate to http://localhost:5000
# Stop and clean up
docker compose down
```
## Deployment
- **Server:** local
- **Deploy:** `docker-compose up production` (or `production-gpu` for GPU support)
- **URL:** http://localhost:5000 (default)
- **Port:** 5000 (configurable in docker-compose.yml)
- **Service:** Docker Compose managed service
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/presenton
- **Volume mounts:** `./app_data:/app_data` (persistent data storage)
**Deployment Notes:**
- Change port in `docker-compose.yml` by modifying the port binding (e.g., `"8080:80"`)
- GPU deployment requires NVIDIA Docker runtime and `nvidia-docker`
- Persistent data stored in `./app_data` directory
- Can be deployed on any Docker-capable infrastructure (cloud, on-premise, local)
- **Server:** Local (self-hosted)
- **Deploy:** `docker compose up production` or `docker run -it --name presenton -p 5000:80 [env vars] ghcr.io/presenton/presenton:latest`
- **URL:** `http://localhost:5000`
- **Port:** 5000 (host) → 80 (container)
- **Service:** None (container-based)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/presenton`
- **Image:** `ghcr.io/presenton/presenton:latest`
- **Base:** `python:3.11-slim-bookworm`
- **Persistent volume:** `./app_data → /app_data`
### Quick Start
```bash
git clone https://github.com/presenton/presenton.git
cd presenton
docker run -it --name presenton -p 5000:80 \
-e LLM="openai" \
-e OPENAI_API_KEY="sk-..." \
-e IMAGE_PROVIDER="dall-e-3" \
-v "./app_data:/app_data" \
ghcr.io/presenton/presenton:latest
# Open http://localhost:5000
```
## Environment Variables
Core LLM Configuration:
- `LLM` — Primary LLM provider (`openai`, `gemini`, `claude`, `ollama`, `custom`)
- `OPENAI_API_KEY` — OpenAI API key
- `OPENAI_MODEL` — OpenAI model name (e.g., `gpt-4-turbo`)
- `GOOGLE_API_KEY` — Google API key for Gemini
- `GOOGLE_MODEL` — Gemini model name
- `ANTHROPIC_API_KEY` — Anthropic Claude API key
- `ANTHROPIC_MODEL` — Claude model name
- `OLLAMA_URL` — Ollama server URL for local models
- `OLLAMA_MODEL` — Ollama model name
Custom LLM:
- `CUSTOM_LLM_URL` — Custom OpenAI-compatible endpoint URL
**LLM Configuration:**
- `LLM` — Provider: `openai`, `google`, `anthropic`, `ollama`, `custom`
- `OPENAI_API_KEY` — OpenAI API key (required for OpenAI LLM or DALL-E/GPT-Image)
- `OPENAI_MODEL` — Model ID (default: `gpt-4-1`)
- `GOOGLE_API_KEY` — Google API key (Gemini LLM or image generation)
- `GOOGLE_MODEL` — Gemini model ID (default: `models/gemini-2.0-flash`)
- `ANTHROPIC_API_KEY` — Claude API key
- `ANTHROPIC_MODEL` — Claude model ID (default: `claude-3-5-sonnet-20241022`)
- `OLLAMA_URL` — Ollama server (default: `http://localhost:11434`)
- `OLLAMA_MODEL` — Model to use (e.g., `llama3.2:3b`)
- `CUSTOM_LLM_URL` — OpenAI-compatible endpoint URL
- `CUSTOM_LLM_API_KEY` — API key for custom endpoint
- `CUSTOM_MODEL` — Model name on custom endpoint
- `CUSTOM_MODEL` — Model ID for custom endpoint
- `TOOL_CALLS` — Use tool calls for structured output (default: `false`)
- `DISABLE_THINKING` — Disable chain-of-thought (default: `false`)
- `EXTENDED_REASONING` — Enable extended reasoning (default: empty)
- `WEB_GROUNDING` — Enable LLM web search (default: `false`)
Image Generation:
- `PEXELS_API_KEY` — Pexels stock image API key
- `COMFYUI_URL` — ComfyUI server URL for local image generation
- `COMFYUI_WORKFLOW` — ComfyUI workflow configuration
**Image Provider Configuration:**
- `IMAGE_PROVIDER` — Provider: `dall-e-3`, `gpt-image-1.5`, `gemini`, `pexels`, `pixabay`, `comfyui`
- `PEXELS_API_KEY` — Pexels API key
- `PIXABAY_API_KEY` — Pixabay API key
- `COMFYUI_URL` — ComfyUI server URL (for local image generation)
- `COMFYUI_WORKFLOW` — ComfyUI workflow JSON path
Advanced Features:
- `EXTENDED_REASONING` — Enable extended reasoning in LLM
- `TOOL_CALLS` — Enable function/tool calling in LLM
- `DISABLE_THINKING` — Disable model thinking/reasoning
- `WEB_GROUNDING` — Enable web search grounding for content generation
- `DISABLE_ANONYMOUS_TRACKING` — Disable anonymous usage telemetry
Infrastructure:
- `DATABASE_URL` — PostgreSQL connection string
- `CAN_CHANGE_KEYS` — Allow users to change API keys in UI (true/false)
**Data & Infrastructure:**
- `APP_DATA_DIRECTORY` — Root for persistent data (default: `/app_data`)
- `TEMP_DIRECTORY` — Temp file staging (default: `/tmp/presenton`)
- `DATABASE_URL` — Override SQLite; use `postgresql://` or `mysql://` for other DBs
- `PUPPETEER_EXECUTABLE_PATH` — Chromium path (default: `/usr/bin/chromium`)
- `CAN_CHANGE_KEYS` — Allow UI to modify API keys (default: `true`)
## API / Endpoints
Presenton exposes a REST API for presentation generation and includes a built-in MCP (Model Context Protocol) server.
**Key API Endpoints** (inferred from codebase):
- `POST /api/presentations/generate` — Generate presentation from prompt or document
- `POST /api/presentations/template` — Generate template from uploaded PPTX
- `POST /api/export` — Export presentation to PPTX/PDF
- `GET /api/templates` — List available templates
- `POST /api/settings` — Update user API key settings
- `GET /health` — Health check endpoint
**REST API Base:** `http://localhost:5000/api/v1/`
**MCP Server Integration:**
- Built-in MCP server for programmatic presentation generation
- Allows integration with Claude and other MCP-compatible tools
**Key Endpoints:**
- `POST /ppt/presentation/generate` — Generate presentation (sync, returns PPTX/PDF path)
- `POST /ppt/presentation/create` — Save new presentation (returns ID)
- `GET /ppt/outlines/stream/{id}` — Stream outline JSON (SSE)
- `POST /ppt/presentation/prepare` — Assign layouts and structure
- `GET /ppt/presentation/stream/{id}` — Stream slide content (SSE)
- `POST /ppt/presentation/export` — Export to PPTX or PDF
- `POST /ppt/pptx-slides/process` — Analyze uploaded PPTX (extract slides, fonts, XML)
- `POST /ppt/slide-to-html/` — Convert PPTX slide to HTML
See `/docs` (https://docs.presenton.ai) for complete API documentation.
**MCP Server:**
- `http://localhost:5000/mcp` — FastMCP HTTP endpoint for Claude Desktop integration
**Swagger UI:** `http://localhost:5000/docs`
## Known Issues
- No explicit issues documented in provided files
- Icon fallback mechanism in place for missing slide icons (commit d623fa6)
- Recent API parameter rename from 'prompt' to 'content' (commit c5e36ab)
- Some MCP/OpenAI spec compatibility fixes implemented (commit 932c807)
**Notable Recent Changes:**
- New template system and refactored template loading (v1.0.0+)
- Added GPT Image 1.5 support
- NanoBanana image generation integration
- ComfyUI local image generation workflow support
- Ukrainian language support added
- Multiple image provider options (DALL-E 3, Gemini Flash, Pexels, Pixabay)
- None explicitly documented in provided files. Refer to GitHub issues at https://github.com/presenton/presenton/issues
## Git
- **Remote:** https://github.com/presenton/presenton.git
- **Latest commit:** acb850b (feat: New Templates and refactor template loading)
- **Branch:** presenton/feat/new_templates_and_refactor_template_loading
- **Community:** Discord (https://discord.gg/9ZsKKxudNE), X/Twitter (@presentonai)
- **License:** Apache 2.0 (open-source)
- **Contact:** suraj@presenton.ai (enterprise inquiries)
- **Remote:** `https://github.com/presenton/presenton.git`
- **Latest commits:** Recent work includes new templates refactor, MCP OpenAI spec fixes, Ukrainian language support, GPT-Image-1.5 integration, local ComfyUI image provider support, and NanoBanana integration
- **License:** Apache 2.0
---
## Additional Resources
- **Architecture & Design:** See `docs/project/architecture.md` for detailed data flows and component interactions
-
## Sessions
### 2026-04-14 Project catalogued

View file

@ -2,73 +2,187 @@
name: "Sandbox NotebookLM"
client: Oliver Internal
status: active
tech: [Next.js 15, FastAPI, Python 3.13, uv, PostgreSQL, Redis, ElevenLabs, Docker]
tech: [Next.js 15, React 19, FastAPI, Python 3.13, TypeScript, PostgreSQL, Redis, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/sandbox-notebookllamalm-nextjs
deploy: docker compose up --build
deploy: bash scripts/deploy.sh
url: https://ai-sandbox.oliver.solutions/notebookllama/
server: optical-web-1
tags: [oliver, ai, notebooklm, llamaindex, rag, multi-user, nextjs15, elevenlabs]
created: 2026-04-14
last_commit: 2026-04-27
commits: 183
port: 9000 (backend), 4000 (frontend)
db: PostgreSQL
service: docker-compose
---
## Overview
Enterprise-ready alternative to Google NotebookLM. Multi-user, self-hosted, with AI Studio generators and podcast generation.
**Live:** https://ai-sandbox.oliver.solutions/notebookllama/
**Backend API docs:** https://ai-sandbox.oliver.solutions/notebookllama-back/api/docs
**Repo:** https://bitbucket.org/zlalani/sandbox-notebookllamalm-nextjs
Sandbox-NotebookLM is a self-hosted enterprise alternative to Google NotebookLM, providing multi-user notebook management, multi-model AI support (OpenAI, Anthropic, Gemini, Groq, DeepSeek), and advanced document analysis including RAG, podcast generation, and 7 Studio output types (flashcards, quiz, mind maps, slides, reports, infographics, datatables). Built with FastAPI backend and Next.js 15 frontend, deployed via Docker Compose on `optical-web-1`. Users can upload 40+ file formats, synthesize cross-document insights, generate conversational podcasts with ElevenLabs, and create rich study/presentation materials in one platform.
## Tech Stack
- **Frontend:** Next.js 15 App Router + React 19 + TypeScript + Tailwind 4
- **Backend:** FastAPI + Python 3.13 + `uv` package manager
- **Database:** PostgreSQL 18
- **Cache:** Redis 7
- **AI:** LlamaIndex + LLM factory (multi-model)
- **Audio:** ElevenLabs (podcast generation)
- **Auth:** Microsoft SSO + signup/login
- **Infrastructure:** Docker + docker-compose
- **Frontend:** Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS 4, Zustand (state), Axios (HTTP)
- **Backend:** FastAPI, Python 3.13, SQLAlchemy ORM, LlamaIndex (RAG), Pydantic
- **Database:** PostgreSQL 18, Redis 7 (caching/tasks)
- **Infrastructure:** Docker Compose, `uv` package manager (Python), Jaeger (tracing), Adminer (DB admin)
- **AI/ML:** LlamaCloud API (RAG), OpenAI, Anthropic Claude, Google Gemini, Groq, DeepSeek; ElevenLabs (podcast audio)
- **Key libraries:** `llama-index`, `python-multipart`, `pydantic-settings`, `alembic` (migrations), `tenacity` (retry logic)
## Architecture
```
Next.js 15 (App Router)
src/app/notebooks/[id]/page.tsx ← main notebook page (~1600 lines)
src/lib/api.ts ← all API calls (axios)
store/authStore.ts ← Zustand auth (localStorage)
FastAPI backend
api/routes/
auth.py ← signup, login, Microsoft SSO
notebooks.py ← CRUD, synthesis, podcast, Studio
documents.py ← upload, task status
chat.py ← WebSocket chat
admin.py ← admin dashboard
notebookllama/
studio_generators.py ← 7 LLM generators (flashcards, quiz, mindmap, slides, report, infographic, datatable)
audio.py ← ElevenLabs podcast (saves to PODCAST_DATA_DIR)
llm_factory.py ← get_llm_by_type(), get_structured_llm()
```
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (Next.js 15, port 4000) │
│ - Notebook page (~1600 lines): chat, docs, synthesis, Studio │
│ - Auth store (Zustand → localStorage) │
│ - Axios client wraps all backend API calls │
└────────────────┬────────────────────────────────────────────────┘
│ HTTP/WebSocket
┌─────────────────────────────────────────────────────────────────┐
│ Backend (FastAPI, port 9000) │
│ ├─ /auth — signup, login, Microsoft SSO │
│ ├─ /notebooks — CRUD, synthesis, podcast, sharing, Studio │
│ ├─ /documents — upload, status, summaries │
│ ├─ /chat — WebSocket (24h heartbeat + auto-reconnect) │
│ ├─ /admin — dashboard │
│ └─ /api/health — status check │
│ │
│ Core modules: │
│ ├─ llm_factory.py — multi-model dispatcher │
│ ├─ studio_generators.py — 7 output type generators │
│ ├─ audio.py — ElevenLabs podcast generation │
│ ├─ background_tasks.py — threaded task queue │
│ ├─ notebook_synthesis.py — cross-doc insights │
│ └─ database.py — SQLAlchemy models + DB utilities │
└────────────────┬────────────────────────────────────────────────┘
│ SQL / Cache
┌───────────────────────────┐
│ PostgreSQL (port 5433) │
│ + Redis (port 6380) │
│ + Jaeger (port 17000) │
│ + Adminer (port 9001) │
└───────────────────────────┘
```
**Key Design:**
- **Multi-model support:** `llm_factory.py` returns configured LLM instance by type (GPT-5, Claude, Gemini, etc.)
- **RAG pipeline:** LlamaCloud ingests documents → LlamaIndex retrieves context → LLM generates answers
- **Studio generators:** 7 independent generator classes inherit base template; queried async via API
- **Background tasks:** Upload processing, podcast generation, synthesis run in thread pool (non-blocking)
- **Auth:** Token-based (JWT-like); persisted in Zustand localStorage as `auth-storage`; optional Azure AD SSO
- **WebSocket chat:** 24h keep-alive with heartbeat ping/pong; auto-reconnect on close
## Dev Commands
```bash
# Local dev (both services)
docker compose up -d # Start all (postgres, redis, backend, frontend, jaeger, adminer)
docker compose down # Stop all
# Frontend only (dev server on port 4000, watches changes)
cd frontend
npm run dev # Requires NEXT_PUBLIC_API_URL=http://localhost:9000
# Backend only (dev server on port 9000, auto-reload)
cd backend
uv run uvicorn src.api.main:app --reload --port 9000
# Build (Docker)
docker compose up -d --build # Rebuild images and start
docker compose build backend # Rebuild backend only
docker compose build frontend # Rebuild frontend only
# Database migrations (manual, auto-runs in deploy.sh)
docker compose exec backend /app/.venv/bin/python -c \
"import sys; sys.path.insert(0, '/app/src/notebookllama'); from database import run_studio_migration; run_studio_migration(); print('Done')"
# Check logs
docker compose logs backend --tail=50 -f
docker compose logs frontend --tail=50 -f
# Shell into container
docker compose exec backend bash
docker compose exec frontend bash
# Test API health
curl http://localhost:9000/api/health
```
## Deployment
- **Server:** `optical-web-1` at `/opt/sandbox-notebookllamalm-nextjs`
- **Run:** `docker compose up --build`
- **Live URL:** https://ai-sandbox.oliver.solutions/notebookllama/
### Deploy Commands (Server)
- **Server:** `optical-web-1`
- **Deploy:** `bash scripts/deploy.sh` (standard: git pull + build + docker compose up + health check)
- `--backend-only` — rebuild backend only
- `--frontend-only` — rebuild frontend only
- `--no-build` — restart without rebuild (env changes only)
- **URL:** `https://ai-sandbox.oliver.solutions/notebookllama/`
- **Backend API:** `https://ai-sandbox.oliver.solutions/notebookllama-back/api/docs` (Swagger)
- **Port:** Backend 9000, Frontend 4000 (Docker internal)
- **Service:** Docker Compose (no systemd; `docker-compose.yml` in repo root)
- **Local path:** `/opt/sandbox-notebookllamalm-nextjs` (on optical-web-1)
- **Health checks:** `/api/health` (backend); frontend checks port 4000
```bash
git pull origin main
docker compose build backend # or frontend or both
docker compose up -d
# Full deploy on server (must SSH first)
ssh michael_clervi@optical-web-1
cd /opt/sandbox-notebookllamalm-nextjs
sudo bash scripts/deploy.sh # No sudo for git pull, yes for file ops
# Rebuild only backend (faster)
docker compose build backend && docker compose up -d backend
# Partial redeploy
sudo bash scripts/deploy.sh --backend-only
sudo bash scripts/deploy.sh --no-build
# Logs
# Manual rollback to previous commit
sudo bash scripts/rollback.sh <commit-sha>
# Manual: check logs
docker compose logs backend --tail=50
docker compose logs frontend --tail=50
```
**Important:**
- `git pull` must run WITHOUT `sudo`; Docker and file operations in `/opt/` require `sudo`
- Frontend env vars (NEXT_PUBLIC_*) are baked into the build — frontend rebuild required if changed
- Backend uses venv at `/app/.venv/` — always call `/app/.venv/bin/python` in containers
- PostgreSQL health check waits for 10 retries; Redis health check 10 retries; backend 5 retries (30s start)
## Environment Variables
### Backend (`.env` in `backend/` directory)
**Core (required):**
- `OPENAI_API_KEY` — OpenAI API key (GPT models)
- `LLAMACLOUD_API_KEY` — LlamaCloud API key for RAG/document ingestion
**Models (optional, enables corresponding model in UI):**
- `ANTHROPIC_API_KEY` — Anthropic Claude
- `GOOGLE_API_KEY` — Google Gemini
- `GROQ_API_KEY` — Groq LPU
- `DEEPSEEK_API_KEY` — DeepSeek
**Podcast generation (optional):**
- `ELEVENLABS_API_KEY` — ElevenLabs for audio synthesis
**Database:**
- `pgql_user` — PostgreSQL user (default: `postgres`)
- `pgql_psw` — PostgreSQL password (default: `admin`)
- `pgql_db` — PostgreSQL database name (default: `postgres_nextjs`)
- `pgql_host` — DB host (Docker Compose sets to `postgres`)
- `pgql_port` — DB port (Docker Compose: `5432` in container, mapped to `5433` locally)
**SSO (optional, enables Microsoft Azure AD login):**
- `AZURE_CLIENT_ID` — Azure app client ID
- `AZURE_AUTHORITY` — Azure login endpoint (e.g., `https://login.microsoftonline.com/tenant-id`)
- `AZURE_REDIRECT_URI` — OAuth redirect (e.g., `https://ai-sandbox.oliver.solutions/notebookllama/`)
**Docker/Runtime:**
- `PODCAST_DATA_DIR` — output directory for podcast audio files (set to `conversations` in docker-compose.yml)
### Frontend (`.env.production`
## Timeline / Git History
| Date | Change |
|------|--------|
@ -255,4 +369,4 @@ docker compose logs frontend --tail=50
## Related
- [[enterprise-ai-hub-nexus/Enterprise AI Hub Nexus]]
- [[cinema-studio-pro/Cinema Studio Pro]] (same ai-sandbox.oliver.solutions server)
- [[Oliver-ai-bot_2.0/Oliver AI Bot 2.0]]
- [[Oliver-ai-bot_2.0/Oliver AI Bot 2.0]]

View file

@ -2,72 +2,173 @@
name: "Semblance — Synthetic Society"
client: Oliver Internal
status: active
tech: [Python, Quart, Socket.IO, MongoDB, React, TypeScript, Tailwind, shadcn/ui, Docker, Gemini, OpenAI]
tech: [React, TypeScript, Python, Quart, Socket.IO, MongoDB, Gemini API, OpenAI API]
local_path: /Users/ai_leed/Documents/Projects/Oliver/semblance
deploy: docker compose up --build
url:
server: optical-web-1
deploy: ./deploy.sh
url: https://optical-dev.oliver.solutions/semblance/
server: optical-dev
tags: [oliver, ai, synthetic-personas, focus-group, insights, gcp, socketio]
created: 2026-04-14
last_commit: 2026-04-24
commits: 122
port: 5137
db: MongoDB 7
service: Docker Compose
---
## Overview
AI platform for creating synthetic consumer personas and running autonomous focus group sessions. Real-time multi-persona AI conversations.
**Key capabilities:**
- AI persona generation from audience briefs (Gemini 3 Pro Preview, GPT-4.1, GPT-5.2)
- Live focus group sessions (manual or fully autonomous AI moderation)
- Document uploads (PDF, DOCX, TXT) as focus group assets
- Analytics + theme extraction
- Bulk export (PDF)
Backup: `semblance_backup/`
**Semblance** is an AI-powered research platform that generates synthetic consumer personas and orchestrates autonomous focus group sessions. Users build realistic buyer profiles (via AI generation or manual entry), moderate live discussions with LLM-powered persona responses, or enable AI autonomous mode to orchestrate multi-persona conversations with speaker selection and turn-taking. The platform extracts themes and session analytics in real time via Socket.IO, with full quota enforcement, usage tracking, and admin analytics for cost visibility.
## Tech Stack
- **Frontend:** React + TypeScript + Vite + Tailwind + shadcn/ui (port 5137 or build)
- **Backend:** Python + Quart (async Flask) + Hypercorn ASGI (port 5137)
- **Database:** MongoDB (PyMongo)
- **Real-time:** python-socketio + AsyncServer (ASGI wrapped)
- **AI:** Gemini 3 Pro Preview (default), GPT-4.1, GPT-5.2
- **Auth:** Custom JWT (Quart-compatible, NOT Flask-JWT-Extended)
- **Infrastructure:** Docker + docker-compose
- **Frontend:** React 18 + TypeScript, Vite, TanStack Query (server state), Socket.IO client, React Router DOM, MSAL (Microsoft SSO)
- **Backend:** Python 3.11+, Quart (async web framework), python-socketio, Motor (async MongoDB driver), PyMongo (sync driver)
- **Database:** MongoDB 7 (Docker container, 127.0.0.1:27017)
- **Infrastructure:** Docker Compose, Apache 2 (reverse proxy), systemd/Docker (process management)
- **AI/ML:** Google Gemini 2.0 Flash & Pro, OpenAI GPT-4o, unified LLM interface with token counting
- **Key libraries:** asyncio (concurrent AI threads), Hypercorn (ASGI server), pydantic (validation), PyJWT (auth)
## Architecture
```
React SPA (TypeScript + shadcn/ui)
↓ Socket.IO (WebSocketContext)
Quart backend (python-socketio AsyncServer)
├── ai_runner_service.py (background task execution)
├── autonomous_conversation_controller.py (multi-persona orchestration)
├── conversation_decision_service.py (next speaker logic)
├── conversation_context_service.py (state + history)
└── llm_service.py (multi-model: Gemini/GPT)
MongoDB (personas, sessions, messages)
Browser (React SPA)
├── TanStack Query + Socket.IO client
├── AuthContext (JWT in memory)
└── WebSocketContextNew (real-time events)
Apache (optical-dev.oliver.solutions)
├── /semblance/ → static files (/var/www/html/semblance)
├── /semblance_back/api/ → proxy to 127.0.0.1:5137
└── /semblance_back/socket.io/ → WebSocket to 127.0.0.1:5137
Hypercorn ASGI (port 5137)
├── Quart app (8 blueprints: auth, personas, focus-groups, ai-personas, etc.)
├── python-socketio AsyncServer (room-based WebSocket emit)
├── 19 Services (LLM, AI runner, persona generation, theme extraction, etc.)
└── Motor + PyMongo clients
MongoDB 7 (10 collections: users, personas, focus_groups, usage_events, etc.)
```
### Known Issue — GCP 30s Load Balancer Timeout
All async LLM routes were fixed to bypass GCP 30s LB timeout (same issue as Mod Comms).
Tasks migrated from WebSocket delivery → HTTP polling.
**Core data flows:**
1. **Persona Generation (AI):** POST `/api/ai-personas/generate` → task_manager creates async job → ai_persona_service calls LLM (Gemini/OpenAI) with persona-basic/detailed-generation.md prompt → stores result in `personas` collection with allowlisted fields → UsageEvent records tokens + cost → WebSocket task_complete event to user.
2. **Manual Focus Group Message:** POST `/api/focus-groups/<id>/messages` → jwt_required + quota check → focus_group_response_service iterates personas → calls LLM for each persona response in parallel → stores in focus_group_messages collection → emits new_message WebSocket event.
3. **AI Autonomous Mode:** After session create, autonomous_conversation_controller spawns dedicated asyncio thread → conversation_decision_service selects next speaker → generates turn-by-turn responses → conversation_context_service maintains rolling context window → conversation_state_manager tracks turn count + termination logic → emits real-time events as messages arrive.
4. **Theme Extraction:** After session end, key_theme_service calls LLM to identify patterns → stores in focus_group_themes collection → focus_group_summary_service aggregates for session report.
## Dev Commands
```bash
# Frontend
npm run build # Production build (use for testing)
npm run build:dev # Dev build
npm run lint
npm run preview
# Backend
cd backend && python run.py # Starts on port 5137
```bash
# First-time setup
cd /Users/ai_leed/Documents/Projects/Oliver/semblance
npm install
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cd ..
cp .env.development .env
cd backend && cp .env.example .env
# Edit backend/.env: fill SECRET_KEY, JWT_SECRET_KEY, GEMINI_API_KEY, OPENAI_API_KEY
nano .env
# Run locally (combined)
./start.sh
# Backend: http://localhost:5137
# Frontend: http://localhost:5173 (proxies /api → backend)
# Or separate terminals:
# Terminal 1 — Backend:
cd backend && source venv/bin/activate && python run.py
# Terminal 2 — Frontend:
npm run dev
# Development tasks
npm run lint # Frontend lint
npm run build # Production build
npm run build:dev # Dev mode build
cd backend && python -m pytest tests/ # Backend tests
python scripts/seed_model_pricing.py # Seed model costs locally
# Default local login: user / pass
```
## Deployment
- **Run:** `docker compose up --build`
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/semblance`
- **Backend port:** 5137 (Hypercorn)
- **Server:** optical-dev.oliver.solutions
- **Deploy:** `./deploy.sh` (on server, in `/opt/semblance/`)
- **URL:** https://optical-dev.oliver.solutions/semblance/
- **Port:** 5137 (backend, 127.0.0.1 only; public via Apache)
- **Service:** Docker Compose (`docker compose up -d --build`)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/semblance
**Deploy process (automated by deploy.sh):**
1. Pre-flight: verify `backend/.env` has all 4 required keys (SECRET_KEY, JWT_SECRET_KEY, GEMINI_API_KEY, OPENAI_API_KEY)
2. `git pull`
3. Docker build frontend → copy dist to `/var/www/html/semblance`
4. Ensure Apache proxy config + reload
5. `docker compose up -d --build` (rebuild + restart mongo + backend)
6. Health check loop (30 retries, 10s intervals)
7. Run seed_model_pricing.py backfill
8. Verify both services healthy
**⚠️ CONSTRAINT:** Do NOT run `./deploy.sh` without explicit user instruction. Hard rule.
## Environment Variables
**Frontend (.env):**
- `VITE_FRONTEND_BASE_URL` — public app URL (dev: `http://localhost:5173`, prod: `https://optical-dev.oliver.solutions/semblance`)
- `VITE_API_BASE_URL` — backend API root (dev: `/api` proxied by Vite, prod: `https://optical-dev.oliver.solutions/semblance_back/api`)
- `VITE_WEBSOCKET_PATH` — Socket.IO path (dev: `/socket.io/`, prod: `/semblance_back/socket.io/`)
- `VITE_MSAL_REDIRECT_URI` — Microsoft SSO redirect (dev: `http://localhost:5173/`, prod: `https://optical-dev.oliver.solutions/semblance`)
- `VITE_MSAL_POST_LOGOUT_REDIRECT_URI` — Post-logout URL (same as REDIRECT_URI)
- `VITE_ENABLE_LOCAL_LOGIN` — enable user/pass login (dev: `true`, prod: `false`)
- `VITE_ENABLE_WEBSOCKET_DEBUG` — WebSocket logging (dev: `true`, prod: `false`)
**Backend (backend/.env) — required:**
- `SECRET_KEY` — Flask session secret (generate: `python3 -c "import secrets; print(secrets.token_hex(32))"`)
- `JWT_SECRET_KEY` — JWT signing key (same generation as above)
- `GEMINI_API_KEY` — Google Gemini API key
- `OPENAI_API_KEY` — OpenAI API key
**Backend (optional):**
- `MONGODB_URI` — MongoDB connection string (default: `mongodb://localhost:27017/semblance`)
- `MSAL_CLIENT_ID`, `MSAL_CLIENT_SECRET`, `MSAL_TENANT_ID` — Microsoft SSO credentials (prod only)
- `QUART_ENV``development` or `production`
## API / Endpoints
**Key routes (all under `/api/`):**
- `POST /auth/login` — local auth (dev only)
- `POST /ai-personas/generate` — async persona generation (returns 202 + task_id)
- `GET /tasks/<task_id>` — poll job status
- `POST /focus-groups` — create session
- `POST /focus-groups/<id>/messages` — send manual message
- `POST /focus-groups/<id>/autonomous-start` — enable AI mode
- `POST /focus-groups/<id>/autonomous-stop` — disable AI mode
- `GET /focus-groups/<id>` — retrieve session (includes messages, themes)
- `POST /admin/users` — manage users & quotas (admin only)
- `GET /admin/usage` — analytics dashboard (admin only)
**WebSocket events:**
- `task_complete` — persona/theme generation finished
- `new_message` — focus group message from human or AI
- `mode_changed` — autonomous mode toggled
- `conversation_turn` — AI turn announcement (in autonomous mode)
- `session_summary` — final themes & analytics
## Known Issues
- **Live token extraction:** Missing `usage_metadata` in some LLM responses logs warning but continues gracefully; "thinking tokens" captured when available (o1-mini limitation).
- **Backfill pricing:** Requires `--delete-existing-estimates` flag to recalculate; uses accumulated conversation context for estimation.
- **Admin filters:** ISO Z timestamp parsing previously crashed; fixed in commit 7b6a7c73; validate all date filters are ISO 8601.
- **AI autonomous mode:** Historical race conditions (split-brain UI, cross-loop WebSocket emit) fixed in commits 283b31e7b4978989; monitor logs for asyncio.TimeoutError
## Timeline / Git History
| Date | Change |
@ -224,4 +325,4 @@ cd backend && python run.py # Starts on port 5137
## Related
- [[modcomms/Mod Comms]] (same GCP timeout issue)
- [[olivas/OliVAS]]
- [[build-a-squad/Build A Squad]]
- [[build-a-squad/Build A Squad]]

View file

@ -1,11 +1,11 @@
---
name: "SmartCrop26"
client: Oliver Solutions
client: Oliver Internal
status: active
server: ubuntu-server
tech: [React 18, TypeScript, Vite, Tailwind CSS, shadcn-ui, Google Gemini API, Azure AD]
server: optical-web-1
tech: [React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui, Google Gemini API, Azure AD]
local_path: /Users/ai_leed/Documents/Projects/Oliver/smartcrop26
deploy: /opt/smartcrop26/deploy.sh
deploy: bash deploy.sh
url: https://ai-sandbox.oliver.solutions/smartcrop26
tags: [oliver, smartcrop, image, lovable, react]
created: 2026-04-14
@ -14,119 +14,110 @@ port: 8080
## Overview
**SmartCrop2026** is a fully client-side, AI-powered image cropping tool built with React 18 and TypeScript. Users upload images and select aspect ratios; the app suggests optimal crop regions using either Google Gemini 2.5 Flash API or local browser-based Sobel edge detection. Crops are previewed interactively, edited manually, and exported as resized PNGs bundled into a ZIP file. It requires Azure AD SSO login and serves Oliver Solutions' internal workflows.
SmartCrop26 is a fully client-side React SPA that provides AI-powered image cropping for multiple aspect ratios and platforms. Users upload images, select or define aspect ratios, and receive crop suggestions from either Google Gemini 2.5 Flash (remote AI) or a local Sobel edge-detection algorithm running entirely in the browser. Results export as a ZIP archive of PNGs at specified dimensions. Built for Oliver Internal and deployed to `https://ai-sandbox.oliver.solutions/smartcrop26`.
## Tech Stack
- **Frontend:** React 18, TypeScript, Vite, Tailwind CSS 3.4, shadcn-ui (Radix primitives), Lucide icons
- **Backend:** None (fully client-side)
- **Frontend:** React 18, TypeScript, Vite, Tailwind CSS 3.4, shadcn/ui (Radix primitives), Lucide icons
- **Backend:** None (fully client-side; API calls to Google Gemini only)
- **Database:** None
- **Infrastructure:** Ubuntu server deployment via bash script; served at `https://ai-sandbox.oliver.solutions/smartcrop26`
- **AI/ML:** Google Gemini 2.5 Flash (API-based analysis) + Sobel edge detection (browser-based fallback)
- **Key libraries:** JSZip (ZIP export), FileSaver (download), MSAL (Azure AD auth), React Hook Form (forms), React Query (lightly configured)
- **Infrastructure:** Apache on optical-web-1; deployed to `/var/www/html/smartcrop26`
- **AI/ML:** Google Gemini 2.5 Flash (function calling); local Sobel edge detection (Canvas API)
- **Key libraries:** JSZip (export), file-saver (download), MSAL (Azure AD auth), React Hook Form, TanStack React Query, next-themes (dark mode)
## Architecture
**Central orchestrator:** `src/pages/Index.tsx` manages all top-level state:
- `images[]` — uploaded image data URLs
- `selectedRatios[]` — chosen aspect ratios
- `cropsMap{}` — crop suggestions per image
- `engine` toggle — AI vs. Local analysis mode
**Single-page orchestrator:** `src/pages/Index.tsx` holds all top-level state (`images`, `selectedRatios`, `cropsMap`, `engine` toggle) and acts as the central wiring point.
**Dual analysis engines:**
- **AI engine** (`lib/analyze-image.ts`): Encodes image as base64, sends to Google Gemini via function calling, returns `CropSuggestion[]` with normalized coords (01)
- **Local engine** (`lib/analyze-local.ts`): Sobel edge detection in-browser; scales image to max 400px, samples candidate windows with center-weight bias
- **AI engine** (`src/lib/analyze-image.ts`): Encodes image as base64, sends to Google Gemini 2.5 Flash via function calling with a crop suggestion prompt. Requires `VITE_GOOGLE_API_KEY`.
- **Local engine** (`src/lib/analyze-local.ts`): Sobel edge detection entirely in-browser. Scales image to max 400px, samples candidate crop windows with center-weight bias, returns edge scores.
**UI components:**
- `ImageUpload` — FileReader for image ingestion
- `RatioSelector` — 12 built-in platform-grouped ratios + custom input
- `CropPreviewCard` — Canvas-rendered thumbnails
- `CropEditor` — Interactive crop region dragging/resizing via Canvas
Both engines return `CropSuggestion[]` with normalized coordinates (01 range).
**Export pipeline** (`lib/export-zip.ts`):
1. Iterate all uploaded images + selected crops
2. Rescale each crop to requested pixel sizes via Canvas
3. Pack PNGs into JSZip archive
4. Trigger browser download via FileSaver
**Image handling:** Images stored as data URLs (FileReader). Canvas API used for:
- Thumbnail rendering (CropPreviewCard)
- Interactive crop editing (CropEditor)
- Export resizing and export PNG generation
**Config parsing** (`lib/crop-types.ts`):
- Handles current v2 JSON, legacy Python tool format, and simple ratio lists
- `PRESET_RATIOS` defines 12 platform groups (Instagram, TikTok, LinkedIn, etc.)
**Export pipeline** (`src/lib/export-zip.ts`): Iterates all image/crop combinations, rescales via canvas at each requested pixel size, packs PNGs into ZIP (JSZip), triggers browser download (FileSaver).
**Config parsing** (`src/lib/crop-types.ts`): `parseConfig()` handles v2 JSON schema, legacy Python tool format, and simple ratio lists. `PRESET_RATIOS` defines 12 built-in aspect ratios (Instagram, TikTok, LinkedIn, YouTube, etc.).
**Auth:** Azure AD SSO via MSAL (`@azure/msal-browser`, `@azure/msal-react`). Tenant and client IDs in `.env`.
**Styling:** HSL custom properties, Tailwind CSS, dark mode via next-themes class strategy, Montserrat font.
```
┌─────────────────────────────────────────────────────────┐
│ Index.tsx (State) │
│ images[] | selectedRatios[] | cropsMap{} | engine │
└────┬──────────────────────────────────────────────┬─────┘
│ │
┌────▼──────────┐ ┌──────────────────┐ ┌────────▼──┐
│ ImageUpload │ │ RatioSelector │ │ CropEditor│
│ (FileReader) │ │ (PRESET_RATIOS) │ │ (Canvas) │
└───────────────┘ └──────────────────┘ └───────────┘
│ │
┌────▼────────────────────────────────────────────▼─────┐
│ Dual Analysis Engines │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ AI (Gemini) │ │ Local (Sobel) │ │
│ │ base64 → API │ │ in-browser EdgeDet │ │
│ └──────────────────┘ └──────────────────────┘ │
│ ↓ CropSuggestion[] (normalized 01) │
└──────────────────────────────────────────────────────┘
┌────▼──────────────┐
│ CropPreviewCard │
│ (Canvas render) │
└─────────┬────────┘
┌────▼──────────────────┐
│ export-zip.ts │
│ (Canvas rescale + ZIP)│
└─────────┬─────────────┘
(download)
┌──────────────────────────────────┐
│ Index.tsx (State Container) │
├──────────────────────────────────┤
│ images[] | selectedRatios[] │
│ cropsMap | engine toggle │
└──────────────────┬───────────────┘
┌─────────┼──────────┬────────────┐
│ │ │ │
┌────▼──┐ ┌───▼──┐ ┌────▼───┐ ┌──▼────┐
│Upload │ │Ratio │ │Preview │ │Export │
│Mgr │ │Select│ │& Editor│ │ ZIP │
└────┬──┘ └──────┘ └───┬────┘ └──┬────┘
│ │ │
┌────▼──────────┬────────▼──┐ ┌────▼─────┐
│ AI Engine │Local Sobel│ │export-zip│
│ (Gemini 2.5) │ (Canvas) │ │(JSZip) │
└───────────────┴───────────┘ └──────────┘
```
## Dev Commands
```bash
npm run dev # Start dev server at http://localhost:8080
npm run build # Production build (dist/)
npm run dev # Start dev server on http://localhost:8080 (Vite)
npm run build # Production build (output to dist/)
npm run build:dev # Dev build with source maps
npm run lint # Run ESLint
npm run lint # ESLint check
npm run test # Vitest single run
npm run test:watch # Vitest watch mode
npm run preview # Preview production build locally
```
## Deployment
- **Server:** Ubuntu (internal Oliver Solutions infrastructure)
- **Deploy:** Run `/opt/smartcrop26/deploy.sh` as root or with sudo
- Script pulls from git, runs `npm ci && npm run build`, copies `dist/` to `/var/www/html/smartcrop26/`
- **Server:** optical-web-1 (Apache)
- **Deploy:** `bash deploy.sh` (run on server at `/opt/smartcrop26`)
- **URL:** `https://ai-sandbox.oliver.solutions/smartcrop26`
- **Port:** 8080 (dev); served via web server on production
- **Service:** None (static site; no systemd service)
- **Port:** N/A (served via Apache; dev server on 8080)
- **Service:** N/A (static files served by Apache)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/smartcrop26`
**Deploy script flow:**
1. Pulls latest from git
2. Runs `npm ci && npm run build`
3. Copies `dist/` to `/var/www/html/smartcrop26/` (uses `sudo` for permissions)
## Environment Variables
- `VITE_GOOGLE_API_KEY` — Google Gemini 2.5 Flash API key; required for AI analysis engine
- `VITE_AZURE_TENANT_ID` — Azure AD tenant ID (`e519c2e6-bc6d-4fdf-8d9c-923c2f002385`)
- `VITE_AZURE_CLIENT_ID` — Azure AD app registration client ID (`9079054c-9620-4757-a256-23413042f1ef`)
- `VITE_AZURE_REDIRECT_URI`Azure AD redirect after login (`https://ai-sandbox.oliver.solutions/smartcrop26`)
- `VITE_GOOGLE_API_KEY`API key for Google Gemini 2.5 Flash. Without this, only local Sobel engine is available.
- `VITE_AZURE_TENANT_ID` — Azure AD tenant: `e519c2e6-bc6d-4fdf-8d9c-923c2f002385`
- `VITE_AZURE_CLIENT_ID` — Azure AD app client ID: `9079054c-9620-4757-a256-23413042f1ef`
- `VITE_AZURE_REDIRECT_URI`Redirect URI after login: `https://ai-sandbox.oliver.solutions/smartcrop26`
See `.env.example` for template.
## API / Endpoints
**No REST backend.** All image processing runs client-side. Only external API is:
- **Google Gemini 2.5 Flash** — Called directly from browser with function calling payload (image base64 + crop suggestion schema). Credentials managed by `VITE_GOOGLE_API_KEY`.
## Known Issues
- TypeScript config is loose (`noImplicitAny: false`, no strict null checks) — reduces type safety but allows faster iteration
- TanStack React Query is installed but lightly used; consider full adoption for server state
- Azure AD auth is required; no anonymous/guest mode
- Gemini API calls require valid `VITE_GOOGLE_API_KEY` or falls back to local Sobel engine
- **TypeScript config is loose:** `noImplicitAny: false`, no strict null checks. Do not tighten without full codebase audit.
- **No backend:** All state and processing on client; stateless after page reload.
- **SSH access:** Do not SSH to optical-web-1 without explicit user instruction.
- **shadcn/ui editing:** Never edit components in `src/components/ui/` manually. Regenerate via `npx shadcn-ui@latest add <component>`.
## Git
- **Remote:** `git@bitbucket.org:zlalani/smartcrop26.git`
- **Recent commits:** Add Reset/Remove buttons (24883a3), batch generate + ZIP fixes (66bafdc), login redesign (222ebe8), Azure SSO auth (f145712)
- **Latest commits:** "Add Reset button and per-image Remove button" (24883a3); "Improve UX: batch generate, ZIP fix, workflow guide, status badges" (66bafdc)
## Sessions
### 2026-04-14 Project catalogued

View file

@ -1,11 +1,11 @@
---
name: "Social Reporting Tool"
client: "TBD"
client: Oliver Internal
status: active
server: local
tech: [TypeScript, Node.js, React, PostgreSQL, Claude AI, Apify, Docker, Apache]
server: Ubuntu + Apache
tech: [TypeScript, Node.js, React/Vanilla JS, PostgreSQL, Docker, Apache, Apify API, Claude AI]
local_path: /Users/ai_leed/Documents/Projects/Oliver/social-reporting-tool
deploy: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
deploy: git pull && cp frontend/* /var/www/html/social-reporting/ && docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
url: https://your-domain.com/social-reports/
tags:
- project
@ -15,131 +15,145 @@ db: PostgreSQL
---
## Overview
Social Reporting Tool is an automated social media research platform that scrapes TikTok, Instagram, and YouTube via Apify, analyzes content with Claude AI, and generates client-ready HTML reports. It serves marketing teams and research agencies who need rapid competitive intelligence and trend analysis across multiple social platforms. The system combines real-time scraping with AI-driven content analysis in an 8-stage pipeline, offering a web dashboard for brief management, progress tracking, and cost monitoring.
Social Listening Pipeline is an automated social media research tool that orchestrates an 8-stage pipeline to scrape TikTok, Instagram, and YouTube via Apify, analyse content with Claude AI, and generate client-ready HTML reports. Built for Oliver Internal, it provides a real-time SSE dashboard for monitoring pipeline execution, cost tracking, saved client briefs, and complete run history backed by PostgreSQL. The platform enforces budget controls, integrates optional Azure AD SSO, and deploys as containerized services behind Apache on Ubuntu production servers.
## Tech Stack
- **Frontend:** HTML/CSS/JavaScript (vanilla), MSAL.js for Azure AD SSO
- **Backend:** Node.js 20+, TypeScript, tsx (runtime)
- **Database:** PostgreSQL 16 (Alpine)
- **Infrastructure:** Docker Compose, Apache (production reverse proxy)
- **AI/ML:** Anthropic Claude API (content analysis, trend detection, report generation)
- **Key libraries:** postgres (Node.js driver), Apify SDK (web scraping), native Node.js HTTP
- **Frontend:** Vanilla HTML/CSS/JavaScript (with optional MSAL.js for Azure AD SSO)
- **Backend:** Node.js 20+ with TypeScript, tsx for execution
- **Database:** PostgreSQL 16 (Alpine), pgdata volume persistence
- **Infrastructure:** Docker Compose (local + production), Apache reverse proxy on Ubuntu, static files served from `/var/www/html/social-reporting`
- **AI/ML:** Claude AI (Anthropic API) for analysis; Apify scrapers (TikTok, Instagram, YouTube actors)
- **Key libraries:** `postgres` (native PostgreSQL client), `tsx` (TypeScript executor), Node.js built-in `http` and `EventTarget` for SSE
## Architecture
The system is organized as a Node.js backend with a static frontend, powered by an 8-stage pipeline:
The system comprises four main layers:
1. **Frontend** (`frontend/`): Vanilla JavaScript dashboard with tabs for running pipelines, viewing reports, managing saved briefs, and run history. Communicates with backend via HTTP POST and SSE for real-time updates.
2. **Backend Server** (`agents/social-listening/dashboard/server.ts`): Node.js HTTP server on port 3456 that:
- Authenticates requests via HMAC-signed HttpOnly session cookies (`sl_session`)
- Handles POST `/run` to trigger pipelines (singleton; returns HTTP 409 if one is already running)
- Streams pipeline progress via SSE (Server-Sent Events)
- Exposes REST endpoints for briefs, runs, cost history, and report downloads
- Rate-limits login attempts (5 per 15 min per IP)
3. **Pipeline Orchestrator** (`agents/social-listening/pipeline-v2.ts`): 8-stage sequential processor:
- **Stage 1 (Brief Validation):** Normalises and validates the client brief schema
- **Stage 2 (Strategy Review):** Claude AI reviews strategy, suggests up to 3 extra hashtags
- **Stage 3 (Discovery Scrape):** Apify scrapes TikTok/Instagram/YouTube for matching content
- **Stage 4 (Data Review):** Claude AI identifies trends and insights from scraped data
- **Stage 5 (Enrichment Scrape):** Fetches transcripts and additional metadata
- **Stage 6 (Pre-Report Review):** Claude AI refines findings before report generation
- **Stage 7 (Desk Research):** Web search for additional context
- **Stage 8 (Report Generation):** Outputs final HTML report with video embeds
4. **Data Layer** (`agents/social-listening/db.ts`): PostgreSQL client managing:
- `runs` table: pipeline execution records (brief, start/end timestamps, status, final HTML)
- `cost_events` table: granular Apify call costs for budget tracking and accountability
- Supports querying, filtering (by date range), and soft deletes
```
┌─────────────────────────────────────────────────────┐
│ Frontend (Static HTML/JS) │
│ (Brief Entry, Dashboard, Runs, History, Help Tabs)│
└─────────────┬───────────────────────────────────────┘
│ HTTP + SSE
┌─────────────▼───────────────────────────────────────┐
│ Node.js Dashboard Server (port 3456) │
│ • Session auth (HMAC cookie-based) │
│ • Brief management (save/load/list) │
│ • Pipeline orchestration & SSE progress │
│ • Run history + cost tracking │
└─────────────┬───────────────────────────────────────┘
│ SQL
┌─────────────▼───────────────────────────────────────┐
│ PostgreSQL (social_listening) │
│ • Runs table (status, costs, brief data) │
│ • Brief storage (JSON documents) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Frontend (Vanilla JS) │
│ ├─ Dashboard tab (start pipeline, SSE progress) │
│ ├─ Reports tab (view, download past reports) │
│ ├─ Briefs tab (save/load client briefs JSON) │
│ └─ History tab (cost breakdown, delete runs) │
└────────────────┬────────────────────────────────────────┘
│ HTTP + SSE (port 3456)
┌─────────────────────────────────────────────────────────┐
│ Backend Server (Node.js) │
│ ├─ Auth: Cookie-based HMAC sessions │
│ ├─ POST /run → Singleton pipeline orchestration │
│ ├─ SSE /progress → Real-time stage updates + costs │
│ └─ REST: /briefs, /runs, /costs, /download/:id │
└────────┬─────────────────────────┬──────────────────────┘
│ │
↓ ↓
┌──────────────────┐ ┌─────────────────────┐
│ Pipeline (8 stages) │ PostgreSQL (pgdata) │
│ ├─ Brief validation │ ├─ runs table │
│ ├─ AI strategy review │ └─ cost_events table │
│ ├─ Apify scraping │ │
│ ├─ AI analysis │ │
│ ├─ Enrichment scrape │ │
│ ├─ Pre-report AI │ │
│ ├─ Desk research │ │
│ └─ Report gen │ │
└──────────────────┘ └─────────────────────┘
┌─────────────────────────────────────────────┐
│ External APIs │
│ ├─ Apify (TikTok, Instagram, YouTube actors)
│ ├─ Claude AI (Anthropic) │
│ └─ Azure AD (optional SSO) │
└─────────────────────────────────────────────┘
Pipeline (run.ts):
Stage 1: Brief Validation → normalize and validate client brief
Stage 2: Strategy Review → Claude suggests 3 extra hashtags
Stage 3: Discovery Scrape → Apify scrapes TikTok/Instagram/YouTube
Stage 4: Data Review → Claude analyzes trends from scraped content
Stage 5: Enrichment Scrape → fetch transcripts and metadata
Stage 6: Pre-Report Review → Claude refines findings
Stage 7: Desk Research → web search for context
Stage 8: Report Generation → HTML report with video embeds
┌─────────────────────────────────────────────────────────┐
│ Apache (Reverse Proxy on Ubuntu) │
│ ├─ Frontend static files ← /var/www/html/social-reports │
│ └─ Backend API ← :3456 │
└─────────────────────────────────────────────────────────┘
```
**Key Design Decisions:**
- Apify scraping runs **sequentially** (not parallel) to prevent budget overruns
- Budget control via `APIFY_COST_LIMIT` — pipeline stops when limit is reached
- Per-brief Apify budget with platform cost splitting
- SSE real-time progress updates with live cost tracking
- Run outputs stored as JSON; reports as HTML with native embeds (YouTube iframes, Instagram embeds, TikTok links)
- Session-based auth with HMAC-signed tokens; optional Azure AD SSO
## Dev Commands
```bash
# Local setup
npm install
# Start PostgreSQL + app via Docker
```bash
# Start full stack with Docker (PostgreSQL + app)
docker compose up -d
# Dashboard runs at http://localhost:3456
# Rebuild images (e.g., after package.json changes)
docker compose up -d --build
# CLI commands (without dashboard)
npm run pipeline # dry run (no actual scraping)
npm run pipeline:test # test mode (mock data)
npm run pipeline:live # live Apify scraping (requires APIFY_LIVE_APPROVED=true)
# Start dashboard server only (no Docker; requires manual PostgreSQL)
npm install && npm run dashboard
# Start just the dashboard server (if DB already running)
npm run dashboard
# Run pipeline CLI (dry run; no API calls)
npm run pipeline
# Run pipeline CLI (test mode; mocked data, no live Apify)
npm run pipeline:test
# Run pipeline CLI (live Apify scraping; requires APIFY_LIVE_APPROVED=true)
npm run pipeline:live
# Deploy to production server
git pull && cp frontend/* /var/www/html/social-reporting/ && docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
```
## Deployment
- **Server:** Ubuntu server (any; configured for Apache reverse proxy)
- **Deploy:**
```bash
cd /opt/social-reporting
git pull
cp frontend/* /var/www/html/social-reporting/
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
```
- **URL:** `https://your-domain.com/social-reports/`
- **Port:** 3456 (backend, proxied via Apache)
- **Service:** Docker Compose (not systemd)
- **Server:** Ubuntu + Apache (production); Docker containers at `/opt/social-reporting`, static files at `/var/www/html/social-reporting`
- **Deploy:** `git pull && cp frontend/* /var/www/html/social-reporting/ && docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build`
- **URL:** `https://your-domain.com/social-reports/` (set via `ALLOWED_ORIGIN` env var)
- **Port:** 3456 (dashboard backend; bound to 127.0.0.1 in Docker to prevent direct exposure)
- **Service:** None (containerized; managed via docker compose, not systemd)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/social-reporting-tool`
**Setup:** Run `deploy/setup.sh` on a fresh Ubuntu server for initial provisioning (Apache config, PostgreSQL init, Docker setup).
## Environment Variables
- `APIFY_TOKEN` — Apify API token (required for scraping)
- `ANTHROPIC_API_KEY` — Claude API key (required)
- `APIFY_LIVE_APPROVED``true` to enable real Apify scraping; `false` for dry run
- `APIFY_COST_LIMIT` — USD budget limit per run (default: 5)
- `TEST_MODE``true` to use mock data instead of real APIs
- `DASHBOARD_PORT` — port for Node.js backend (default: 3456)
- `DATABASE_URL` — PostgreSQL connection string
- `DASH_USER` / `DASH_PASS` — basic auth credentials for dashboard
- `SESSION_SECRET` — random secret for HMAC cookie signing
- `ALLOWED_ORIGIN` — CORS origin (e.g., for production domain)
- `AZURE_TENANT_ID` / `AZURE_CLIENT_ID` — optional Azure AD SSO config
- `DB_PASSWORD` — PostgreSQL password (used in docker-compose)
## API / Endpoints
All endpoints run on the dashboard server (default `:3456`):
Essential env vars (copy `.env.example` to `.env`):
- `POST /run` — start a new pipeline run (request body: brief JSON)
- `GET /runs` — list all historical runs with costs and status
- `GET /runs/:id` — fetch specific run details and report
- `DELETE /runs/:id` — delete a run
- `POST /briefs` — save a new brief (request body: brief JSON)
- `GET /briefs` — list all saved briefs
- `GET /briefs/:id` — load a specific brief
- `DELETE /briefs/:id` — delete a brief
- `GET /events` — SSE stream (real-time pipeline progress + costs)
Session auth via `session` cookie; responses include cost breakdowns per platform.
## Known Issues
- **Unicode/Claude API**: Fixed in commit ce916cd (ensure UTF-8 encoding in all API calls)
- **SSE reconnect**: Fixed in commit 087d1bb (POST /run only once per pipeline start)
- **Apify budget**: Runs sequentially to prevent overruns; parallel scraping disabled
- **Frontend deployment**: Apache must copy frontend files to `/var/www/html` on deploy (fixed in 7a70283)
- **No documented TODOs or breaking issues** as of latest commit
## Git
- **Remote:** `git@bitbucket.org:zlalani/social-reporting-tool.git`
- **Default branch:** main (assumed)
- **Recent focus:** Azure AD SSO, security hardening, report quality, brief management, cost tracking
- `APIFY_TOKEN` — API token for Apify account (required for scraping)
- `ANTHROPIC_API_KEY` — API key for Claude AI (required for analysis stages)
- `APIFY_LIVE_APPROVED` — Set to `true` to enable real Apify scraping; default `false` (dry runs only)
- `APIFY_COST_LIMIT` — Maximum USD spend per pipeline run (default `5.00`); pipeline aborts scraping when exceeded
- `TEST_MODE` — Set to `true` to skip live Apify calls and use mock data (overrides `APIFY_LIVE_APPROVED`)
- `DASHBOARD_PORT` — HTTP port for backend server (default `3456`)
- `DATABASE_URL` — PostgreSQL connection string (e.g., `postgresql://sl_user:password@db:5432/social_listening`)
- `DASH_USER` — Dashboard login username (default `admin`)
- `DASH_PASS` — Dashboard login password (required; must not be `changeme` in production or server refuses to start)
- `SESSION_SECRET` — Random string for HMAC signing session cookies (required in production; use `openssl rand -base64 32`)
- `ALLOWED_ORIGIN` — CORS origin for frontend requests (e.g., `https://your-domain.com`)
- `AZURE_TENANT_ID` — (Optional) Azure AD tenant ID for SSO; requires `AZURE_CLIENT_ID` to be set too
- `AZURE_CLIENT_ID` — (Optional) Azure AD app registration client ID; requires `AZURE_TENANT_ID` to be set too
## Sessions
### 2026-04-15 Fix frontend deployment to /var/www/html and

View file

@ -1,183 +1,177 @@
---
name: "Solventum Image Metadata Tool v3.1"
client: Solventum / Oliver Marketing
client: Solventum
status: active
server: ai-sandbox.oliver.solutions
tech: [Python, FastAPI, Flask, SQLite, ExifTool, OpenAI, Azure AD]
tech: [Python, FastAPI, SQLite, ExifTool, OpenAI GPT-5.2, Docker, Apache]
local_path: /Users/ai_leed/Documents/Projects/Oliver/solventum-image-metadata
deploy: docker-compose up -d
url: https://ai-sandbox.oliver.solutions/solventum-image-metadata
deploy: docker-compose up -d OR sudo systemctl restart oliver-metadata
url: https://ai-sandbox.oliver.solutions/solventum-image-metadata/
tags: [solventum, metadata, ai, openai, enterprise]
created: 2026-04-14
port: 5001
db: SQLite
service: oliver-metadata
---
## Overview
Oliver Metadata Tool v3.1 is a Flask/FastAPI-based web application for universal file metadata management and creation. It supports 300+ file formats through ExifTool integration and enables metadata ingestion from four sources: file imports (CSV/Excel/JSON), AI generation via OpenAI, manual entry, and reusable templates. The tool targets enterprise users who need to batch-process metadata across heterogeneous file types with audit logging, user authentication (local + Azure AD SSO), and AI-powered intelligent metadata generation with token tracking.
Oliver Metadata Tool is an enterprise web application for universal file metadata management and enrichment. Users upload files (300+ formats supported via ExifTool), select a metadata source (Excel lookup, AI generation via OpenAI GPT-5.2, manual entry, or templates), review and edit metadata in the browser, and download processed files with metadata written back. The tool supports PDF, images, Office documents, and video; all user actions are logged to an audit trail. Built and maintained for Solventum (internal Oliver tooling).
## Tech Stack
- **Frontend:** Jinja2 templates, HTML/CSS/JavaScript, MSAL.js (Azure AD SSO)
- **Backend:** FastAPI 0.109.0+, Flask 2.3.0+, Python 3.8+
- **Database:** SQLite (oliver_metadata.db)
- **Infrastructure:** Docker, Docker Compose, Apache reverse proxy (subpath routing)
- **AI/ML:** OpenAI GPT-4o-mini (configurable), tiktoken for token counting, tenacity for retry logic
- **Key libraries:** PyExifTool 0.5.6+, pandas, openpyxl, pdfplumber, Pillow, pytesseract, mutagen, msal (for Azure AD)
- **Frontend:** Jinja2 HTML templates, vanilla JavaScript, no framework
- **Backend:** FastAPI (primary, Python 3.11) with legacy Flask support (`web_app.py`)
- **Database:** SQLite (audit log, sessions, config)
- **Infrastructure:** Docker Compose + systemd, Gunicorn + Uvicorn workers, Apache reverse proxy (HTTPS)
- **AI/ML:** OpenAI GPT-5.2 API (Responses v1 endpoint) for metadata generation
- **File processing:** ExifTool (primary), PyPDF, python-docx, Pillow, mutagen (native handlers)
- **Auth:** Microsoft Entra ID SSO (MSAL.js client-side) + local password fallback
## Architecture
The application uses a hybrid FastAPI/Flask architecture with the following components:
The application is a monolithic Python server behind Apache HTTPS proxy. FastAPI (`app/main.py`) is the active production entrypoint, served by Gunicorn with 2 Uvicorn workers on port 5001 (loopback only). All requests to `https://ai-sandbox.oliver.solutions/solventum-image-metadata/` are proxied via Apache to the Docker container.
- **Web Framework:** Primary entry via `web_app.py` (Flask); core API likely in FastAPI
- **Metadata Engine:** ExifTool subprocess integration via PyExifTool for reading/writing 300+ formats
- **File Processing Pipeline:**
- CSV/Excel/JSON import → pandas DataFrames → smart column mapping (fuzzy matching)
- Image processing: Pillow, pytesseract (OCR), piexif (EXIF)
- PDF: pdfplumber, PyPDF2, pdf2image for text extraction
- Video: mutagen, ffmpeg-python, pymediainfo
- Office docs: python-docx, python-pptx
- **AI Generation:** OpenAI API calls with template variable substitution ({filename}, {date}, {user}, custom)
- **Authentication:**
- Local: username/password with Werkzeug password hashing
- SSO: Azure AD via MSAL (client-side MSAL.js in browser, server-side MSAL library)
- **Database:** SQLite with user management, audit logging, session tracking, AI usage metrics
- **Deployment:** Docker Compose with persistent volumes (uploads, database, output), Apache reverse proxy at root path `/solventum-image-metadata`
**Core design pattern:** Every supported file type (PDF, IMAGE, OFFICE_DOC, OFFICE_SHEET, OFFICE_PRESENTATION, VIDEO) has a corresponding **extractor** (read metadata) and **updater** (write metadata) class, all keyed by a `FileType` enum and registered in a shared registry. `FileDetector.detect_file_type(filepath)` routes incoming files by extension, then MIME type as fallback. ExifTool (via PyExifTool) is the primary handler for format coverage; native Python libraries handle specific types directly.
**Processing pipeline:**
1. **Auth** → Login (SSO or local password) routed via `app/routers/auth.py`
2. **Upload** → Files saved to `/app/uploads`, in-memory session created
3. **Metadata source** → User selects: Excel lookup, import from file, AI generation (SSE stream), manual entry, or template
4. **AI generation** → OpenAI GPT-5.2 generates title, subject, keywords per file (async via `app/services/ai_service.py`)
5. **Review** → User edits metadata inline in browser
6. **Write** → Extractor/updater pair writes metadata back to file
7. **Download** → User downloads processed files individually or as ZIP
**Key layers:**
- `app/routers/` — HTTP request handling, validation, response
- `app/services/` — Business logic (AI, auth, file ops, admin)
- `app/session/` — In-memory file-processing session management
- `src/` — Shared library (extractors, updaters, file detection, config, DB, auth)
- `templates/` — Jinja2 HTML (shared by FastAPI and legacy Flask)
- `static/` — CSS, JS, images (served by Apache in production, not Docker)
```
┌─────────────────────────────────────────────────────────┐
│ Browser / MSAL.js (Azure AD SSO) │
└────────────────────┬────────────────────────────────────┘
│ HTTPS (Apache reverse proxy)
┌─────────────────────────────────────────────────────────┐
│ Apache (ai-sandbox.oliver.solutions) │
│ └─► /solventum-image-metadata ──► localhost:5001 │
└────────────────────┬────────────────────────────────────┘
┌────────────────────────┐
│ Flask/FastAPI Server │
│ (web_app.py) │
│ :5001 in container │
└────┬──────────────┬────┘
│ │
┌──────▼──┐ ┌─────▼─────────┐
│ SQLite │ │ ExifTool │
│ Database│ │ (subprocess) │
│ (auth, │ │ Metadata I/O │
│ audit) │ │ 300+ formats │
└─────────┘ └─────┬─────────┘
┌──────▼────────┐
│ File Pipeline│
│ (PIL, OCR, │
│ pandas, etc.)│
└─────┬────────┘
┌─────▼──────┐
│ OpenAI API │
│ (GPT-4o) │
└─────────────┘
Browser (HTTPS)
Apache reverse proxy (ai-sandbox.oliver.solutions:443)
├─ /solventum-image-metadata/static → disk (/var/www/html/…)
└─ /solventum-image-metadata/* → ProxyPass to Docker
Docker container oliver-metadata-tool (127.0.0.1:5001)
├─ Gunicorn (2 UvicornWorker processes)
│ └─ FastAPI app (app/main.py)
│ ├─ Routers (HTTP)
│ ├─ Services (business logic)
│ └─ src/ (file ops, DB, auth)
├─ SQLite DB (/app/data/oliver_metadata.db)
├─ Upload directory (/app/uploads)
└─ Output directory (/app/output)
```
## Dev Commands
```bash
# Setup
# Clone and navigate
cd /Users/ai_leed/Documents/Projects/Oliver/solventum-image-metadata
# Create virtual environment
python3 -m venv venv_local
source venv_local/bin/activate
# Install dependencies
pip install -r requirements.txt
# System dependencies (macOS)
# Install system dependencies (macOS)
brew install exiftool tesseract tesseract-lang poppler
# System dependencies (Linux)
sudo apt-get install libimage-exiftool-perl tesseract-ocr tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-jpn tesseract-ocr-kor poppler-utils
# Verify ExifTool (must be 12.15+)
exiftool -ver
# Verify ExifTool
exiftool -ver # Should be 12.15+
# Configure environment
cp .env.example .env # or create .env manually
# Edit .env and set OPENAI_API_KEY, AZURE_CLIENT_ID, etc.
# Run locally
# Run FastAPI (production entrypoint)
gunicorn app.main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 1 --bind 127.0.0.1:5001 --reload
# Or run Flask legacy (simpler for local dev)
python web_app.py
# Opens http://localhost:5001 automatically
# Docker
# Open browser
# http://localhost:5001
# Test user: tester / oliveradmin (if ENABLE_TEST_USER=true)
# Docker (build and start)
docker-compose up -d
# Access at http://localhost:5001
# Database initialization (manual)
python -c "from src.database import Database; db = Database(); print('Database initialized')"
# View logs
docker-compose logs -f oliver-metadata
# Create user (interactive)
python
>>> from src.database import Database
>>> db = Database()
>>> db.create_user(username='newuser', password='password123', email='user@example.com', full_name='New User', auth_method='local')
# List users
python
>>> from src.database import Database
>>> db = Database()
>>> users = db.get_all_users()
>>> for user in users: print(f"{user['username']} - Last login: {user['last_login']}")
# Test imports
python -c "from src.database import Database; from src.config import Config; print('✅ All imports successful')"
# Stop
docker-compose down
```
## Deployment
- **Server:** ai-sandbox.oliver.solutions
- **Deploy:** `docker-compose up -d` (or `./docker-run.sh start`)
- **URL:** https://ai-sandbox.oliver.solutions/solventum-image-metadata
- **Port:** 5001 (inside container; reverse-proxied via Apache)
- **Service:** Docker Compose (systemd service not documented)
- **Server:** ai-sandbox.oliver.solutions (Ubuntu/Debian, Apache 2.4, Docker)
- **Deploy:** `docker-compose build --no-cache oliver-metadata && docker-compose up -d` OR `sudo systemctl restart oliver-metadata`
- **URL:** https://ai-sandbox.oliver.solutions/solventum-image-metadata/
- **Port:** 5001 (internal Docker container, loopback only; Apache proxies externally on 443)
- **Service:** `oliver-metadata` (systemd unit file: `deploy/oliver-metadata.service`)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/solventum-image-metadata
- **Deploy path (server):** `/var/www/oliver/`
- **Static files (server):** `/var/www/html/solventum-image-metadata/static/`
**Deployment Notes:**
- Application runs in Docker with three persistent volumes: `uploads`, `database`, `output`
- Apache reverse proxy must match `ROOT_PATH=/solventum-image-metadata` (no trailing slash)
- REDIRECT_URI in `.env` must exactly match Azure AD App Registration (includes `/auth/callback`)
- Health check: `curl http://localhost:5001/login` every 30s
- Restart policy: `unless-stopped`
**Process manager:** Gunicorn + Uvicorn workers (2 workers, 120s timeout). Health check: `curl -sf http://localhost:5001/login` every 30s.
**Docker volumes:**
- `uploads``/app/uploads` (temp uploaded files)
- `database``/app/data` (SQLite: `oliver_metadata.db`, `oliver_sessions.db`)
- `output``/app/output` (processed files, backups, reports, templates)
## Environment Variables
- `SECRET_KEY` — Flask secret key for session management; generate with `python3 -c "import secrets; print(secrets.token_hex(32))"`
- `DOCKER_MODE` — Set to `true` when running in Docker
- `ROOT_PATH` — Subpath prefix for Apache reverse proxy (e.g., `/solventum-image-metadata`); must match reverse proxy config
- `AZURE_TENANT_ID` — Azure AD tenant ID (e.g., `e519c2e6-bc6d-4fdf-8d9c-923c2f002385`)
- `AZURE_CLIENT_ID` — Azure AD application client ID (e.g., `9079054c-9620-4757-a256-23413042f1ef`)
- `AZURE_CLIENT_SECRET` — Azure AD client secret from Certificates & secrets (required for server-side MSAL flow)
- `REDIRECT_URI` — OAuth2 redirect URI; must match Azure App Registration exactly (e.g., `https://ai-sandbox.oliver.solutions/solventum-image-metadata/auth/callback`)
- `ALLOWED_TENANT_IDS` — Comma-separated list of allowed Azure tenant IDs; leave empty to allow any organizational tenant
- `OPENAI_API_KEY` — OpenAI API key for AI metadata generation (optional)
- `AI_MODEL` — OpenAI model (default: `gpt-4o-mini`)
- `MAX_TOKENS` — Max tokens for AI responses (default: `500`)
- `TEMPERATURE` — AI temperature parameter (default: `0.5`)
- `SUPERADMIN_EMAIL` — Email auto-created as admin on first startup via SSO (e.g., `vadymsamoilenko@oliver.agency`)
- `ENABLE_TEST_USER` — Enable test user account (default: `false`)
- `HTTPS_ONLY` — Enforce HTTPS (default: `true`)
- `DEBUG` — Enable Flask debug mode (default: `false`)
| Variable | Purpose |
|----------|---------|
| `OPENAI_API_KEY` | OpenAI API key for GPT-5.2 metadata generation |
| `AZURE_CLIENT_ID` | Microsoft Entra ID (Azure AD) client ID for SSO (MSAL.js) |
| `AZURE_TENANT_ID` | Microsoft Entra ID tenant ID for SSO |
| `AZURE_CLIENT_SECRET` | Optional; client secret for server-side token exchange (MSAL.js is client-side, so this is optional) |
| `ENABLE_TEST_USER` | Enable test login `tester / oliveradmin` (dev/staging only) |
| `DATABASE_URL` | SQLite connection string (default: `sqlite:///./data/oliver_metadata.db`) |
| `SESSION_STORE` | Session persistence backend (`memory` or `sqlite`) |
| `LOG_LEVEL` | Python logging level (`INFO`, `DEBUG`, `WARNING`) |
| `ADMIN_EMAILS` | Comma-separated list of superadmin email addresses |
| `FILE_UPLOAD_MAX_MB` | Max file size in MB (default: 500) |
| `AI_TIMEOUT_SECONDS` | OpenAI API timeout (default: 60) |
## API / Endpoints
Not fully documented in provided files. Known endpoints:
- `GET /login` — Login page
- `POST /auth/callback` — Azure AD OAuth2 callback
- Metadata import endpoints (file upload, AI generation, manual entry, templates)
- Metadata export/CSV endpoints
- User management endpoints (for admin)
All endpoints routed via `app/routers/`:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/login` | GET, POST | SSO (MSAL.js) or local password auth |
| `/logout` | POST | Destroy session |
| `/upload` | POST | Receive file upload, create session |
| `/metadata/extract` | POST | Extract existing metadata from uploaded file |
| `/metadata/source/excel` | POST | Lookup metadata from pre-loaded Excel |
| `/metadata/source/import` | POST | Import metadata from CSV/Excel/JSON with column mapping |
| `/metadata/source/ai` | POST | Stream AI-generated metadata via SSE |
| `/metadata/update` | POST | Write metadata back to file(s) |
| `/download` | GET | Download single file or ZIP of processed files |
| `/admin/audit` | GET | Audit log (superadmin only) |
| `/admin/users` | GET, POST, DELETE | User management (superadmin only) |
## Known Issues
- **Azure AD SSO:** Fixed in commit `0976ee9` — AADSTS900144 client_id error resolved
- **Filename collisions:** Fixed in commit `1156209` — prevented collisions breaking Excel metadata lookup
- **Background AI processing:** Implemented in commit `ebc2322` for bulk uploads (up to 100 files) with polling
- **MSAL.js loading:** Fixed in commits `ff3b89f`, `eaa12be`, `154658f` — async initialization and CDN migration to jsdelivr
- **
- **Azure AD SSO:** Last fix (commit `0976ee9`) addressed `AADSTS900144` client_id validation error; verify `AZURE_CLIENT_ID` matches Azure app registration
- **Filename collisions:** Fixed in commit `1156209`; Excel metadata lookup now handles duplicate filenames
- **AI bulk uploads:** Background processing supports up to 100 files; larger batches should be chunked
-
## Sessions
### 2026-04-14 Project catalogued

View file

@ -2,70 +2,192 @@
name: "Accessible Video Processing Platform"
client: Oliver Internal
status: active
tech: [Python, FastAPI, Celery, MongoDB, Redis, React, TypeScript, Vite, Gemini, Google TTS, ElevenLabs, Docker]
tech: [React, FastAPI, Celery, MongoDB, Redis, Google Cloud (GCS, Gemini), Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/video-accessibility
deploy: docker compose up --build
url:
deploy: ./scripts/full-deploy.sh
url: https://ai-sandbox.oliver.solutions/video-accessibility/
server: optical-web-1
tags: [oliver, video, accessibility, ai, captions, audio-description, tts, celery, mongodb]
created: 2026-04-14
last_commit: 2026-04-29
commits: 248
commits: 249
port: 8000
db: MongoDB Atlas
service: docker-compose
---
## Overview
Comprehensive AI-powered platform for generating accessible video content. Full pipeline from upload to delivery with QC workflow.
**Features implemented (85% production-ready):**
- Closed captions (Gemini 2.5 Pro)
- Audio descriptions (AI-generated + TTS)
- SDH captions (Subtitles for Deaf and Hard of Hearing)
- Descriptive transcripts
- 50+ language translation (Google Translate + cultural transcreation)
- QC workflow (reviewer approval/rejection, VTT editing)
- DCMP compliance
**20,471 lines of code** (12,198 backend + 8,273 frontend)
**video-accessibility** is an AI-powered SaaS platform that generates legally-required accessibility assets (closed captions, audio descriptions, SDH captions, transcripts) from video files. It processes videos through an asynchronous pipeline using Gemini 2.5 Pro, routes outputs through human QC workflows, supports 50+ languages with transcreation, and delivers final assets via signed GCS URLs to clients. Currently 85% production-ready and serving Oliver Internal on optical-web-1.
## Tech Stack
- **Frontend:** React 18 + Vite + TypeScript (SPA)
- **Backend:** FastAPI + Python 3.11 + Celery workers
- **Database:** MongoDB Atlas
- **Storage:** Google Cloud Storage (signed URLs)
- **Queue:** Redis + Celery
- **AI:** Gemini 2.5 Pro
- **Translation:** Google Cloud Translate API
- **TTS:** Google Cloud TTS + ElevenLabs
- **Auth:** JWT + HttpOnly refresh cookies (RBAC)
- **Infrastructure:** Docker + docker-compose
- **Frontend:** React + TypeScript (Vite dev server)
- **Backend:** FastAPI (Python 3.11+) + Pydantic
- **Async Processing:** Celery + Redis queue
- **Database:** MongoDB Atlas (documents) + Redis (queue/cache)
- **Infrastructure:** Docker Compose, Apache with mod_proxy, GCP (GCS + Gemini 2.5 Pro)
- **AI/ML:** Google Gemini 2.5 Pro (transcription), Google Translate (translation), Google TTS / ElevenLabs (audio synthesis)
- **Key libraries:** FastAPI, Celery, pymongo, google-cloud-storage, google-generativeai, python-multipart, pydantic-settings
## Architecture
```
React SPA (TypeScript)
↓ JWT auth (RBAC)
FastAPI backend
├── Celery workers (video processing pipeline)
├── MongoDB Atlas (jobs, users, assets)
├── Google Cloud Storage (video files, VTT)
└── Redis (task queue)
Pipeline phases:
1. Upload → Ingestion worker
2. Gemini 2.5 Pro → VTT captions
3. Audio Description generation
4. QC review (approve/reject/edit VTT)
5. Translation → 50+ languages
6. TTS synthesis (GCP TTS + ElevenLabs)
7. Final delivery
**Three-tier monorepo:** React SPA frontend → FastAPI backend (sync) → Celery workers (async) → persistent stores (MongoDB, Redis, GCS).
**Request flow:**
1. Browser → Apache reverse proxy
2. Apache routes API requests to FastAPI on localhost:8000
3. FastAPI enforces RBAC via JWT + `MembershipContext`, stores job state in MongoDB
4. Long-running tasks (transcription, translation, TTS, rendering) are enqueued in Redis and processed by Celery workers asynchronously
5. All file I/O (video upload, VTT generation, audio synthesis) uses GCS with V4 signed URLs (24h expiry, never cached)
6. WebSocket updates flow via Apache mod_proxy_wstunnel for real-time status
**Job state machine (16 states):**
```
CREATED → INGESTING → AI_PROCESSING → PENDING_QC → [QC_FEEDBACK ↔ PENDING_QC]
PENDING_QC → APPROVED_ENGLISH/APPROVED_SOURCE → TRANSLATING → TTS_GENERATING
→ [TTS_FAILED] → RENDERING_VIDEO → [RENDER_FAILED] → RENDERING_QC
→ PENDING_FINAL_REVIEW → COMPLETED
[REJECTED] (terminal, from any QC state)
```
### Critical Dev Note
**Always read `video_accessibility_development_plan.txt` before any dev work** — it's the authoritative source for all technical specs, API contracts, DB models.
Terminal states: `COMPLETED`, `REJECTED`. Manual-retry states: `TTS_FAILED`, `RENDER_FAILED`.
**Key components:**
- `api/v1/routes_*.py` — HTTP endpoints + RBAC
- `models/job.py` — Job document schema + `JobStatus` enum
- `services/gemini.py` — Gemini API wrapper
- `services/gcs.py` — GCS signed URL generation
- `services/language_qc.py` — Per-language QC state machine
- `services/tts.py` — TTS service wrapper
- `tasks/ingest_and_ai.py`, `tasks/translate_and_synthesize.py` — Celery tasks
```
┌─────────────────────────────────────────────────────────────┐
│ Browser (React + TS) │
└──────────────┬──────────────────────────────────────────────┘
┌──────────────▼──────────────────────────────────────────────┐
│ Apache (mod_rewrite, mod_proxy, mod_proxy_wstunnel) │
│ *.ai-sandbox.oliver.solutions (SSL/TLS) │
└──────────────┬──────────────────────────────────────────────┘
│ HTTP + WS
┌──────────────▼──────────────────────────────────────────────┐
│ FastAPI (port 8000) — RBAC, JWT auth, job orchestration │
└──┬────────────────────────┬─────────────────────────┬───────┘
│ │ │
▼ ▼ ▼
MongoDB Atlas Redis (queue) GCS Bucket
(job docs, users) (Celery tasks) (videos, VTTs)
+ rate limiting + audio files
+ final assets
│ Celery Workers
│ (transcription, TTS, render)
└─ External APIs
- Gemini 2.5 Pro
- Google Translate
- Google TTS / ElevenLabs
```
## Dev Commands
```bash
# First-time local setup
cp .env.prod.example .env.local # Fill in values
cp frontend/.env.example frontend/.env.local
cp ./secrets/gcp-credentials.json # Place service account JSON
chmod 600 ./secrets/gcp-credentials.json
# Start local environment (all services via Docker Compose)
./scripts/run-local.sh
# Start frontend dev server (separate terminal)
cd frontend && npm install && npm run dev
# Useful local commands
./scripts/run-local.sh --rebuild # Rebuild after code changes
./scripts/run-local.sh --stop # Stop all services
docker compose logs -f # Tail all logs
docker compose logs -f api # Tail API logs
docker compose logs -f worker # Tail worker logs
docker compose restart api # Restart API container
# Run tests
npm run test # Frontend tests
pytest # Backend tests (in container or locally)
# Access local services
# API: http://localhost:8003
# API docs (Swagger): http://localhost:8003/docs
# Frontend: http://localhost:6001/video-accessibility
# MongoDB: mongodb://localhost:27017
# Redis: redis://localhost:6379
```
**Local test credentials (Docker environment only):**
| Role | Email | Password |
|------|-------|---------|
| Admin | admin@example.com | admin |
| Reviewer | reviewer@example.com | reviewer |
| Client | client@example.com | client123 |
*(Production uses Microsoft SSO)*
## Deployment
- **Run:** `docker compose up --build`
- **Server:** optical-web-1 (GCP VM, 32GB RAM, 8 CPU)
- **Deploy path:** `/opt/video-accessibility/`
- **Deploy command:** `./scripts/full-deploy.sh` (pulls code, builds images, restarts containers, builds frontend bundle, copies to Apache webroot)
- **Frontend-only deploy:** `./scripts/build-frontend.sh`
- **URL:** https://ai-sandbox.oliver.solutions/video-accessibility/
- **Port:** 8000 (FastAPI), 6379 (Redis), 27017 (MongoDB)
- **Service:** Docker Compose (no systemd service)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/video-accessibility`
**Post-deploy verification:**
```bash
curl https://ai-sandbox.oliver.solutions/video-accessibility-back/health
docker compose ps
# Visit https://ai-sandbox.oliver.solutions/video-accessibility/ in browser
```
**URL Map (all reverse-proxied through Apache):**
| Endpoint | URL |
|----------|-----|
| Frontend SPA | `https://ai-sandbox.oliver.solutions/video-accessibility/` |
| Backend API | `https://ai-sandbox.oliver.solutions/video-accessibility-back/` |
| API health | `https://ai-sandbox.oliver.solutions/video-accessibility-back/health` |
| API docs | `https://ai-sandbox.oliver.solutions/video-accessibility-back/docs` |
| WebSocket | `wss://ai-sandbox.oliver.solutions/video-accessibility-back/api/v1/ws/` |
## Environment Variables
Key env vars (see `.env.prod.example` for complete list):
- `MONGODB_URL` — MongoDB Atlas connection string
- `REDIS_URL` — Redis queue URL
- `GCS_PROJECT_ID` — GCP project ID (optical-414516)
- `GCS_BUCKET_NAME` — GCS bucket name (accessible-video)
- `GOOGLE_APPLICATION_CREDENTIALS` — Path to GCP service account JSON
- `GEMINI_API_KEY` — Google Gemini API key
- `GOOGLE_TRANSLATE_API_KEY` — Google Translate API key
- `ELEVENLABS_API_KEY` — ElevenLabs API key (TTS fallback)
- `JWT_SECRET_KEY` — Secret for JWT signing
- `FRONTEND_URL` — Frontend domain for CORS
- `BACKEND_URL` — Backend domain for API
- `CELERY_BROKER_URL` — Redis URL for task queue
- `MS_SSO_CLIENT_ID`, `MS_SSO_CLIENT_SECRET` — Microsoft OAuth (production only)
## API / Endpoints
**Core endpoints (FastAPI, RESTful + WebSocket):**
- `POST /api/v1/jobs` — Create job from video upload
- `GET /api/v1/jobs/{jobId}` — Fetch job state
- `GET /api/v1/jobs` — List all jobs (paginated, with filters)
- `POST /api/v1/jobs/{jobId}/qc/approve` — QC reviewer approves captions
- `POST /api/v1/jobs/{jobId}/qc/reject` — QC reviewer rejects
- `POST /api/v1/jobs/{jobId}/qc/feedback` — QC reviewer sends feedback
- `POST /api/v1/jobs/{jobId}/final-review/approve` — PM final approval
- `
## Timeline / Git History
| Date | Change |
|------|--------|
@ -270,6 +392,8 @@ Pipeline phases:
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 2026-04-29 | Test audit | Remove unused named imports, add inline dynamic imports | 6 files across codebase |
| 2026-04-29 | Audit suite setup | TypeScript type errors fixed, inline imports added | 6 project files |
| 2026-04-29 | Video-accessibility audit | Add --no-root flag to Poetry install commands | Dockerfile, Dockerfile.whisper-service |
| 2026-04-29 | Audit full stack | Architecture review, dependency sync validation, security baseline | pyproject.toml, poetry.lock, Dockerfile |
| 2026-04-29 | Full audit | Dependency resolution via Docker, Poetry lock regeneration | poetry.lock, deployment scripts |
@ -336,4 +460,4 @@ Pipeline phases:
## Related
- [[pdf-accessibility/PDF Accessibility Checker]]
- [[sandbox-notebookllamalm-nextjs/Sandbox NotebookLM]] (same infrastructure approach)
- [[sandbox-notebookllamalm-nextjs/Sandbox NotebookLM]] (same infrastructure approach)

View file

@ -311,3 +311,18 @@ tags: [daily]
- 14:37 (<1min) | `video-accessibility`
- **Asked:** Conduct a complete audit, testing, and documentation of the video-accessibility platform.
- **Done:** Identified Poetry 2.x installation issue in Docker containers and provided fixes for Dockerfile and Dockerfile.whisper-service by adding `--no-root` flag.
- 14:42 | `aimpress`
- **Asked:** Install and configure Glance widget framework, then review its documentation and capabilities.
- **Done:** Analyzed Glance API structure and added two widgets to Home dashboard for torrent speeds/stats and downloads list.
- 14:43 | `video-accessibility`
- **Asked:** Conduct a complete audit, testing, and documentation of the video-accessibility platform.
- **Done:** Resolved 6 TypeScript type-checking errors across multiple files using inline imports.
- 14:48 | `Oliver (root)`
- **Asked:** Requested reviewing documentation pipeline output from video-accessibility project | Analyzed generated docs and identified that SOURCE_FILES list needs updating to include AGENTS.md and docs/ for proper enrichment | documentation-pipeline configuration
- **Done:**
- 14:48 | `Oliver (root)`
- **Asked:** User requested to run documentation pipeline on video-accessibility project and discuss whether to apply it across all projects.
- **Done:** Initiated enrichment process across all 29 projects using Haiku model with prioritized source files.
- 14:49 | `Oliver (root)`
- **Asked:** Asked | Developer ran documentation-pipeline skill on video-accessibility project and wants to discuss relevance of generated docs before scaling to all projects
- **Done:** Done | Reviewed enrichment process running across 29 projects; confirmed Haiku reads priority sources (AGENTS.md, architecture.md, infrastructure.md, runbook.md) for richer documentation