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>
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-browserdep) - Update all internal import paths after restructure
- Create new root
docker-compose.ymlwith services:web(Next.js frontend, port 3000)api(FastAPI backend, port 8000)worker(ARQ worker, same image asapi, 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.devfor development with hot-reload - Update
nginx.conf: route/api/*→ FastAPI,/admin/*and/*→ Next.js - Create
.env.examplewith all required variables - Create
Makefilewith common commands (make dev,make build,make migrate,make test) - Verify
docker compose upboots all services and health checks pass
Verification:
docker compose upstarts all 5 serviceshttp://localhostloads Next.js apphttp://localhost/api/docsloads 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.15topyproject.toml - Run
alembic init migrationsinbackend/ - Configure
alembic.iniandenv.pyto use async PostgreSQL + SQLModel metadata - Set
DATABASE_URL=postgresql+asyncpg://deckforge:deckforge@postgres:5432/deckforgein.env - Remove SQLite container DB logic (
container.db,get_container_session)
New models (create in backend/models/sql/):
-
user.py—UserModelid: 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.py—ClientModelid: 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.py—TeamModelid: 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.py—TeamMembershipModelid: 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.py—BrandConfigModelid: 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.py—MasterDeckModelid: 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.py—AuditLogModelid: 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.py—JobModelid: 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— extendPresentationModel:+ 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— extendSlideModel:+ 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 headcreates all tables in PostgreSQL
Verification:
alembic upgrade headruns 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:AuthServiceclass with:get_authorization_url()→ returns Azure AD OAuth URL with redirect_uriexchange_code_for_token(code: str)→ exchanges auth code for access/id token via MSALvalidate_token(token: str)→ validates JWT, returns claims (oid, email, name)get_or_create_user(claims: dict)→ find byazure_oidor create newUserModel(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 URLGET /api/v1/auth/callback→ exchange code, create/update user, set JWT cookie, redirect to dashboardGET /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 newapi/v1/router.py)
-
Create
backend/api/middlewares/auth_middleware.py:AuthMiddleware: extract JWT from cookie/header → validate → attachrequest.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 checksrequest.state.user.rolerequire_super_admin→ shortcut dependencyrequire_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[]}
- State:
-
Create
frontend/components/AuthGuard.tsx:- Wraps app; redirects to
/api/v1/auth/loginif not authenticated - Shows loading state while checking auth
- Wraps app; redirects to
-
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
AuthGuardand Redux Provider - Call
fetchCurrentUser()on mount
- Wrap app with
Verification:
- Click "Sign in with Microsoft" → Azure AD login → redirect back → user created in DB
/api/v1/auth/mereturns 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 membershipsGET /api/v1/admin/users/{id}→ user detailPUT /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_admindependency
-
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 membersPOST /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 detailPUT /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 membershipget_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_presentationsby user's accessible clients - Check
check_client_access(user, client_id)on create/generate - Check ownership or client access on get/update/delete
- Add
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:AuditServiceclass:log(user_id, action, resource_type, resource_id, client_id, details, ip_address)→ insertAuditLogModel- Use background task (not blocking request) via
asyncio.create_task() query(filters: AuditLogFilter) → list[AuditLogModel]— paginated, filterable by date range, user, action, clientexport_csv(filters) → StreamingResponse— CSV exportexport_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 configlocales/en/common.json— common strings (buttons, labels, errors)locales/en/auth.json— auth-related stringslocales/en/admin.json— admin panel stringslocales/en/wizard.json— wizard step stringslocales/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+)
- Sidebar navigation with sections:
-
Create admin pages (skeleton — data integration in subsequent steps):
frontend/app/admin/page.tsx→ dashboard/overview redirectfrontend/app/admin/users/page.tsx→ user list + role managementfrontend/app/admin/users/[id]/page.tsx→ user detail + team assignmentsfrontend/app/admin/clients/page.tsx→ client listfrontend/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 clientfrontend/app/admin/clients/[id]/master-decks/page.tsx→ master deck list for clientfrontend/app/admin/clients/[id]/brand/page.tsx→ brand config editorfrontend/app/admin/audit/page.tsx→ audit log viewer with filters + exportfrontend/app/admin/analytics/page.tsx→ analytics dashboardfrontend/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.tsxfrontend/app/admin/components/UserTable.tsxfrontend/app/admin/components/ClientCard.tsxfrontend/app/admin/components/RoleBadge.tsxfrontend/app/admin/components/AuditLogTable.tsxfrontend/app/admin/components/DataExportButton.tsx(CSV/JSON)
Verification:
/adminaccessible 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.pyendpoints 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 configPUT /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 filePOST /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
MasterDeckModelwithparse_status=pending - Enqueue parse job to Redis
- Return deck_id + status
- Save file to
GET /api/v1/admin/clients/{client_id}/master-decks→ list master decksGET /api/v1/admin/master-decks/{id}→ deck detail with parsed layoutsPUT /api/v1/admin/master-decks/{id}→ update name, description, active statusPUT /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:-
MasterDeckParserServiceclass:parse(deck_id: UUID)→ main parse pipeline:- Open PPTX with
python-pptx - Enumerate
prs.slide_masters[0].slide_layouts→ extract each layout - For each layout:
- Extract placeholder types and positions
- Render screenshot via Puppeteer/LibreOffice (reuse existing
pptx_slideslogic) - Extract OXML
- Extract theme:
a:clrScheme→ color palette,a:fontScheme→ font config - Extract logos/images from master slide backgrounds
- For each layout screenshot + OXML:
- Call existing
slide_to_htmlpipeline (vision LLM converts screenshot + OXML → HTML) - Call existing
html_to_reactpipeline (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
- Call existing
- Store results in
MasterDeckModel.parsed_configandlayoutsJSON - Update
parse_status=completed
- Open PPTX with
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 logicbackend/api/v1/ppt/endpoints/slide_to_html.py— vision LLM slide-to-HTMLbackend/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 (viaopenpyxl)parse_csv(file_path) → TableData— parse CSV into structured table dataextract_images(file_path) → list[ImageInfo]— extract images from any doc formatparse_pdf_text(file_path) → str— extract text from PDF (viapdfplumber, 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
trafilaturaorreadability-lxml - Return markdown
- Fetch URL via
- Add deps:
pip install trafilatura>=2.0
- Add
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:ContentIntelligenceServiceclass:-
classify(markdown: str, attachments: list) → ClassifiedContent:- Split markdown into chunks using
ScoreBasedChunker(existing) - 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
- Regex for numbers/percentages/currency →
- LLM-based classification for ambiguous blocks (batch multiple chunks in one call)
- Rule-based pass first:
- Merge attachment data: Excel tables →
tableblocks, images →image_referenceblocks - Extract numeric data from metric blocks:
{value, label, unit, context} - Rank blocks by priority (title > metrics > quotes > narrative)
- Return
ClassifiedContent
- Split markdown into chunks using
-
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
metricwith{value: "45%", label: "Revenue growth"} - Table in markdown → classified as
tablewith 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:SlideMappingEngineclass:map(classified_content: ClassifiedContent, master_deck: MasterDeckModel, n_slides: int, instructions: str | None) → list[SlideMapping]:- 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 layoutquote→ quote layouttable→ table layout or chart layout (user will choose later)comparison→ dual-column/comparison layouttimeline→ timeline layoutlist_items→ bullet list layoutnarrative→ content/description layoutimage_reference→ image-focused layout
- Match content types to available layouts in master deck (by layout
typefield) - If no matching layout: fall back to generic content layout
- Respect
n_slidesconstraint: merge/drop low-priority blocks if too many, split high-content blocks if too few - Use LLM for ambiguous mappings (existing
generate_presentation_structurepattern) - 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
- Determine required slide types based on content:
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:ChartDataExtractorclass:extract(content_block: ContentBlock, table_data: TableData | None) → ChartData | None:- If
table_dataprovided: convert directly toChartData - If
content_block.type == metric: try to extract numeric series from text - Use LLM for complex extraction: "Convert this data to chart format"
- 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)
- 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"
- If
-
Create
backend/services/native_chart_service.py:NativeChartServiceclass (extendsPptxPresentationCreator):add_native_chart(slide, chart_data: ChartData, position, size):- For bar/column/line/pie/doughnut/area/scatter/bubble:
- Use
python-pptxchart API directly:slide.shapes.add_chart() - Set chart data from
ChartData.series - Apply brand colors from client config
- Set fonts from brand config
- Use
- For Gantt:
- Build from shapes: one
add_shape()per task bar (rectangle) - Add text labels, date markers, connectors
- Apply brand colors
- Build from shapes: one
- 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
- For bar/column/line/pie/doughnut/area/scatter/bubble:
-
Modify
backend/services/pptx_presentation_creator.py:- Import and use
NativeChartServicefor chart shapes - Replace any image-based chart rendering with native chart calls
- Import and use
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:BrandEnforcementServiceclass:get_brand_context_for_llm(client_id: UUID) → str:- Load
BrandConfigModelfor client - Build a prompt snippet with voice rules, examples, tone guidelines
- Used to inject into LLM system prompts during content generation
- Load
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
- Walk all slides → all shapes → replace:
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."
- Brand voice guidelines (from
- 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)
- Extend
-
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:JobQueueServiceclass:enqueue(job_type: str, payload: dict) → UUID— push job to ARQ Redis queue, createJobModelin DBget_status(job_id: UUID) → JobModel— poll job statuscancel(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):- Load job from DB, update status →
processing - Load brief content + attachments
- Run
ContentIntelligenceService.classify() - Run
SlideMappingEngine.map() - Run outline generation (LLM)
- Run slide content generation (LLM, batched 10 at a time)
- Run asset generation (images, icons — parallel)
- Run chart data extraction + native chart prep
- Run brand enforcement
- Save slides to DB
- Update job status →
completed, progress → 100
- At each step: update
JobModel.progressandprogress_message - On failure: update status →
failed, save error_message - SSE: push progress events via Redis pub/sub (existing SSE pattern)
- Load job from DB, update status →
-
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
workerservice: same image asapi, entrypointpython -m arq backend.workers.main.WorkerSettings
- Add
-
Create
backend/api/v1/ppt/endpoints/jobs.py:GET /api/v1/ppt/jobs/{id}→ job status + progressGET /api/v1/ppt/jobs/{id}/stream→ SSE stream of job progress eventsDELETE /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
- Client selector dropdown (only assigned clients from
-
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.
- State:
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-kitdependency) - 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)
- LEFT panel: parsed source content (brief markdown rendered as formatted text)
- 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
- Split view (side-by-side):
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}/streamfor 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
- Click image on slide → popover shows:
-
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
- "Edit data" → opens
- Click table → context menu:
-
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
- Body:
GET /api/v1/ppt/presentation/{id}/comments→ list comments/status changesPOST /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):- Load presentation + slides from DB
- Load master deck config + brand config
- Convert slide data →
PptxPresentationModel(existing pipeline via Next.js API) - For chart slides: use
NativeChartService.add_native_chart()instead of image - Run
BrandEnforcementService.enforce_on_pptx_model()— final brand pass - Render PPTX via
PptxPresentationCreator - If format == "pdf": also render via Puppeteer (existing pipeline)
- Audit log: record export action
- 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
- Two-level navigation:
-
Create
frontend/store/slices/clientSlice.ts:- State:
{clients: Client[], selectedClientId, masterDecks: MasterDeck[], presentations: Presentation[]} - Thunks:
fetchClients(),fetchMasterDecks(clientId),fetchPresentations(clientId)
- State:
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:RetentionServiceclass:run_cleanup()— scheduled task (daily via ARQ cron):- Query all clients with
retention_days IS NOT NULL - For each client: find presentations where
created_at < now - retention_daysanddeleted_at IS NULL - Soft-delete expired presentations (set
deleted_at) - Delete associated files from filesystem
- Audit log each deletion
- Query all clients with
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_cleanupdaily at 2:00 AM - Schedule
purge_soft_deletedweekly
- Schedule
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: #...; }
- Define CSS custom properties for brand-adaptive colors:
-
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 testruns all backend testsmake test-e2eruns 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)