---
name: "PIMCO Chart Generator"
client: PIMCO
status: active
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
url: https://ai-sandbox.oliver.solutions/Pimco-charts
tags: [pimco, charts, svg, ai, publication-quality]
created: 2026-04-14
port: 8569
---

## Overview

**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

- **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

- **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
**Asked:** Fix bar chart rendering failures and axis control application issues in the charting system.
**Done:** Identified chart rendering problems (bar charts failing, missing axes/legends) and created plan to address chart type handling and axis controls.

### 2026-04-28 – (Haiku error: <urlopen error [Errno 65]
**Asked:** (Haiku error: <urlopen error [Errno 65] No route to host>)
**Done:** —

### 2026-04-28 – Fix bar chart rendering failures and
**Asked:** Fix bar chart rendering failures and axis control issues in chart output.
**Done:** Created export backend with Playwright/Chromium renderer and updated main.py to support proper font rendering and PDF/PNG export.

### 2026-04-28 – Fix chart rendering issues (bar charts
**Asked:** Fix chart rendering issues (bar charts failing, axis controls not applying) and improve export quality (fonts, InDesign compatibility).
**Done:** Implemented Playwright-based PNG/PDF exporter with proper font handling and created 14 file changes including new export.py module to resolve rendering and font issues.

### 2026-04-28 – (Haiku error: <urlopen error [Errno 8]
**Asked:** (Haiku error: <urlopen error [Errno 8] nodename nor servname provided, or not known>)
**Done:** —

### 2026-04-14 – Project catalogued
**Done:** Added to Obsidian second brain.

---
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 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 | — |