ppt-tool/implementation_plan.md
Vadym Samoilenko cf21ba4516 Phase 1-2: Foundation + Admin Panel & Client Management
Phase 1 (Foundation):
- Project restructure (presenton-main → backend/ + frontend/)
- Database schema (8 new models, Alembic config, seed script)
- Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware)
- RBAC (access_service, rbac_middleware, admin routers)
- Audit logging (fire-and-forget, AuditMiddleware, admin router)
- i18n (react-i18next with 5 namespace files)

Phase 2 (Admin Panel & Client Management):
- Admin panel shell (sidebar layout, role guard, 12 pages)
- Redux admin slice with 18 async thunks
- User management (role changes, deactivation)
- Client management (CRUD, brand config, team management)
- Brand config editor (colors, fonts, logos, voice rules)
- Master deck upload & parser (PPTX → HTML → React pipeline)
- Audit log viewer with filters and CSV/JSON export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:37:17 +00:00

61 KiB

OLIVER DeckForge — Implementation Plan

Reference: concept.md for full project concept and decisions. Base: presenton-main/ (fork + restructure)


Phase 1: Foundation

1. Project Setup & Restructure

Fork Presenton, reorganize file structure, strip Electron shell, configure for web-only deployment.

Backend restructure:

  • Copy presenton-main/servers/fastapi/backend/
  • Copy presenton-main/servers/nextjs/frontend/
  • Remove presenton-main/electron/ (Electron shell — not needed)
  • Remove Mixpanel tracking references (MixpanelInitializer.tsx, mixpanel.ts, mixpanel-browser dep)
  • Update all internal import paths after restructure
  • Create new root docker-compose.yml with services:
    • web (Next.js frontend, port 3000)
    • api (FastAPI backend, port 8000)
    • worker (ARQ worker, same image as api, different entrypoint)
    • postgres (PostgreSQL 16 with TDE-ready config)
    • redis (Redis 7 for job queue)
    • nginx (reverse proxy, port 80/443)
  • Create new root Dockerfile (multi-stage: Python + Node)
  • Create Dockerfile.dev for development with hot-reload
  • Update nginx.conf: route /api/* → FastAPI, /admin/* and /* → Next.js
  • Create .env.example with all required variables
  • Create Makefile with common commands (make dev, make build, make migrate, make test)
  • Verify docker compose up boots all services and health checks pass

Verification:

  • docker compose up starts all 5 services
  • http://localhost loads Next.js app
  • http://localhost/api/docs loads FastAPI Swagger UI

2. Database Schema & Migrations

Set up PostgreSQL as primary database, add Alembic for migrations, create all new tables.

Setup:

  • Add alembic>=1.15 to pyproject.toml
  • Run alembic init migrations in backend/
  • Configure alembic.ini and env.py to use async PostgreSQL + SQLModel metadata
  • Set DATABASE_URL=postgresql+asyncpg://deckforge:deckforge@postgres:5432/deckforge in .env
  • Remove SQLite container DB logic (container.db, get_container_session)

New models (create in backend/models/sql/):

  • user.pyUserModel

    id: UUID (PK)
    azure_oid: str (unique, from Azure AD)
    email: str (unique)
    display_name: str
    role: Enum(super_admin, client_admin, user)
    is_active: bool (default True)
    last_login_at: datetime (nullable)
    created_at: datetime
    updated_at: datetime
    
  • client.pyClientModel

    id: UUID (PK)
    name: str
    slug: str (unique, URL-safe)
    logo_path: str (nullable)
    retention_days: int (nullable — null = unlimited)
    review_policy: Enum(self_approve, require_reviewer) (default self_approve)
    is_active: bool (default True)
    created_at: datetime
    updated_at: datetime
    
  • team.pyTeamModel

    id: UUID (PK)
    name: str
    client_id: UUID (FK → ClientModel, nullable for Oliver Team)
    is_default: bool (default False — True only for Oliver Team)
    created_at: datetime
    
  • team_membership.pyTeamMembershipModel

    id: UUID (PK)
    user_id: UUID (FK → UserModel)
    team_id: UUID (FK → TeamModel)
    assigned_by: UUID (FK → UserModel, nullable)
    assigned_at: datetime
    UNIQUE(user_id, team_id)
    
  • brand_config.pyBrandConfigModel

    id: UUID (PK)
    client_id: UUID (FK → ClientModel, unique)
    primary_colors: JSON (list of hex codes)
    secondary_colors: JSON (list of hex codes)
    fonts: JSON ({heading: str, body: str, accent: str})
    logo_paths: JSON (list of file paths)
    voice_rules: text (nullable — brand voice text rules)
    voice_examples: JSON (nullable — [{good: str, bad: str}])
    guideline_doc_path: str (nullable — uploaded brand guide PDF/DOCX)
    created_at: datetime
    updated_at: datetime
    
  • master_deck.pyMasterDeckModel

    id: UUID (PK)
    client_id: UUID (FK → ClientModel)
    name: str
    description: str (nullable)
    original_file_path: str
    thumbnail_path: str (nullable)
    parsed_config: JSON (nullable — extracted themes, colors, fonts)
    layouts: JSON (nullable — list of {id, name, type, description, json_schema, react_code})
    parse_status: Enum(pending, processing, completed, failed)
    is_active: bool (default True)
    created_at: datetime
    updated_at: datetime
    
  • audit_log.pyAuditLogModel

    id: UUID (PK)
    user_id: UUID (FK → UserModel, nullable — system actions)
    action: str (e.g., "presentation.create", "master_deck.upload", "user.role_change")
    resource_type: str (e.g., "presentation", "master_deck", "user")
    resource_id: UUID (nullable)
    client_id: UUID (nullable — for client-scoped actions)
    details: JSON (nullable — action-specific metadata)
    ip_address: str (nullable)
    created_at: datetime (indexed)
    
  • job.pyJobModel

    id: UUID (PK)
    user_id: UUID (FK → UserModel)
    client_id: UUID (FK → ClientModel)
    presentation_id: UUID (FK → PresentationModel, nullable — set after creation)
    job_type: str (e.g., "generate_presentation", "parse_master_deck")
    status: Enum(queued, processing, completed, failed)
    progress: int (0-100)
    progress_message: str (nullable)
    error_message: str (nullable)
    created_at: datetime
    started_at: datetime (nullable)
    completed_at: datetime (nullable)
    

Modify existing models:

  • presentation.py — extend PresentationModel:

    + owner_id: UUID (FK → UserModel)
    + client_id: UUID (FK → ClientModel)
    + master_deck_id: UUID (FK → MasterDeckModel, nullable)
    + status: Enum(draft, in_review, approved) (default draft)
    + review_comment: text (nullable)
    + source_type: Enum(brief, url, manual)
    + is_saved: bool (default False — "Save to library" flag)
    + deleted_at: datetime (nullable — soft delete for GDPR)
    
  • slide.py — extend SlideModel:

    + deleted_at: datetime (nullable — soft delete)
    

Migration:

  • Generate initial Alembic migration: alembic revision --autogenerate -m "initial_schema"
  • Create seed script: auto-create "Oliver Team" (is_default=True) on first run
  • Test: alembic upgrade head creates all tables in PostgreSQL

Verification:

  • alembic upgrade head runs without errors
  • All tables visible in PostgreSQL
  • Seed script creates Oliver Team
  • Existing Presenton tables (PresentationModel, SlideModel) coexist with new tables

3. Auth Module (Azure AD SSO)

Implement Microsoft Azure AD OAuth 2.0 login, JWT session management, and auth middleware.

Dependencies: pip install msal>=1.31 python-jose[cryptography]>=3.3 httpx>=0.28

Backend:

  • Create backend/services/auth_service.py:

    • AuthService class with:
      • get_authorization_url() → returns Azure AD OAuth URL with redirect_uri
      • exchange_code_for_token(code: str) → exchanges auth code for access/id token via MSAL
      • validate_token(token: str) → validates JWT, returns claims (oid, email, name)
      • get_or_create_user(claims: dict) → find by azure_oid or create new UserModel (role=user by default)
      • create_session_jwt(user: UserModel) → sign app-level JWT with user_id, role, expiry (24h)
      • refresh_session(token: str) → refresh if near expiry
    • Auto-add new users to Oliver Team (default team)
  • Create backend/api/v1/auth/router.py:

    • GET /api/v1/auth/login → redirect to Azure AD authorize URL
    • GET /api/v1/auth/callback → exchange code, create/update user, set JWT cookie, redirect to dashboard
    • GET /api/v1/auth/me → return current user (id, email, name, role, teams, clients)
    • POST /api/v1/auth/logout → clear JWT cookie
    • Register router in api/v1/ppt/router.py (or new api/v1/router.py)
  • Create backend/api/middlewares/auth_middleware.py:

    • AuthMiddleware: extract JWT from cookie/header → validate → attach request.state.user (UserModel)
    • Skip auth for: /api/v1/auth/*, /api/docs, /api/openapi.json, /api/health
    • Return 401 for invalid/missing token
  • Create backend/utils/auth_dependencies.py:

    • get_current_user(request) → UserModel (FastAPI Depends)
    • require_role(role: str) → dependency that checks request.state.user.role
    • require_super_admin → shortcut dependency
    • require_client_admin → shortcut dependency
  • Add env vars to .env.example:

    AZURE_AD_TENANT_ID=
    AZURE_AD_CLIENT_ID=
    AZURE_AD_CLIENT_SECRET=
    AZURE_AD_REDIRECT_URI=http://localhost/api/v1/auth/callback
    JWT_SECRET_KEY=<random-256-bit-key>
    

Frontend:

  • Create frontend/store/slices/authSlice.ts:

    • State: {user: User | null, isLoading: bool, isAuthenticated: bool}
    • Actions: fetchCurrentUser() (calls /api/v1/auth/me), logout()
    • Types: User {id, email, displayName, role, teams: Team[], clients: Client[]}
  • Create frontend/components/AuthGuard.tsx:

    • Wraps app; redirects to /api/v1/auth/login if not authenticated
    • Shows loading state while checking auth
  • Create frontend/app/login/page.tsx:

    • Simple login page with "Sign in with Microsoft" button
    • Button redirects to /api/v1/auth/login
  • Modify frontend/app/layout.tsx:

    • Wrap app with AuthGuard and Redux Provider
    • Call fetchCurrentUser() on mount

Verification:

  • Click "Sign in with Microsoft" → Azure AD login → redirect back → user created in DB
  • /api/v1/auth/me returns user with role and teams
  • Unauthenticated requests to /api/v1/ppt/* return 401
  • New user auto-added to Oliver Team

4. RBAC & Team Management

Role-based access control middleware and team/client access enforcement.

Backend:

  • Create backend/api/middlewares/rbac_middleware.py:

    • check_client_access(user: UserModel, client_id: UUID) → verify user belongs to a team for this client (or is super_admin)
    • check_team_admin(user: UserModel, client_id: UUID) → verify user is client_admin for this client or super_admin
    • Both raise HTTPException(403) on failure
  • Create backend/api/v1/admin/users_router.py (Super Admin only):

    • GET /api/v1/admin/users → list all users with their roles and team memberships
    • GET /api/v1/admin/users/{id} → user detail
    • PUT /api/v1/admin/users/{id}/role → change role (super_admin, client_admin, user)
    • DELETE /api/v1/admin/users/{id} → deactivate user (soft: is_active=False)
    • All endpoints protected with require_super_admin dependency
  • Create backend/api/v1/admin/teams_router.py:

    • POST /api/v1/admin/teams → create team for a client (Client Admin for that client, or Super Admin)
    • GET /api/v1/admin/teams → list teams (filtered by user's accessible clients)
    • GET /api/v1/admin/teams/{id} → team detail with members
    • POST /api/v1/admin/teams/{id}/members → add user to team (body: {user_id})
    • DELETE /api/v1/admin/teams/{id}/members/{user_id} → remove user from team
      • Enforce: transfer ownership of user's presentations in this client before removal
    • DELETE /api/v1/admin/teams/{id} → delete team (Super Admin only, cannot delete Oliver Team)
  • Create backend/api/v1/admin/clients_router.py:

    • POST /api/v1/admin/clients → create client (Super Admin only)
    • GET /api/v1/admin/clients → list clients (filtered: Super Admin sees all, others see assigned)
    • GET /api/v1/admin/clients/{id} → client detail
    • PUT /api/v1/admin/clients/{id} → update client (Client Admin or Super Admin)
    • DELETE /api/v1/admin/clients/{id} → deactivate client (Super Admin only, soft delete)
    • When creating client: auto-create a team with same name
  • Create backend/services/access_service.py:

    • get_accessible_client_ids(user: UserModel) → list[UUID] — returns client IDs user can access via team membership
    • get_accessible_clients(user: UserModel) → list[ClientModel] — returns full client objects
    • Used throughout the app for filtering queries
  • Modify all existing presentation endpoints (backend/api/v1/ppt/endpoints/presentation.py):

    • Add user: UserModel = Depends(get_current_user) to all handlers
    • Filter get_all_presentations by user's accessible clients
    • Check check_client_access(user, client_id) on create/generate
    • Check ownership or client access on get/update/delete

Verification:

  • Super Admin can see all clients, users, teams
  • Client Admin can manage only assigned clients and their teams
  • User can only see presentations for their assigned clients
  • Removing user from team requires ownership transfer of their decks
  • Oliver Team cannot be deleted
  • Users auto-join Oliver Team on creation

5. Audit Logging

Full audit trail for GDPR compliance with CSV/JSON export.

Backend:

  • Create backend/services/audit_service.py:

    • AuditService class:
      • log(user_id, action, resource_type, resource_id, client_id, details, ip_address) → insert AuditLogModel
      • Use background task (not blocking request) via asyncio.create_task()
      • query(filters: AuditLogFilter) → list[AuditLogModel] — paginated, filterable by date range, user, action, client
      • export_csv(filters) → StreamingResponse — CSV export
      • export_json(filters) → StreamingResponse — JSON export
  • Create backend/api/middlewares/audit_middleware.py:

    • Auto-log middleware for mutating requests (POST, PUT, DELETE)
    • Captures: user_id (from auth), action (from endpoint name), IP (from request)
    • Non-blocking: fires and forgets the log insert
  • Create backend/api/v1/admin/audit_router.py:

    • GET /api/v1/admin/audit-log → paginated audit log (Super Admin: all, Client Admin: own clients)
    • GET /api/v1/admin/audit-log/export?format=csv|json → download filtered audit log
    • Filters: date_from, date_to, user_id, action, client_id, resource_type
  • Add audit calls to critical actions:

    • Auth: login, logout, role change
    • Clients: create, update, delete
    • Teams: create, add/remove member
    • Master decks: upload, parse, delete
    • Presentations: create, generate, export, status change, delete
    • Brand config: create, update

Verification:

  • Generate a presentation → audit log shows entries for each step
  • Export audit log as CSV → opens in Excel with correct columns
  • Client Admin can only see logs for their clients
  • Super Admin sees all logs

6. i18n Setup

Configure react-i18next for English MVP with structure ready for future languages.

Frontend:

  • Install: npm install react-i18next i18next i18next-http-backend i18next-browser-languagedetector
  • Create frontend/i18n/ directory:
    • i18n.ts — i18next initialization config
    • locales/en/common.json — common strings (buttons, labels, errors)
    • locales/en/auth.json — auth-related strings
    • locales/en/admin.json — admin panel strings
    • locales/en/wizard.json — wizard step strings
    • locales/en/editor.json — slide editor strings
  • Create frontend/i18n/I18nProvider.tsx — wraps app with I18nextProvider
  • Update frontend/app/layout.tsx — add I18nProvider
  • Replace hardcoded strings in existing Presenton components with t() calls (incremental — do this as we touch each component)

Verification:

  • App loads in English
  • All new UI strings use t('key') pattern
  • Adding a new locale JSON file makes it available

Phase 2: Admin Panel & Client Management

7. Admin Panel Frontend Shell

Create the admin section layout, navigation, and page structure.

Frontend:

  • Create frontend/app/admin/layout.tsx:

    • Sidebar navigation with sections:
      • Users & Roles (Super Admin only)
      • Clients (filtered by role)
      • Teams (filtered by role)
      • Master Decks (filtered by role)
      • Brand Config (filtered by role)
      • Audit Log (Super Admin: full, Client Admin: own clients)
      • Analytics (Super Admin: full, Client Admin: own clients)
      • System Settings (Super Admin only: LLM config, image provider config)
    • Role-based sidebar item visibility
    • Breadcrumb navigation
    • Responsive layout (desktop only, 1280px+)
  • Create admin pages (skeleton — data integration in subsequent steps):

    • frontend/app/admin/page.tsx → dashboard/overview redirect
    • frontend/app/admin/users/page.tsx → user list + role management
    • frontend/app/admin/users/[id]/page.tsx → user detail + team assignments
    • frontend/app/admin/clients/page.tsx → client list
    • frontend/app/admin/clients/[id]/page.tsx → client detail (tabs: info, teams, master decks, brand)
    • frontend/app/admin/clients/[id]/teams/page.tsx → team management for client
    • frontend/app/admin/clients/[id]/master-decks/page.tsx → master deck list for client
    • frontend/app/admin/clients/[id]/brand/page.tsx → brand config editor
    • frontend/app/admin/audit/page.tsx → audit log viewer with filters + export
    • frontend/app/admin/analytics/page.tsx → analytics dashboard
    • frontend/app/admin/settings/page.tsx → system settings (LLM, image providers)
  • Create frontend/store/slices/adminSlice.ts:

    • State for users list, clients list, teams list
    • CRUD async thunks calling admin API endpoints
  • Create admin-specific components:

    • frontend/app/admin/components/AdminSidebar.tsx
    • frontend/app/admin/components/UserTable.tsx
    • frontend/app/admin/components/ClientCard.tsx
    • frontend/app/admin/components/RoleBadge.tsx
    • frontend/app/admin/components/AuditLogTable.tsx
    • frontend/app/admin/components/DataExportButton.tsx (CSV/JSON)

Verification:

  • /admin accessible only to Super Admin and Client Admin roles
  • User role sees no admin link in navigation
  • Sidebar items filtered by role
  • All pages render skeleton/empty states

8. Client & Brand Management

Full CRUD for clients, brand configuration, and frontend integration.

Backend:

  • Implement clients_router.py endpoints with full validation (from step 4)
  • Create backend/api/v1/admin/brand_config_router.py:
    • GET /api/v1/admin/clients/{client_id}/brand → get brand config
    • PUT /api/v1/admin/clients/{client_id}/brand → update brand config (colors, fonts, voice rules, voice examples)
    • POST /api/v1/admin/clients/{client_id}/brand/logo → upload logo file
    • POST /api/v1/admin/clients/{client_id}/brand/guideline → upload brand guide document (PDF/DOCX)
    • DELETE /api/v1/admin/clients/{client_id}/brand/logo/{index} → remove logo
    • All protected with check_team_admin(user, client_id)

Frontend:

  • Wire frontend/app/admin/clients/page.tsx:

    • Client list as cards/table with name, logo, team count, deck count, status
    • "New Client" button (Super Admin only)
    • Client create/edit modal with name, slug, retention policy, review policy
  • Wire frontend/app/admin/clients/[id]/page.tsx:

    • Tabs: Overview | Teams | Master Decks | Brand Config
    • Overview: client info, quick stats
  • Wire frontend/app/admin/clients/[id]/brand/page.tsx:

    • Color pickers for primary/secondary palettes
    • Font selectors (heading, body, accent)
    • Logo upload area (drag & drop, multiple logos)
    • Voice rules textarea
    • Voice examples table: good example ↔ bad example pairs
    • Brand guideline document upload (PDF/DOCX)
    • Preview panel showing how brand config looks applied to a sample slide

Verification:

  • Create client "Nike" → team auto-created → appears in client list
  • Upload Nike logo → appears in brand config
  • Set brand colors/fonts → saved to DB
  • Upload brand guideline PDF → file stored, path in DB
  • Client Admin for Nike can edit Nike brand, cannot see Adidas

9. Master Deck Upload & Parser

Upload client PPTX master decks, auto-detect layout types, admin review.

Backend:

  • Create backend/api/v1/admin/master_decks_router.py:

    • POST /api/v1/admin/clients/{client_id}/master-decks → upload PPTX file
      • Save file to data/clients/{client_id}/master_decks/{deck_id}/original.pptx
      • Create MasterDeckModel with parse_status=pending
      • Enqueue parse job to Redis
      • Return deck_id + status
    • GET /api/v1/admin/clients/{client_id}/master-decks → list master decks
    • GET /api/v1/admin/master-decks/{id} → deck detail with parsed layouts
    • PUT /api/v1/admin/master-decks/{id} → update name, description, active status
    • PUT /api/v1/admin/master-decks/{id}/layouts/{layout_index} → edit parsed layout (admin review: rename, recategorize type, edit react code)
    • DELETE /api/v1/admin/master-decks/{id} → soft delete
  • Create backend/services/master_deck_parser_service.py:

    • MasterDeckParserService class:

      • parse(deck_id: UUID) → main parse pipeline:
        1. Open PPTX with python-pptx
        2. Enumerate prs.slide_masters[0].slide_layouts → extract each layout
        3. For each layout:
          • Extract placeholder types and positions
          • Render screenshot via Puppeteer/LibreOffice (reuse existing pptx_slides logic)
          • Extract OXML
        4. Extract theme: a:clrScheme → color palette, a:fontScheme → font config
        5. Extract logos/images from master slide backgrounds
        6. For each layout screenshot + OXML:
          • Call existing slide_to_html pipeline (vision LLM converts screenshot + OXML → HTML)
          • Call existing html_to_react pipeline (LLM converts HTML → React + Zod schema)
          • Auto-classify layout type (title, content, chart, comparison, quote, table, metrics, timeline, image) based on placeholder analysis + LLM classification
        7. Store results in MasterDeckModel.parsed_config and layouts JSON
        8. Update parse_status=completed
      • get_layout_for_content_type(deck_id, content_type) → layout — find best matching layout
    • Reuse from Presenton:

      • backend/api/v1/ppt/endpoints/pptx_slides.py — PPTX parsing logic
      • backend/api/v1/ppt/endpoints/slide_to_html.py — vision LLM slide-to-HTML
      • backend/api/v1/ppt/endpoints/html_to_react.py — HTML-to-React conversion

Frontend:

  • Wire frontend/app/admin/clients/[id]/master-decks/page.tsx:
    • Upload button (drag & drop PPTX)
    • List of uploaded master decks with parse status badge (pending/processing/completed/failed)
    • Click deck → expand to show parsed layouts as thumbnail grid
    • Each layout card shows: thumbnail, auto-detected type, name
    • Click layout → edit modal: rename, change type category, edit/regenerate React code
    • "Reparse" button to re-run parser

Verification:

  • Upload a client PPTX → parse job queued → status changes to processing → completed
  • Parsed layouts visible as thumbnails with auto-detected types
  • Admin can rename layouts and recategorize types
  • Bad PPTX (no slide masters) → system still extracts patterns from content slides
  • Layout React code renders correctly in preview

Phase 3: Content Pipeline

10. File Upload & Document Parsing

Enhance document upload for briefs + attachments with parsing.

Backend:

  • Modify backend/api/v1/ppt/endpoints/files.py:

    • Extend upload endpoint to accept multiple files
    • Classify uploads: brief (DOCX primary), attachments (Excel/CSV/images/PDF), URL reference
    • Store files at data/presentations/{presentation_id}/uploads/
    • Return parsed file info for each uploaded file
  • Create backend/services/attachment_parser_service.py:

    • parse_excel(file_path) → list[TableData] — extract sheets → structured table data (via openpyxl)
    • parse_csv(file_path) → TableData — parse CSV into structured table data
    • extract_images(file_path) → list[ImageInfo] — extract images from any doc format
    • parse_pdf_text(file_path) → str — extract text from PDF (via pdfplumber, already a dep)
    • Output model: TableData {headers: list[str], rows: list[list[Any]], title: str | None}
  • Add deps: pip install openpyxl>=3.1

  • Modify backend/services/documents_loader.py:

    • Add load_url(url: str) → str (secondary input):
      • Fetch URL via aiohttp (already a dep)
      • Extract article via trafilatura or readability-lxml
      • Return markdown
    • Add deps: pip install trafilatura>=2.0

Verification:

  • Upload DOCX brief → parsed to markdown, text extracted
  • Upload Excel → sheets extracted as structured table data
  • Upload CSV → rows/columns extracted
  • Upload images (PNG/JPG) → stored, metadata extracted
  • Upload PDF → text extracted
  • Paste URL → article content extracted as markdown

11. Content Intelligence Service

NLP-powered classification and extraction of content blocks.

Backend:

  • Create backend/models/content_models.py:

    class ContentBlockType(str, Enum):
        narrative = "narrative"
        quote = "quote"
        metric = "metric"
        table = "table"
        timeline = "timeline"
        comparison = "comparison"
        list_items = "list_items"
        image_reference = "image_reference"
        call_to_action = "call_to_action"
    
    class ContentBlock(BaseModel):
        type: ContentBlockType
        raw_text: str
        extracted_data: dict | None  # e.g., {value: "2M", label: "Impressions"} for metrics
        source_section: str | None  # heading from brief
        priority: int  # 1-10
    
    class ClassifiedContent(BaseModel):
        title: str | None
        blocks: list[ContentBlock]
        tables: list[TableData]
        images: list[ImageInfo]
        summary: str  # brief overall summary
    
  • Create backend/services/content_intelligence_service.py:

    • ContentIntelligenceService class:
      • classify(markdown: str, attachments: list) → ClassifiedContent:

        1. Split markdown into chunks using ScoreBasedChunker (existing)
        2. For each chunk, classify content type:
          • Rule-based pass first:
            • Regex for numbers/percentages/currency → metric
            • Regex for quoted text with attribution → quote
            • Markdown table patterns → table
            • Year sequences or date patterns → timeline
            • "vs", "compared to", "versus" → comparison
            • Bullet lists → list_items
            • Image references (URLs, "see figure") → image_reference
          • LLM-based classification for ambiguous blocks (batch multiple chunks in one call)
        3. Merge attachment data: Excel tables → table blocks, images → image_reference blocks
        4. Extract numeric data from metric blocks: {value, label, unit, context}
        5. Rank blocks by priority (title > metrics > quotes > narrative)
        6. Return ClassifiedContent
      • ask_followup_questions(content: ClassifiedContent) → list[str] | None:

        • If total content is too short (< 200 words, < 3 blocks): generate clarifying questions
        • LLM call: "Given this brief content, what additional information would be needed for a presentation?"
        • Return list of questions or None if content is sufficient

Verification:

  • Brief with "Revenue grew 45% to $2.3M" → classified as metric with {value: "45%", label: "Revenue growth"}
  • Table in markdown → classified as table with extracted data
  • Quoted text with speaker → classified as quote
  • Short 2-sentence brief → returns follow-up questions
  • Excel attachment → tables merged into content blocks

12. Slide Mapping Engine

Rules engine that maps classified content to master deck layout types.

Backend:

  • Create backend/services/slide_mapping_engine.py:
    • SlideMappingEngine class:
      • map(classified_content: ClassifiedContent, master_deck: MasterDeckModel, n_slides: int, instructions: str | None) → list[SlideMapping]:
        1. Determine required slide types based on content:
          • Always: Title slide (1st)
          • If >5 content blocks: Agenda/TOC slide (2nd)
          • Per section heading in brief: Section divider slide
          • Per content block: map type to layout:
            • metric → metrics/KPI layout
            • quote → quote layout
            • table → table layout or chart layout (user will choose later)
            • comparison → dual-column/comparison layout
            • timeline → timeline layout
            • list_items → bullet list layout
            • narrative → content/description layout
            • image_reference → image-focused layout
        2. Match content types to available layouts in master deck (by layout type field)
        3. If no matching layout: fall back to generic content layout
        4. Respect n_slides constraint: merge/drop low-priority blocks if too many, split high-content blocks if too few
        5. Use LLM for ambiguous mappings (existing generate_presentation_structure pattern)
        6. Return ordered list of SlideMapping:
          class SlideMapping(BaseModel):
              content_block_index: int | None
              layout_id: str
              layout_name: str
              slide_type: str
              content_summary: str
              attachment_ids: list[str]  # mapped attachments
          

Verification:

  • Brief with 3 metrics, 1 quote, 2 narrative sections, 1 table → correct layout assignments
  • Requesting 8 slides from 15 content blocks → low-priority blocks merged/dropped
  • Missing layout type in master deck → falls back to generic layout
  • Attachments (Excel) assigned to correct table/chart slides

13. Chart Data Extraction & Native PPTX Charts

Extract numeric data → chart-ready structures. Render as native editable PPTX charts.

Backend:

  • Create backend/services/chart_data_extractor.py:

    • ChartDataExtractor class:
      • extract(content_block: ContentBlock, table_data: TableData | None) → ChartData | None:
        1. If table_data provided: convert directly to ChartData
        2. If content_block.type == metric: try to extract numeric series from text
        3. Use LLM for complex extraction: "Convert this data to chart format"
        4. Recommend chart type based on data shape:
          • Single category with values → bar chart
          • Time series → line chart
          • Parts of whole (percentages summing to ~100%) → pie/donut chart
          • Two series comparison → grouped bar
          • Project phases with dates → Gantt (shape-based)
          • Sequential increases/decreases → waterfall (shape-based)
        5. Return ChartData:
          class ChartData(BaseModel):
              chart_type: Enum(bar, column, line, pie, doughnut, area, scatter, bubble, gantt, waterfall)
              title: str
              categories: list[str]  # X-axis labels
              series: list[ChartSeries]  # {name, values: list[float]}
              unit: str | None  # e.g., "$", "%", "units"
          
  • Create backend/services/native_chart_service.py:

    • NativeChartService class (extends PptxPresentationCreator):
      • add_native_chart(slide, chart_data: ChartData, position, size):
        • For bar/column/line/pie/doughnut/area/scatter/bubble:
          • Use python-pptx chart API directly: slide.shapes.add_chart()
          • Set chart data from ChartData.series
          • Apply brand colors from client config
          • Set fonts from brand config
        • For Gantt:
          • Build from shapes: one add_shape() per task bar (rectangle)
          • Add text labels, date markers, connectors
          • Apply brand colors
        • For Waterfall:
          • Build from shapes: stacked rectangles with invisible bases
          • Add value labels, category labels, connector lines
          • Color-code: green for increases, red for decreases, blue for totals
  • Modify backend/services/pptx_presentation_creator.py:

    • Import and use NativeChartService for chart shapes
    • Replace any image-based chart rendering with native chart calls

Frontend:

  • Create frontend/app/(presentation-generator)/components/ChartDataEditor.tsx:
    • Spreadsheet-like grid (using simple HTML table with contentEditable cells, or a lightweight lib)
    • Columns: categories + series names
    • Rows: data values
    • Add/remove rows and series
    • Chart type selector dropdown
    • Live Recharts preview next to the data editor
    • "Apply" button saves changes to Redux store and triggers slide re-render

Verification:

  • Table with sales data → extracted as bar chart → native editable chart in PPTX
  • Open PPTX in PowerPoint → right-click chart → "Edit Data" works
  • Gantt chart renders as editable shapes with task bars
  • Waterfall chart shows increases (green) and decreases (red) as shapes
  • Chart data editor: change a value → preview updates live → export reflects change
  • Brand colors applied to all chart elements

Phase 4: Generation Pipeline

14. Brand Enforcement Service

Apply client brand rules to generated content and PPTX output.

Backend:

  • Create backend/services/brand_enforcement_service.py:
    • BrandEnforcementService class:
      • get_brand_context_for_llm(client_id: UUID) → str:
        • Load BrandConfigModel for client
        • Build a prompt snippet with voice rules, examples, tone guidelines
        • Used to inject into LLM system prompts during content generation
      • enforce_on_pptx_model(model: PptxPresentationModel, brand: BrandConfigModel) → PptxPresentationModel:
        • Walk all slides → all shapes → replace:
          • Font families → brand fonts (heading font for titles, body font for content)
          • Colors → brand palette (map template colors to brand primary/secondary)
          • Add logo shape to designated position on each slide (configurable: top-left, bottom-right, etc.)
        • Contrast check: ensure text color has sufficient contrast against background
      • enforce_on_native_charts(slide, brand: BrandConfigModel):
        • Set chart colors to brand palette
        • Set chart fonts to brand fonts

Verification:

  • Generate deck for Nike → all fonts are Nike brand fonts
  • All chart bars use Nike brand colors
  • Nike logo appears on every slide in the configured position
  • Text contrast passes WCAG AA check

15. Outline Generation (Modified)

Enhance existing outline generation with brand context and content intelligence.

Backend:

  • Modify backend/utils/llm_calls/generate_presentation_outlines.py:

    • Extend get_system_prompt() to include:
      • Brand voice guidelines (from BrandEnforcementService.get_brand_context_for_llm())
      • Available layout types from master deck (names + descriptions)
      • Content classification summary (types and counts of content blocks)
      • Critical instruction: "You are restructuring and condensing existing content from a brief. Do NOT invent facts, statistics, or claims. Every data point must originate from the source material."
    • Extend user prompt to include:
      • User's custom instructions (free-text from Step 2)
      • Classified content blocks with their types
      • Attachment summaries (what Excel/CSV data is available)
  • Modify backend/utils/llm_calls/generate_slide_content.py:

    • Same brand context injection
    • Same "strictly extract, don't invent" instruction
    • For chart-type slides: include chart data schema so LLM populates data correctly
    • For slides with mapped attachments: include attachment data as context
  • Implement LLM auto-fallback in backend/services/llm_client.py:

    • Wrap all LLM calls in try/except
    • On provider failure (timeout, rate limit, API error):
      • Log the failure
      • Switch to fallback provider (Claude → OpenAI → Gemini)
      • Retry the call
      • Log which provider was used

Verification:

  • Generate outline from brief → outline reflects source content accurately
  • No hallucinated facts in outline
  • Brand voice guidelines influence tone of generated text
  • Disable primary LLM → auto-fallback to secondary provider → generation succeeds
  • User instructions ("focus on ROI") reflected in outline emphasis

16. Job Queue & Async Generation

Redis-based job queue with ARQ for reliable async presentation generation.

Backend:

  • Add dep: pip install arq>=0.26

  • Create backend/services/job_queue_service.py:

    • JobQueueService class:
      • enqueue(job_type: str, payload: dict) → UUID — push job to ARQ Redis queue, create JobModel in DB
      • get_status(job_id: UUID) → JobModel — poll job status
      • cancel(job_id: UUID) — cancel queued job
  • Create backend/workers/main.py:

    • ARQ worker entry point
    • Register task functions:
      • generate_presentation_task(ctx, job_id, ...)
      • parse_master_deck_task(ctx, job_id, ...)
    • Configure: max_jobs=5, job_timeout=600s, retry=3 with exponential backoff
  • Create backend/workers/presentation_worker.py:

    • generate_presentation_task(ctx, job_id: UUID):
      1. Load job from DB, update status → processing
      2. Load brief content + attachments
      3. Run ContentIntelligenceService.classify()
      4. Run SlideMappingEngine.map()
      5. Run outline generation (LLM)
      6. Run slide content generation (LLM, batched 10 at a time)
      7. Run asset generation (images, icons — parallel)
      8. Run chart data extraction + native chart prep
      9. Run brand enforcement
      10. Save slides to DB
      11. Update job status → completed, progress → 100
      • At each step: update JobModel.progress and progress_message
      • On failure: update status → failed, save error_message
      • SSE: push progress events via Redis pub/sub (existing SSE pattern)
  • Create backend/workers/master_deck_worker.py:

    • parse_master_deck_task(ctx, job_id: UUID):
      • Load master deck from DB
      • Run MasterDeckParserService.parse()
      • Update parse_status on completion/failure
  • Modify docker-compose.yml:

    • Add worker service: same image as api, entrypoint python -m arq backend.workers.main.WorkerSettings
  • Create backend/api/v1/ppt/endpoints/jobs.py:

    • GET /api/v1/ppt/jobs/{id} → job status + progress
    • GET /api/v1/ppt/jobs/{id}/stream → SSE stream of job progress events
    • DELETE /api/v1/ppt/jobs/{id} → cancel job

Verification:

  • Start generation → job queued → worker picks up → progress updates visible
  • Kill worker mid-job → job stays in processing → worker restart picks up retry
  • 3 concurrent users generate → jobs processed via queue without conflicts
  • Master deck parse runs as background job → status updates in admin UI

Phase 5: Frontend Wizard & Editor

17. Wizard Flow — Steps 1-2 (Upload & Configure)

Frontend:

  • Create frontend/app/(presentation-generator)/generate/layout.tsx:

    • Wizard layout with step indicator bar (5 steps)
    • Step labels: Upload → Configure → Outline → Generate → Edit
    • Current step highlighted, completed steps checkmarked
    • Each step is a nested route
  • Create frontend/app/(presentation-generator)/generate/upload/page.tsx (Step 1):

    • Large drag & drop zone for primary brief (DOCX)
    • "Or browse files" button fallback
    • Section for additional attachments (Excel, CSV, images, PDF)
    • Each uploaded file shows: name, size, type badge, remove button
    • Optional: URL input field with "Add reference URL" button
    • Parsed content preview (shows extracted text/tables for each file)
    • "Next" button → navigates to Step 2
    • Auto-save: uploads saved to JobModel / session state
  • Create frontend/app/(presentation-generator)/generate/configure/page.tsx (Step 2):

    • Client selector dropdown (only assigned clients from authSlice)
    • Master deck selector (filtered by selected client, shows thumbnails)
    • Slide count slider (range: 5-40, default: smart suggestion based on content length)
    • Free-text instructions textarea ("Focus on...", "Skip...", "Emphasize...")
    • Tone selector (if not locked by brand config): professional / casual / educational
    • "Generate Outline" button → triggers outline generation → navigates to Step 3
    • "Back" button → returns to Step 1
  • Create frontend/store/slices/wizardSlice.ts:

    • State: {currentStep, uploadedFiles, selectedClientId, selectedDeckId, slideCount, instructions, tone, outlines, jobId, presentationId}
    • Persist to localStorage for auto-save (user can close browser and return)
    • Actions: setStep, setFiles, setClient, setDeck, etc.

Verification:

  • Drag DOCX → file appears with metadata → proceed to Step 2
  • Select client → master decks filter → select deck → slide count slider works
  • Close browser on Step 2 → reopen → state restored
  • Back button works between steps

18. Wizard Flow — Step 3 (Outline Review)

Frontend:

  • Create frontend/app/(presentation-generator)/generate/outline/page.tsx (Step 3):
    • Split view (side-by-side):
      • LEFT panel: parsed source content (brief markdown rendered as formatted text)
        • Collapsible sections per heading
        • Highlight which content block maps to which outline item (linked highlighting)
      • RIGHT panel: generated outline
        • Each outline item as a card: slide number, title, description, layout type badge
        • Drag & drop reordering (using existing @dnd-kit dependency)
        • Inline edit: click title/description to edit
        • "Add Slide" button → insert new outline item
        • "Delete" button per slide → remove with confirmation
        • Layout type dropdown per slide (from master deck available layouts)
    • Attachment mapping panel (bottom or collapsible):
      • List of uploaded attachments (Excel tables, images, etc.)
      • Drag attachment onto a specific outline item → maps it to that slide
      • Show which attachments are assigned and which are unassigned
    • Mandatory approval: "Approve Outline & Generate" button (disabled until user has reviewed)
    • "Back" button → return to Step 2

Verification:

  • Outline shows on right, source on left
  • Drag slide 3 above slide 2 → order changes
  • Edit title inline → saved
  • Add new slide → appears at bottom
  • Delete slide → removed from outline
  • Map Excel attachment to slide 5 → indicator shows
  • Click "Approve & Generate" → generation starts → navigate to Step 4

19. Wizard Flow — Step 4 (Generation Progress)

Frontend:

  • Create frontend/app/(presentation-generator)/generate/progress/page.tsx (Step 4):
    • Progress bar showing overall completion (0-100%)
    • Status message text (e.g., "Generating slide 5 of 15...")
    • Live slide preview grid:
      • As each slide completes, its thumbnail appears in the grid
      • Slides appear one by one with a fade-in animation
      • Click thumbnail → shows larger preview in a modal
    • SSE connection to /api/v1/ppt/jobs/{id}/stream for real-time updates
    • On completion → auto-navigate to Step 5 (/presentation/{id})
    • On failure → show error message with "Retry" button
    • "Cancel" button → cancels job, returns to Step 3

Verification:

  • Generation starts → progress bar moves → slides appear one by one
  • Close browser → reopen → progress page shows current state (polling fallback)
  • Generation fails at slide 8 → error shown → "Retry" re-queues from slide 8
  • Cancel → job cancelled → return to outline

20. Wizard Flow — Step 5 / Slide Editor

Enhance Presenton's existing slide editor with new capabilities.

Frontend:

  • Modify frontend/app/(presentation-generator)/presentation/page.tsx:

    • Integrate with wizard flow (accessible as Step 5 and as standalone for saved presentations)
    • Add toolbar with:
      • Review status indicator (Draft / In Review / Approved)
      • "Save to Library" button
      • Export dropdown: ".pptx" and "PDF"
      • "Download" button
  • Add per-slide regeneration:

    • Right-click slide or "..." menu → "Regenerate this slide"
    • Opens modal with:
      • Current slide content preview
      • Text input: "Instructions for regeneration" (e.g., "Make more concise", "Add a chart")
      • "Regenerate" button → calls backend → replaces slide content
      • "Cancel" button
  • Add per-image choice:

    • Click image on slide → popover shows:
      • Current image (from source or AI-generated)
      • "Source image" tab (if brief had an image for this section)
      • "Stock images" tab (search Pexels/Pixabay)
      • "AI generate" tab (enter prompt, generate new image)
      • "Upload" tab (upload custom image)
    • Select image → replaces in slide
  • Add per-table/chart choice:

    • Click table → context menu:
      • "Keep as table" (render as formatted table)
      • "Convert to chart" → opens chart type picker + chart data editor
    • Click chart → context menu:
      • "Edit data" → opens ChartDataEditor
      • "Change chart type" → type picker dropdown
      • "Convert to table" → renders data as table instead
  • Integrate ChartDataEditor.tsx (from step 13):

    • Opens as modal/panel when editing chart data
    • Changes reflect live in slide preview

Verification:

  • Regenerate slide 5 with "make it shorter" → new content generated, slide updates
  • Click image → swap with stock photo from Pexels → slide updates
  • Click table → "Convert to chart" → bar chart appears → edit data → chart updates
  • Save to library → presentation persisted → appears on dashboard

21. Review Workflow

Status-based review: Draft → In Review → Approved.

Backend:

  • Create backend/api/v1/ppt/endpoints/review.py:
    • PUT /api/v1/ppt/presentation/{id}/status → change status
      • Body: {status: "in_review" | "approved" | "draft", comment: str | None}
      • Validation: only owner or team member can change status
      • If client review_policy == "require_reviewer": cannot self-approve (owner != approver)
      • Audit log entry on every status change
    • GET /api/v1/ppt/presentation/{id}/comments → list comments/status changes
    • POST /api/v1/ppt/presentation/{id}/comment → add comment (any team member)

Frontend:

  • Create frontend/app/(presentation-generator)/components/ReviewWorkflow.tsx:

    • Status badge (color-coded: yellow=Draft, blue=In Review, green=Approved)
    • "Submit for Review" button (Draft → In Review)
    • "Approve" button (In Review → Approved, only if reviewer is different user or self-approve allowed)
    • "Request Changes" button (In Review → Draft, with required comment)
    • Comments section: threaded comments with user avatar, timestamp
    • "Add Comment" text area
  • Add to presentation page header:

    • Review status badge
    • Action buttons based on current status and user role

Verification:

  • User creates deck → status = Draft
  • User clicks "Submit for Review" → status = In Review
  • Different user opens same deck → can Approve or Request Changes
  • Client with require_reviewer policy → owner cannot self-approve
  • Comment added → visible to all team members
  • Approved deck → export enabled

Phase 6: Export & Polish

22. Export Pipeline (PPTX + PDF)

Enhance export with native charts, brand enforcement, and PDF support.

Backend:

  • Modify backend/utils/export_utils.py:

    • export_presentation(presentation_id, format, user):
      1. Load presentation + slides from DB
      2. Load master deck config + brand config
      3. Convert slide data → PptxPresentationModel (existing pipeline via Next.js API)
      4. For chart slides: use NativeChartService.add_native_chart() instead of image
      5. Run BrandEnforcementService.enforce_on_pptx_model() — final brand pass
      6. Render PPTX via PptxPresentationCreator
      7. If format == "pdf": also render via Puppeteer (existing pipeline)
      8. Audit log: record export action
      9. Return file path for download
  • Create backend/api/v1/ppt/endpoints/export.py:

    • POST /api/v1/ppt/presentation/{id}/export → body: {format: "pptx" | "pdf"}
    • Returns: file download response
    • Access check: user must have access to presentation's client

Frontend:

  • Add export dropdown to slide editor:
    • "Export as .pptx" → calls export endpoint → browser downloads file
    • "Export as PDF" → calls export endpoint → browser downloads file
    • Show loading spinner during export
    • Toast on success/failure

Verification:

  • Export PPTX → open in PowerPoint → all text editable, charts editable, brand correct
  • Export PDF → opens in viewer, all slides rendered correctly
  • Charts in PPTX: right-click → Edit Data → data table appears
  • Gantt/Waterfall: shapes are individually selectable and editable
  • Brand colors/fonts/logo present on every slide

23. Client Library & Dashboard

Two-level navigation: clients → templates/decks. Dashboard for saved presentations.

Frontend:

  • Redesign frontend/app/(presentation-generator)/dashboard/page.tsx:

    • Two-level navigation:
      • First view: grid of client cards (only assigned clients + Oliver Team)
      • Each card: client logo, name, deck count, "New Presentation" button
      • Click client → second view: client's master decks + saved presentations
    • Client detail view:
      • Tabs: "Templates" (master decks) | "My Presentations" | "Team Presentations"
      • Templates tab: master deck cards with thumbnails, "Use this template" button
      • My Presentations: list of user's saved decks for this client (with status badges)
      • Team Presentations: decks by other team members (read-only unless reviewer)
    • "New Presentation" button → starts wizard (Step 1) with client pre-selected
    • Search/filter bar for presentations
  • Create frontend/store/slices/clientSlice.ts:

    • State: {clients: Client[], selectedClientId, masterDecks: MasterDeck[], presentations: Presentation[]}
    • Thunks: fetchClients(), fetchMasterDecks(clientId), fetchPresentations(clientId)

Verification:

  • User in Nike + Adidas teams → sees Nike, Adidas, Oliver Team cards
  • Click Nike → sees Nike master decks + Nike presentations
  • User not in Adidas team → Adidas card not visible
  • Super Admin sees all clients
  • "New Presentation" → wizard starts with Nike pre-selected

24. Data Retention Service

Auto-purge expired data per client retention policy.

Backend:

  • Create backend/services/retention_service.py:

    • RetentionService class:
      • run_cleanup() — scheduled task (daily via ARQ cron):
        1. Query all clients with retention_days IS NOT NULL
        2. For each client: find presentations where created_at < now - retention_days and deleted_at IS NULL
        3. Soft-delete expired presentations (set deleted_at)
        4. Delete associated files from filesystem
        5. Audit log each deletion
      • purge_soft_deleted(days_after_soft_delete=30) — permanently delete records 30 days after soft delete
  • Add ARQ cron job in backend/workers/main.py:

    • Schedule run_cleanup daily at 2:00 AM
    • Schedule purge_soft_deleted weekly

Verification:

  • Set client retention to 1 day → create presentation → wait → presentation soft-deleted
  • Soft-deleted presentations not visible in UI but still in DB
  • After 30 days → permanently deleted from DB and filesystem
  • Audit log shows retention cleanup entries

25. Brand-Adaptive UI Theme

UI colors adapt to the selected client's brand colors.

Frontend:

  • Modify frontend/app/globals.css:

    • Define CSS custom properties for brand-adaptive colors:
      :root {
        --brand-primary: #...;
        --brand-secondary: #...;
        --brand-accent: #...;
      }
      
  • Create frontend/hooks/useBrandTheme.ts:

    • Reads selected client's brand config from Redux store
    • Sets CSS custom properties on document.documentElement
    • Fallback: Oliver brand colors when no client selected
  • Update key UI elements to use brand CSS variables:

    • Wizard step indicator active color
    • Primary buttons
    • Header accent color
    • Slide editor toolbar highlights
    • Client card borders

Verification:

  • Select Nike (red brand) → wizard step bar turns red, buttons turn red
  • Select Adidas (blue brand) → UI shifts to blue accents
  • No client selected → Oliver default theme
  • Theme changes are instant (no page reload)

26. Analytics Dashboard

Admin dashboard with usage, quality, and performance metrics.

Backend:

  • Create backend/api/v1/admin/analytics_router.py:
    • GET /api/v1/admin/analytics/overview → aggregated stats:
      • Total presentations generated (all time, this month, this week)
      • Active users count
      • Average generation time
      • Most popular master decks
    • GET /api/v1/admin/analytics/usage → usage metrics:
      • Decks per user (top 10)
      • Decks per client (top 10)
      • Decks per day (time series, last 30 days)
    • GET /api/v1/admin/analytics/quality → quality metrics:
      • Average slides accepted without edit (% per deck)
      • Average regenerations per deck
      • Average comments per deck
      • Approval rate
    • GET /api/v1/admin/analytics/performance → performance metrics:
      • Average generation time (by slide count)
      • LLM provider usage breakdown
      • Error/failure rates
      • API cost estimates (based on token usage)
    • Client Admin: filtered to their clients. Super Admin: all.

Frontend:

  • Wire frontend/app/admin/analytics/page.tsx:
    • Overview cards: total decks, active users, avg generation time
    • Usage chart: line chart of decks per day (Recharts)
    • Quality metrics: bar chart of acceptance rates
    • Performance: generation time distribution
    • Client filter dropdown (Super Admin sees all)
    • Date range picker

Verification:

  • Generate 5 decks → analytics show 5 decks, correct generation times
  • Client Admin sees only their client's metrics
  • Charts render with real data

Phase 7: Testing & Verification

27. Testing Suite

Backend tests:

  • Auth tests: login flow, JWT validation, role checks, token expiry
  • RBAC tests: client access, team membership, cross-client isolation
  • Audit tests: actions logged correctly, export format correct
  • Content intelligence tests: classification accuracy for each content type
  • Chart data extraction tests: table → chart data conversion accuracy
  • Slide mapping tests: content type → layout mapping correctness
  • Brand enforcement tests: color/font/logo replacement
  • Native chart tests: each chart type renders correctly in PPTX
  • Job queue tests: enqueue, process, retry, failure handling
  • Retention tests: soft delete, purge timing
  • Export tests: PPTX validity, PDF generation
  • Integration test: full pipeline brief → classified content → outline → slides → export

Frontend tests (Cypress E2E):

  • Login flow: Azure AD redirect → callback → dashboard
  • Wizard flow: upload → configure → outline → generate → edit → export
  • Admin: create client → upload master deck → configure brand → assign users
  • RBAC: user cannot access unauthorized clients
  • Review workflow: Draft → In Review → Approved
  • Chart editor: edit data → preview updates → export correct
  • Auto-save: close browser mid-wizard → return → state preserved

Verification:

  • make test runs all backend tests
  • make test-e2e runs Cypress suite
  • All tests pass in CI (Docker-based)

Dependency Graph (Build Order)

Phase 1: Foundation
  1. Project Setup ──────────────────────────────────────┐
  2. Database Schema ─── depends on 1                    │
  3. Auth Module ──────── depends on 2                   │
  4. RBAC & Teams ─────── depends on 3                   │
  5. Audit Logging ────── depends on 4                   │
  6. i18n Setup ───────── depends on 1 (parallel with 2) │

Phase 2: Admin & Clients                                 │
  7. Admin Panel Shell ── depends on 4, 6                │
  8. Client & Brand ───── depends on 7                   │
  9. Master Deck Parser ─ depends on 8                   │

Phase 3: Content Pipeline                                │
  10. File Upload ─────── depends on 2, 4                │
  11. Content Intelligence depends on 10                 │
  12. Slide Mapping ───── depends on 9, 11               │
  13. Charts (extract + native PPTX) ── depends on 11    │

Phase 4: Generation                                      │
  14. Brand Enforcement ─ depends on 8                   │
  15. Outline Generation ─ depends on 11, 14             │
  16. Job Queue ────────── depends on 15, 12, 13         │

Phase 5: Frontend Wizard & Editor                        │
  17. Wizard Steps 1-2 ── depends on 7, 10               │
  18. Wizard Step 3 ───── depends on 15, 17              │
  19. Wizard Step 4 ───── depends on 16, 18              │
  20. Wizard Step 5 / Editor ── depends on 13, 19        │
  21. Review Workflow ──── depends on 4, 20              │

Phase 6: Export & Polish                                 │
  22. Export Pipeline ──── depends on 13, 14, 20         │
  23. Client Library ───── depends on 8, 20              │
  24. Data Retention ───── depends on 5, 8               │
  25. Brand-Adaptive Theme depends on 8                  │
  26. Analytics Dashboard ─ depends on 5, 7              │

Phase 7: Testing                                         │
  27. Testing Suite ────── depends on everything         ─┘

Files Quick Reference

New files to create (backend):

backend/
├── api/
│   ├── middlewares/
│   │   ├── auth_middleware.py          (Step 3)
│   │   ├── rbac_middleware.py          (Step 4)
│   │   └── audit_middleware.py         (Step 5)
│   └── v1/
│       ├── auth/
│       │   └── router.py              (Step 3)
│       └── admin/
│           ├── users_router.py        (Step 4)
│           ├── teams_router.py        (Step 4)
│           ├── clients_router.py      (Step 4)
│           ├── brand_config_router.py (Step 8)
│           ├── master_decks_router.py (Step 9)
│           ├── audit_router.py        (Step 5)
│           └── analytics_router.py    (Step 26)
├── models/
│   ├── sql/
│   │   ├── user.py                    (Step 2)
│   │   ├── client.py                  (Step 2)
│   │   ├── team.py                    (Step 2)
│   │   ├── team_membership.py         (Step 2)
│   │   ├── brand_config.py            (Step 2)
│   │   ├── master_deck.py             (Step 2)
│   │   ├── audit_log.py               (Step 2)
│   │   └── job.py                     (Step 2)
│   └── content_models.py             (Step 11)
├── services/
│   ├── auth_service.py                (Step 3)
│   ├── access_service.py              (Step 4)
│   ├── audit_service.py               (Step 5)
│   ├── attachment_parser_service.py   (Step 10)
│   ├── content_intelligence_service.py (Step 11)
│   ├── slide_mapping_engine.py        (Step 12)
│   ├── chart_data_extractor.py        (Step 13)
│   ├── native_chart_service.py        (Step 13)
│   ├── brand_enforcement_service.py   (Step 14)
│   ├── master_deck_parser_service.py  (Step 9)
│   ├── job_queue_service.py           (Step 16)
│   └── retention_service.py           (Step 24)
├── workers/
│   ├── main.py                        (Step 16)
│   ├── presentation_worker.py         (Step 16)
│   └── master_deck_worker.py          (Step 16)
├── utils/
│   └── auth_dependencies.py           (Step 3)
└── migrations/                        (Step 2, Alembic)

New files to create (frontend):

frontend/
├── app/
│   ├── login/
│   │   └── page.tsx                   (Step 3)
│   ├── admin/
│   │   ├── layout.tsx                 (Step 7)
│   │   ├── page.tsx                   (Step 7)
│   │   ├── users/                     (Step 7)
│   │   ├── clients/                   (Step 7, 8, 9)
│   │   ├── audit/                     (Step 7)
│   │   ├── analytics/                 (Step 26)
│   │   ├── settings/                  (Step 7)
│   │   └── components/               (Step 7)
│   └── (presentation-generator)/
│       ├── generate/
│       │   ├── layout.tsx             (Step 17)
│       │   ├── upload/page.tsx        (Step 17)
│       │   ├── configure/page.tsx     (Step 17)
│       │   ├── outline/page.tsx       (Step 18)
│       │   └── progress/page.tsx      (Step 19)
│       └── components/
│           ├── ChartDataEditor.tsx    (Step 13)
│           ├── ReviewWorkflow.tsx     (Step 21)
│           └── AttachmentMapper.tsx   (Step 18)
├── components/
│   └── AuthGuard.tsx                  (Step 3)
├── store/slices/
│   ├── authSlice.ts                   (Step 3)
│   ├── wizardSlice.ts                 (Step 17)
│   ├── clientSlice.ts                 (Step 23)
│   └── adminSlice.ts                  (Step 7)
├── hooks/
│   └── useBrandTheme.ts              (Step 25)
└── i18n/
    ├── i18n.ts                        (Step 6)
    ├── I18nProvider.tsx               (Step 6)
    └── locales/en/                    (Step 6)