vault backup: 2026-04-29 14:50:31
This commit is contained in:
parent
f6147c9e89
commit
c352ac8e52
29 changed files with 3801 additions and 2305 deletions
|
|
@ -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 | — |
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 (~2–4 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
@ -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 (0–10 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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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: 2–4 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 |
|
||||
|
|
|
|||
|
|
@ -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 60–180s)
|
||||
- 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 60–180 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
|
||||
|
|
@ -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 03fd4be–ed638ff; 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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 3–5 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 Sonnet–powered recommendations.
|
||||
**OliVAS** (OLIVER Visual Attention Suite) is an open-source web application that predicts where humans look during the first 3–5 seconds of viewing an image using DeepGaze ML models. It generates saliency heatmaps, gaze sequence predictions, hotspot analysis, and a Design Effectiveness Score (0–100) 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 30–60 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
|
||||
|
|
|
|||
|
|
@ -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 2–17 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 5435–5535)
|
||||
- `OSOP_REDIS_PORT` — Redis host port (auto-selected, default 6380, range 6380–6480)
|
||||
- `OSOP_BACKEND_PORT` — FastAPI host port (auto-selected, default 8003, range 8003–8099)
|
||||
- `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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 | — |
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
@ -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 283b31e7–b4978989; 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]]
|
||||
|
|
@ -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 (0–1)
|
||||
- **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 (0–1 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 0–1) │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌────▼──────────────┐
|
||||
│ 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue