--- stepsCompleted: [1, 2, 3, 4] status: complete completedAt: '2026-02-19' inputDocuments: - prd.md - architecture.md - user-directive-frontend-merge --- # office_translator - Epic Breakdown ## Overview This document provides the complete epic and story breakdown for office_translator, decomposing the requirements from the PRD, UX Design if it exists, and Architecture requirements into implementable stories. ## Requirements Inventory ### Functional Requirements **Document Translation (FR1-FR8):** - FR1: Users can upload Excel files (.xlsx) for translation - FR2: Users can upload Word files (.docx) for translation - FR3: Users can upload PowerPoint files (.pptx) for translation - FR4: Users can select source and target languages for translation - FR5: Users can choose translation mode (Classic or LLM) based on their subscription tier - FR6: System can translate documents using Google/DeepL providers (Classic mode) - FR7: System can translate documents using LLM providers (Ollama, OpenAI, OpenAI-compatible) - FR8: System can process multiple translation requests concurrently **Format Preservation (FR9-FR16):** - FR9: System preserves document layout after translation (colors, fonts, spacing) - FR10: System preserves merged cells in Excel files - FR11: System preserves tables in Word and PowerPoint files - FR12: System preserves images and their positions in all document types - FR13: System preserves charts and their data links in Excel files - FR14: System preserves formulas in Excel files (translating only text content) - FR15: System preserves slide layouts and animations in PowerPoint files - FR16: Users can open translated files in Microsoft Office without errors **User Management & Authentication (FR17-FR22):** - FR17: Users can create an account with email and password - FR18: Users can log in to the web application via JWT authentication - FR19: Users can log out of the web application - FR20: System assigns users to subscription tiers (Free or Pro) - FR21: Admin can change user tier from Free to Pro manually - FR22: Admin can change user tier from Pro to Free manually **Subscription & Billing (FR23-FR28):** - FR23: System enforces daily file quotas based on user tier - FR24: Free users are limited to 5 files per day - FR25: Pro users have unlimited translations (fair use policy) - FR26: Pro users can access LLM translation modes - FR27: System tracks translation usage per user for billing purposes - FR28: Payment module updates user tier upon successful payment **API & Integration (FR29-FR35):** - FR29: Pro users can generate API keys from their dashboard - FR30: Pro users can revoke their own API keys - FR31: Admin can revoke any user's API key - FR32: System authenticates API requests via X-API-Key header - FR33: System provides REST API endpoints under /api/v1/ prefix - FR34: System returns errors in structured JSON format with error code and message - FR35: System provides interactive API documentation (OpenAPI/Swagger) **Webhooks (FR36-FR38, FR65-FR66):** - FR36: Users can specify webhook URL per translation request (passed as parameter) - FR37: System sends POST request to webhook URL when translation completes - FR38: System includes translation metadata in webhook payload (file ID, status, timestamp) - FR65: Webhook URL is passed as parameter in the API request body - FR66: System sends webhook notification only if URL is provided in request **Admin Dashboard (FR39-FR45):** - FR39: Admin can log in to admin dashboard with secure authentication - FR40: Admin can view system health metrics (disk space, memory usage) - FR41: Admin can view provider status (Google, DeepL, Ollama, OpenAI connectivity) - FR42: Admin can view translation statistics (count, errors, usage) - FR43: Admin can view rate limiting status per user - FR44: Admin can trigger manual cleanup of orphaned temporary files - FR45: Admin can view error logs with structured information **Web User Interface (FR46-FR49):** - FR46: Users can upload files via drag-and-drop interface - FR47: Users see real-time progress indicator during translation - FR48: Users can download translated files after completion - FR49: Users receive clear error messages for unsupported file formats **File Management (FR50-FR54):** - FR50: System validates file format before processing (xlsx, docx, pptx only) - FR51: System stores uploaded files in temporary location - FR52: System automatically deletes files after TTL (60 minutes) - FR53: System deletes files immediately after successful download - FR54: System logs file metadata (name, size, hash, timestamp) without content **Error Handling (FR55-FR57):** - FR55: System returns HTTP 400 errors for client-side issues (invalid format, quota exceeded) - FR56: System never returns HTTP 500 errors (graceful degradation required) - FR57: System provides actionable error messages in user's language **Glossaries & Custom Prompts - Pro Feature (FR58-FR61):** - FR58: Pro users can include a custom glossary in translation requests - FR59: Pro users can include custom system prompt/instructions in translation requests - FR60: System applies glossary terms during LLM translation - FR61: System applies custom prompts to guide LLM translation context **URL Ingestion - Automation (FR62-FR64):** - FR62: API can accept a file URL as input (in addition to binary upload) - FR63: System downloads files from public URLs (S3, Drive, etc.) before processing - FR64: System validates downloaded files against supported formats ### NonFunctional Requirements **Performance (NFR1-NFR5):** - NFR1: Temps de réponse mode classique < 15 secondes pour document 10 pages - NFR2: Temps de réponse mode LLM 1-3 minutes maximum avec progression visible - NFR3: Feedback utilisateur - Progression affichée en temps réel (< 500ms latence) - NFR4: Concurrence - 5 conversions simultanées sans dégradation perceptible - NFR5: Interface Admin - Réponse < 2 secondes même pendant conversions **Security (NFR6-NFR11):** - NFR6: Authentification Web - JWT avec Access Token (15min) + Refresh Token (7 jours) - NFR7: Authentification API - Clés API (256-bit minimum) via header X-API-Key - NFR8: Chiffrement transit - HTTPS obligatoire (TLS 1.2+) - NFR9: Chiffrement repos - Non requis (pas de stockage persistant de documents) - NFR10: Secrets - Variables d'environnement, jamais dans le code - NFR11: Données sensibles - Aucun contenu de document dans les logs **Reliability (NFR12-NFR14):** - NFR12: Gestion d'erreurs - 0% d'erreurs HTTP 500 - toujours retour 4xx avec message JSON - NFR13: Disponibilité providers - Fallback automatique entre providers si l'un échoue - NFR14: Récupération fichiers - Fichiers temporaires nettoyés même après crash (TTL 60min) **Data Retention & Privacy (NFR15-NFR17):** - NFR15: Rétention fichiers - 60 minutes maximum, suppression après téléchargement - NFR16: Logs - Métadonnées uniquement (nom, taille, hash, timestamp) - NFR17: RGPD - Droit à l'oubli respecté par design (zero retention) **API Quality (NFR18-NFR21):** - NFR18: Documentation - OpenAPI 3.0 interactif (Swagger UI ou Redoc) - NFR19: Erreurs API - Format JSON structuré : `{error, message, details?}` - NFR20: Rate limiting - Par utilisateur, réponse 429 avec `Retry-After` header - NFR21: Versioning - Préfixe /api/v1/ obligatoire ### Additional Requirements **From Architecture Document:** - **Brownfield Context**: Pas de starter template - refactoring de l'existant vers Clean Architecture - **Backend Structure**: FastAPI + SQLAlchemy 2.0 + Alembic migrations - **Frontend Stack**: Next.js 15 App Router + TanStack Query + Tailwind CSS - **Database**: PostgreSQL (prod) / SQLite (dev), user.tier field direct sur modèle User - **Caching/Rate Limiting**: Redis pour rate limiting distribué - **Logging**: structlog pour JSON structured logs - **Auth Stack**: PyJWT + passlib[bcrypt] + secrets.token_urlsafe(32) - **API Documentation**: Swagger UI + ReDoc - **Deployment**: Docker Compose + VPS + Reverse Proxy **Architecture Naming Conventions:** - Backend Python: snake_case (fichiers, variables, fonctions), PascalCase (classes) - Frontend TypeScript: camelCase (variables), PascalCase (components), minuscules (page.tsx) - API JSON: snake_case pour tous les champs - API Response: `{data, meta}` succès, `{error, message, details?}` erreur **Colocation Pattern (Frontend):** - Components/hooks/types spécifiques à une page → dans le dossier de la route - Seuls composants GLOBAUX dans `src/components/ui/` - Seuls utilitaires GLOBAUX dans `src/lib/` **⭐ FRONTEND MERGE STRATEGY (User Directive):** ``` CRITICAL: Stratégie d'implémentation Frontend DEUX DOSSIERS EXISTANTS: - frontend/ → Ancien code (logique métier, appels API, state) - office-translator-landing-page/ → Nouveau design (UI/UX, shadcn/ui, Tailwind) DIRECTIVE POUR TOUTES LES STORIES FRONTEND: 1. Piocher les composants visuels et layouts depuis office-translator-landing-page/ 2. Câbler avec la logique métier (TanStack Query, API calls) depuis frontend/ 3. Résultat: Nouveau frontend unifié avec UI moderne + logique refactorée EXEMPLE POUR ACCEPTANCE CRITERIA: - "Utiliser les composants shadcn/ui depuis office-translator-landing-page/components/" - "Migrer la logique d'appel API depuis frontend/src/app/..." - "Intégrer avec TanStack Query pour le data fetching" ``` ### FR Coverage Map | FR | Epic | Description | |----|------|-------------| | FR1 | Epic 2 | Upload Excel files | | FR2 | Epic 2 | Upload Word files | | FR3 | Epic 2 | Upload PowerPoint files | | FR4 | Epic 2 | Select source/target languages | | FR5 | Epic 2 | Choose translation mode (Classic/LLM) | | FR6 | Epic 2 | Google/DeepL providers | | FR7 | Epic 2 | LLM providers (Ollama, OpenAI) | | FR8 | Epic 2 | Concurrent translation requests | | FR9 | Epic 2 | Preserve document layout | | FR10 | Epic 2 | Preserve merged cells | | FR11 | Epic 2 | Preserve tables | | FR12 | Epic 2 | Preserve images | | FR13 | Epic 2 | Preserve charts | | FR14 | Epic 2 | Preserve formulas | | FR15 | Epic 2 | Preserve slide layouts | | FR16 | Epic 2 | Files open in Office without errors | | FR17 | Epic 1 | Create account | | FR18 | Epic 1 | Login via JWT | | FR19 | Epic 1 | Logout | | FR20 | Epic 1 | Assign subscription tier | | FR21 | Epic 1 | Admin changes Free→Pro | | FR22 | Epic 1 | Admin changes Pro→Free | | FR23 | Epic 1 | Enforce daily quotas | | FR24 | Epic 1 | Free limit 5 files/day | | FR25 | Epic 1 | Pro unlimited | | FR26 | Epic 1 | Pro access LLM modes | | FR27 | Epic 1 | Track usage for billing | | FR28 | Epic 1 | Payment updates tier | | FR29 | Epic 3 (Backend) + Epic 4 (UI) | Generate API keys | | FR30 | Epic 3 (Backend) + Epic 4 (UI) | Revoke own API keys | | FR31 | Epic 3 | Admin revoke any API key | | FR32 | Epic 3 | X-API-Key header auth | | FR33 | Epic 3 | REST /api/v1/ endpoints | | FR34 | Epic 3 | JSON error format | | FR35 | Epic 3 | OpenAPI documentation | | FR36 | Epic 3 | Webhook URL parameter | | FR37 | Epic 3 | POST to webhook on complete | | FR38 | Epic 3 | Webhook payload metadata | | FR39 | Epic 5 | Admin login | | FR40 | Epic 5 | System health metrics | | FR41 | Epic 5 | Provider status | | FR42 | Epic 5 | Translation statistics | | FR43 | Epic 5 | Rate limiting status | | FR44 | Epic 5 | Manual file cleanup | | FR45 | Epic 5 | Error logs | | FR46 | Epic 4 | Drag-and-drop upload | | FR47 | Epic 4 | Real-time progress | | FR48 | Epic 4 | Download translated files | | FR49 | Epic 4 | Error messages for formats | | FR50 | Epic 2 | Validate file format | | FR51 | Epic 2 | Temporary file storage | | FR52 | Epic 2 | Auto delete after TTL | | FR53 | Epic 2 | Delete after download | | FR54 | Epic 2 | Log file metadata | | FR55 | Epic 2 | HTTP 400 errors | | FR56 | Epic 2 | No HTTP 500 errors | | FR57 | Epic 2 | Actionable error messages | | FR58 | Epic 3 (Backend) + Epic 4 (UI) | Custom glossary in requests | | FR59 | Epic 3 (Backend) + Epic 4 (UI) | Custom prompts in requests | | FR60 | Epic 3 | Apply glossary during LLM | | FR61 | Epic 3 | Apply prompts during LLM | | FR62 | Epic 2 | Accept file URL input | | FR63 | Epic 2 | Download from public URLs | | FR64 | Epic 2 | Validate downloaded files | | FR65 | Epic 3 | Webhook URL in request body | | FR66 | Epic 3 | Webhook only if URL provided | ## Epic List ### Epic 1: Backend - Authentification & Gestion Utilisateurs Users can register, login, logout, and have tier-based access (Free/Pro). Admin can manually change user tiers. System enforces quotas and tracks usage. **FRs covered:** FR17-FR28 (12 FRs) **NFRs covered:** NFR6, NFR7, NFR10, NFR20 ### Epic 2: Backend - Moteur de Traduction Users can translate Excel/Word/PPT documents with perfect format preservation. System handles file validation, storage, cleanup, and URL ingestion. Zero HTTP 500 errors. **FRs covered:** FR1-FR16, FR50-FR57, FR62-FR64 (27 FRs) **NFRs covered:** NFR1-NFR4, NFR12-NFR17 ### Epic 3: Backend - API & Automation (Pro) Pro users (Thomas) can automate translations via REST API with API keys, webhooks, and glossaries/prompts. System provides OpenAPI docs and structured JSON errors. **Backend FRs covered:** FR29-FR35, FR36-FR38, FR58-FR61 (backend logic), FR65-FR66 (15 FRs backend) **NFRs covered:** NFR7, NFR18-NFR21 ### Epic 4: Frontend - Web Translation & User Dashboard (Merge) Users (Marie) can translate documents via web UI with drag-and-drop, progress indicator, and download. Pro users can manage their API keys and glossaries in their Dashboard. **FRs covered:** FR46-FR49 (Web UI), FR29-FR30 (API keys UI), FR58-FR59 (Glossaries UI) **🚨 MERGE DIRECTIVE:** - Translation UI: `office-translator-landing-page/components/hero-section.tsx`, `translation-card.tsx` - Dashboard layout: `office-translator-landing-page/app/dashboard/` - API Keys UI: `api-automation-card.tsx` - Glossaries UI: `glossary-context-card.tsx` - Wire with TanStack Query + backend API ### Epic 5: Frontend - Admin Dashboard (Merge) Admin can monitor system health, manage users/tiers, view provider status, trigger file cleanup, and view logs. **FRs covered:** FR39-FR45 (7 FRs) **🚨 MERGE DIRECTIVE:** - Admin layout: `office-translator-landing-page/app/admin/` - Components: `admin-sidebar.tsx`, `admin-header.tsx`, `admin-user-table.tsx`, `admin-system-health.tsx`, `admin-provider-status.tsx` - Wire with backend Admin API ### Epic 6: Infrastructure - Production Readiness System is deployable on VPS with Docker Compose, Redis, HTTPS, and structured logging. **Technical scope:** - Docker Compose (backend + frontend + Redis + DB) - Redis for rate limiting - structlog for JSON logs - VPS deployment with reverse proxy (Traefik/Nginx) - Environment configuration **NFRs covered:** NFR5, NFR8, NFR10-NFR14, NFR20 --- ## Epic 1: Backend - Authentification & Gestion Utilisateurs Users can register, login, logout, and have tier-based access (Free/Pro). Admin can manually change user tiers. System enforces quotas and tracks usage. **FRs covered:** FR17-FR28 (12 FRs) **NFRs covered:** NFR6, NFR7, NFR10, NFR20 ### Story 1.1: Setup Base de Données & Modèle User As a **Developer**, I want **to setup the database with Alembic and create the User model with tier field**, So that **the application can persist user data and manage subscriptions**. **Acceptance Criteria:** **Given** a fresh project setup with FastAPI and SQLAlchemy **When** I run Alembic migrations **Then** the `users` table is created with columns: id (UUID), email, hashed_password, tier (default "free"), daily_translation_count, created_at, updated_at **And** Alembic is configured for async SQLAlchemy **And** PostgreSQL works in production, SQLite in development **And** secrets are loaded from environment variables (NFR10) ### Story 1.2: Inscription Utilisateur As a **new user**, I want **to create an account with email and password**, So that **I can access the translation service**. **Acceptance Criteria:** **Given** I am on the registration page **When** I submit a valid email and password **Then** a new user account is created with tier="free" **And** password is hashed using passlib[bcrypt] **And** I receive a 201 response with user ID **And** duplicate email returns 400 with error "EMAIL_EXISTS" **And** invalid email format returns 400 with error "INVALID_EMAIL" ### Story 1.3: Login Utilisateur (JWT) As a **registered user**, I want **to login with email and password**, So that **I can access my dashboard and translation features**. **Acceptance Criteria:** **Given** I have a registered account **When** I submit correct email and password **Then** I receive an access_token (15min expiry) and refresh_token (7 days) **And** tokens are JWT signed with SECRET_KEY from env **And** incorrect password returns 401 with error "INVALID_CREDENTIALS" **And** account not found returns 401 with error "USER_NOT_FOUND" ### Story 1.4: Logout Utilisateur As a **logged-in user**, I want **to logout and invalidate my session**, So that **my account remains secure**. **Acceptance Criteria:** **Given** I am logged in with a valid access token **When** I call the logout endpoint **Then** my refresh token is invalidated **And** subsequent API calls with the old token return 401 ### Story 1.5: Refresh Token As a **logged-in user**, I want **to refresh my access token using my refresh token**, So that **I can stay logged in without re-entering credentials**. **Acceptance Criteria:** **Given** I have a valid refresh token **When** I call the refresh endpoint **Then** I receive a new access_token (15min expiry) **And** invalid/expired refresh token returns 401 with error "TOKEN_EXPIRED" ### Story 1.6: Middleware Rate Limiting par Tier As a **system**, I want **to enforce daily translation quotas based on user tier**, So that **free users are limited and system resources are protected**. **Acceptance Criteria:** **Given** a user with tier="free" has translated 5 files today **When** they attempt another translation **Then** they receive 429 with error "QUOTA_EXCEEDED" and Retry-After header **And** user with tier="pro" has no daily limit **And** daily_translation_count is reset at midnight UTC **And** rate limiting uses Redis with sliding window algorithm **And** response includes `meta.rate_limit_remaining` field ### Story 1.7: Admin - Changement de Tier Manuel As an **Admin**, I want **to change a user's tier from Free to Pro (or vice versa)**, So that **I can manually onboard Pro clients**. **Acceptance Criteria:** **Given** I am logged in as admin **When** I update a user's tier via PATCH /api/v1/admin/users/{user_id} **Then** the user's tier is updated immediately **And** if upgraded to Pro, user can access LLM modes immediately **And** if downgraded to Free, rate limiting applies immediately **And** action is logged with timestamp and admin ID ### Story 1.8: Tracking Usage pour Billing As a **system**, I want **to track translation usage per user**, So that **I can bill Pro users and monitor usage**. **Acceptance Criteria:** **Given** a user completes a translation **When** the translation succeeds **Then** user's daily_translation_count is incremented **And** a translation_logs entry is created with: user_id, file_name, file_size, timestamp, status, provider_used **And** no file content is logged (NFR11, NFR16) --- ## Epic 2: Backend - Moteur de Traduction Users can translate Excel/Word/PPT documents with perfect format preservation. System handles file validation, storage, cleanup, and URL ingestion. Zero HTTP 500 errors. **FRs covered:** FR1-FR16, FR50-FR57, FR62-FR64 (27 FRs) **NFRs covered:** NFR1-NFR4, NFR12-NFR17 ### Story 2.1: Abstraction Provider (Base + Registry) As a **Developer**, I want **to create an abstract TranslationProvider base class**, So that **multiple translation providers can be plugged in with fallback**. **Acceptance Criteria:** **Given** the translation module structure **When** I implement TranslationProvider base class **Then** it defines abstract methods: translate_text(), get_name(), is_available() **And** a ProviderRegistry can register and retrieve providers **And** providers can be configured via environment variables **And** unit tests verify provider interface ### Story 2.2: Provider Google Translate As a **system**, I want **to integrate Google Translate API as a provider**, So that **users can translate documents in Classic mode**. **Acceptance Criteria:** **Given** GOOGLE_API_KEY is configured in environment **When** GoogleTranslateProvider.translate_text() is called **Then** text is translated using Google Translate API **And** API errors return graceful error (not HTTP 500) **And** provider health check returns "ok" or "unavailable" ### Story 2.3: Provider DeepL As a **system**, I want **to integrate DeepL API as a provider**, So that **users can translate documents with high quality**. **Acceptance Criteria:** **Given** DEEPL_API_KEY is configured in environment **When** DeepLProvider.translate_text() is called **Then** text is translated using DeepL API **And** DeepL Pro vs Free API endpoints are auto-detected **And** API errors return graceful error (not HTTP 500) ### Story 2.4: Provider Ollama (LLM Local) As a **system**, I want **to integrate Ollama as an LLM provider**, So that **Pro users can translate with local LLMs**. **Acceptance Criteria:** **Given** OLLAMA_BASE_URL is configured (default: http://localhost:11434) **When** OllamaProvider.translate_text() is called with model and prompt **Then** text is translated using the specified Ollama model **And** connection timeout returns error "PROVIDER_UNAVAILABLE" with message "Ollama service unreachable" **And** custom system prompt can be injected ### Story 2.5: Provider OpenAI (LLM Cloud) As a **system**, I want **to integrate OpenAI API as an LLM provider**, So that **Pro users can translate with GPT models**. **Acceptance Criteria:** **Given** OPENAI_API_KEY is configured in environment **When** OpenAIProvider.translate_text() is called **Then** text is translated using GPT-4 or specified model **And** custom system prompt can be injected **And** API rate limits return error "PROVIDER_RATE_LIMITED" with retry suggestion ### Story 2.6: Provider Fallback Chain As a **system**, I want **to automatically fallback to another provider if the primary fails**, So that **translation remains available even if one provider is down**. **Acceptance Criteria:** **Given** primary provider (e.g., Google) returns an error **When** the translation service catches the error **Then** it tries the next provider in the fallback chain (DeepL → Ollama → OpenAI) **And** if all providers fail, returns error "ALL_PROVIDERS_FAILED" (HTTP 502, not 500) **And** the successful provider name is returned in `meta.provider_used` ### Story 2.7: Processor Excel (.xlsx) As a **user**, I want **to translate Excel files while preserving format, merged cells, charts, and formulas**, So that **I receive a translated file ready to use without reformatting**. **Acceptance Criteria:** **Given** I upload a valid .xlsx file **When** the ExcelProcessor processes the file **Then** only text cells are translated, formulas are preserved **And** merged cells remain merged in the output **And** charts and their data links remain intact **And** cell colors, fonts, and formatting are preserved **And** **the translated file opens in Microsoft Excel without corruption error** (FR16) **And** unsupported formats return 400 with error "INVALID_FORMAT" ### Story 2.8: Processor Word (.docx) As a **user**, I want **to translate Word files while preserving format, tables, and images**, So that **I receive a translated document ready to use**. **Acceptance Criteria:** **Given** I upload a valid .docx file **When** the WordProcessor processes the file **Then** paragraphs, headers, and footers are translated **And** tables are preserved with correct structure **And** images remain in their original positions **And** fonts, colors, and styles are preserved **And** **the translated file opens in Microsoft Word without corruption error** (FR16) ### Story 2.9: Processor PowerPoint (.pptx) As a **user**, I want **to translate PowerPoint files while preserving slides, layouts, and images**, So that **I receive a translated presentation ready to present**. **Acceptance Criteria:** **Given** I upload a valid .pptx file **When** the PowerPointProcessor processes the file **Then** text boxes and shapes are translated **And** slide layouts and master slides are preserved **And** images and charts remain in position **And** animations are preserved **And** **the translated file opens in Microsoft PowerPoint without corruption error** (FR16) ### Story 2.10: Endpoint POST /api/v1/translate (Core) As a **user**, I want **to submit a document for translation via API**, So that **I can get my document translated**. **Acceptance Criteria:** **Given** I am authenticated (JWT or API Key) **When** I POST to /api/v1/translate with file, source_lang, target_lang **Then** the file is validated (format xlsx/docx/pptx only, max size 50MB) **And** if valid, returns 202 with `{data: {id, status: "processing"}, meta: {rate_limit_remaining}}` **And** if invalid format, returns 400 with error "INVALID_FORMAT" and accepted formats list **And** if quota exceeded, returns 429 with error "QUOTA_EXCEEDED" **And** if file too large, returns 413 with error "FILE_TOO_LARGE" **And** translation is processed asynchronously ### Story 2.11: Progress Feedback Temps Réel As a **user**, I want **to see real-time progress during translation**, So that **I know the status of my translation**. **Acceptance Criteria:** **Given** I have submitted a translation request **When** I poll GET /api/v1/translations/{id} **Then** I receive `{data: {id, status, progress_percent, current_step}, meta: {...}}` **And** progress updates within 500ms of actual progress (NFR3) **And** status can be: "queued", "processing", "completed", "failed" **And** for LLM mode, progress shows "translating slide X/Y" or "processing cell X/Y" ### Story 2.12: Téléchargement Fichier Traduit As a **user**, I want **to download my translated file**, So that **I can use it**. **Acceptance Criteria:** **Given** my translation has status "completed" **When** I GET /api/v1/download/{id} **Then** the translated file is returned as binary download **And** Content-Disposition header includes original filename with "_translated" suffix **And** file is deleted immediately after successful download (FR53) **And** if translation not found or expired, returns 404 with error "FILE_EXPIRED" ### Story 2.13: Validation Format Fichier As a **system**, I want **to validate uploaded files before processing**, So that **only valid Office files are processed**. **Acceptance Criteria:** **Given** a user uploads a file **When** the system validates the file **Then** only .xlsx, .docx, .pptx extensions are accepted (FR50) **And** file magic bytes are checked (not just extension) **And** corrupted/invalid files return 400 with error "CORRUPTED_FILE" **And** PDF or other formats return 400 with error "INVALID_FORMAT" listing accepted formats ### Story 2.14: Stockage Temporaire & Métadonnées As a **system**, I want **to store uploaded files temporarily with metadata logging**, So that **files can be processed and tracked**. **Acceptance Criteria:** **Given** a valid file is uploaded **When** it is stored **Then** file is saved to /tmp with unique ID as filename **And** metadata is logged: original_filename, file_size, file_hash (SHA256), timestamp, user_id **And** NO file content is logged (NFR11, NFR16) **And** file path is stored in Redis with TTL 60 minutes ### Story 2.15: Job Cleanup Fichiers (TTL 60 min) As a **system**, I want **to automatically delete temporary files after 60 minutes**, So that **disk space is managed and user data is not retained**. **Acceptance Criteria:** **Given** files are stored with TTL metadata **When** the cleanup job runs (every 5 minutes) **Then** all files older than 60 minutes are hard-deleted from /tmp **And** orphaned files (no DB record) are also deleted **And** cleanup is logged: count of files deleted, space freed **And** job continues even if individual file deletion fails **And** this ensures zero data retention (NFR15, NFR17) ### Story 2.16: URL Ingestion (Téléchargement depuis URL) As a **Pro user (Thomas)**, I want **to provide a file URL instead of uploading binary**, So that **I can automate translations from cloud storage (S3, Drive)**. **Acceptance Criteria:** **Given** I POST to /api/v1/translate with `file_url` parameter instead of file **When** the system receives the request **Then** it downloads the file from the URL (FR63) **And** validates the downloaded file against supported formats (FR64) **And** if download fails, returns 400 with error "URL_DOWNLOAD_FAILED" and details **And** if URL returns non-200, returns 400 with error "URL_UNREACHABLE" **And** max download size is 50MB ### Story 2.17: Gestion d'Erreurs Graceful (Zero HTTP 500) As a **system**, I want **to never return HTTP 500 errors**, So that **users always receive actionable error messages**. **Acceptance Criteria:** **Given** any error occurs during request processing **When** an exception is raised **Then** the global error handler catches it **And** returns appropriate 4xx error with JSON `{error: "CODE", message: "...", details: {...}}` **And** unexpected errors return 500 BUT are logged and converted to `{error: "INTERNAL_ERROR", message: "Une erreur inattendue s'est produite"}` (no stack trace exposed) **And** error messages are in user-friendly French **And** 0% HTTP 500 with exposed stack traces (NFR12) --- ## Epic 3: Backend - API & Automation (Pro) Pro users (Thomas) can automate translations via REST API with API keys, webhooks, and glossaries/prompts. System provides OpenAPI docs and structured JSON errors. **FRs covered:** FR29-FR35, FR36-FR38, FR58-FR61 (backend), FR65-FR66 **NFRs covered:** NFR7, NFR18-NFR21 ### Story 3.1: Modèle API Key & Génération As a **Pro user**, I want **to generate an API key from my dashboard**, So that **I can authenticate my automated requests**. **Acceptance Criteria:** **Given** I am a Pro user (tier="pro") **When** I POST to /api/v1/api-keys **Then** a new API key is generated using secrets.token_urlsafe(32) (256-bit) **And** key is stored hashed in database (never in plaintext after generation) **And** key is returned once: `sk_live_{random_string}` **And** key is associated with my user_id **And** if I am Free tier, returns 403 with error "PRO_FEATURE_REQUIRED" ### Story 3.2: Révocation API Key (User) As a **Pro user**, I want **to revoke my own API key**, So that **I can secure my account if key is compromised**. **Acceptance Criteria:** **Given** I have an active API key **When** I DELETE /api/v1/api-keys/{key_id} **Then** the key is marked as revoked **And** subsequent requests with this key return 401 "API_KEY_REVOKED" **And** revocation is effective immediately ### Story 3.3: Admin - Révocation API Key (Any User) As an **Admin**, I want **to revoke any user's API key**, So that **I can manage security and abuse**. **Acceptance Criteria:** **Given** I am logged in as admin **When** I DELETE /api/v1/admin/api-keys/{key_id} **Then** the specified key is revoked regardless of owner **And** action is logged with admin_id and reason ### Story 3.4: Authentification API via X-API-Key As a **system**, I want **to authenticate API requests via X-API-Key header**, So that **automation clients can access the API without JWT**. **Acceptance Criteria:** **Given** a request includes header `X-API-Key: sk_live_...` **When** the auth middleware validates the key **Then** if valid, request proceeds with user context **And** if invalid, returns 401 with error "INVALID_API_KEY" **And** if revoked, returns 401 with error "API_KEY_REVOKED" **And** if expired/missing, returns 401 with error "MISSING_API_KEY" ### Story 3.5: API Versioning /api/v1/ As a **Developer**, I want **all API endpoints prefixed with /api/v1/**, So that **future API versions can coexist without breaking clients**. **Acceptance Criteria:** **Given** the FastAPI router configuration **When** I define endpoints **Then** all are mounted under /api/v1/ prefix **And** unversioned endpoints return 404 **And** version is documented in OpenAPI spec ### Story 3.6: Documentation OpenAPI (Swagger + ReDoc) As a **Developer/API User**, I want **interactive API documentation**, So that **I can understand and test the API**. **Acceptance Criteria:** **Given** the FastAPI application is running **When** I navigate to /docs **Then** Swagger UI is displayed with all endpoints **And** when I navigate to /redoc **Then** ReDoc is displayed with clean documentation **And** all request/response schemas are documented **And** authentication methods (JWT, API Key) are documented **And** error codes are documented with examples ### Story 3.7: Webhook - Spécification URL As a **user**, I want **to specify a webhook URL in my translation request**, So that **I can be notified when translation completes**. **Acceptance Criteria:** **Given** I POST to /api/v1/translate with `webhook_url` parameter **When** the request is validated **Then** webhook_url is stored with the translation job **And** if webhook_url is invalid format, returns 400 with error "INVALID_WEBHOOK_URL" **And** webhook_url is optional (FR65) ### Story 3.8: Webhook - Envoi POST Fire & Forget As a **system**, I want **to send a POST request to the webhook URL when translation completes**, So that **users can automate their workflows**. **Acceptance Criteria:** **Given** a translation job completes with webhook_url specified **When** the status becomes "completed" or "failed" **Then** a POST request is sent to webhook_url **And** payload includes: `{translation_id, status, timestamp, file_name, error_message?}` **And** request timeout is 10 seconds **And** if webhook fails, error is logged but translation still succeeds (Fire & Forget) **And** webhook is sent ONLY if URL was provided (FR66) ### Story 3.9: Glossaires - Endpoint CRUD As a **Pro user**, I want **to create and manage glossaries via API**, So that **I can customize translations with my terminology**. **Acceptance Criteria:** **Given** I am authenticated as Pro user **When** I POST to /api/v1/glossaries with `{name, terms: [{source, target}]}` **Then** a glossary is created and associated with my account **And** I can GET /api/v1/glossaries to list my glossaries **And** I can PATCH /api/v1/glossaries/{id} to update terms **And** I can DELETE /api/v1/glossaries/{id} to remove a glossary **And** Free users receive 403 with error "PRO_FEATURE_REQUIRED" ### Story 3.10: Glossaires - Application lors Traduction LLM As a **Pro user**, I want **my glossary terms to be applied during LLM translation**, So that **translations use my specific terminology**. **Acceptance Criteria:** **Given** I POST to /api/v1/translate with `glossary_id` parameter **When** the LLM provider translates **Then** glossary terms are injected into the system prompt **And** the LLM is instructed to use the specified translations **And** if glossary_id not found, returns 400 with error "GLOSSARY_NOT_FOUND" ### Story 3.11: Custom Prompts - Endpoint CRUD As a **Pro user**, I want **to create and manage custom system prompts**, So that **I can guide the LLM translation context**. **Acceptance Criteria:** **Given** I am authenticated as Pro user **When** I POST to /api/v1/prompts with `{name, content}` **Then** a prompt template is created and associated with my account **And** I can list, update, delete my prompts **And** Free users receive 403 with error "PRO_FEATURE_REQUIRED" ### Story 3.12: Custom Prompts - Application lors Traduction LLM As a **Pro user**, I want **my custom prompt to be applied during LLM translation**, So that **translations follow my specific instructions**. **Acceptance Criteria:** **Given** I POST to /api/v1/translate with `prompt_id` or `custom_prompt` parameter **When** the LLM provider translates **Then** my custom prompt is used as system message **And** the default prompt is replaced, not appended **And** if prompt_id not found, returns 400 with error "PROMPT_NOT_FOUND" --- ## Epic 4: Frontend - Web Translation & User Dashboard (Merge) Users (Marie) can translate documents via web UI with drag-and-drop, progress indicator, and download. Pro users can manage their API keys and glossaries in their Dashboard. **FRs covered:** FR46-FR49, FR29-FR30 (UI), FR58-FR59 (UI) **🚨 MERGE DIRECTIVE:** Use components from `office-translator-landing-page/` and migrate logic from `frontend/` ### Story 4.1: Setup TanStack Query & API Client As a **Developer**, I want **to setup TanStack Query with a typed API client**, So that **frontend can fetch data from backend consistently**. **Acceptance Criteria:** **Given** Next.js 15 App Router project **When** I setup TanStack Query provider and apiClient **Then** apiClient handles base URL from environment **And** apiClient automatically adds JWT token or X-API-Key header **And** apiClient parses error responses into `{error, message, details}` format **And** QueryProvider wraps the app in layout.tsx **And** React hooks (useState) are used for local UI state **And** **migrate apiClient logic from frontend/src/app/ and frontend/src/components/** ### Story 4.2: Landing Page As a **visitor**, I want **to see a clear landing page explaining the product**, So that **I understand what Office Translator offers**. **Acceptance Criteria:** **Given** I navigate to / **When** the page loads **Then** I see hero section with value proposition **And** I see supported formats (Excel, Word, PowerPoint) **And** I see CTAs for "Try Free" and "View Pricing" **And** page is a Server Component (no client JS for initial load) **And** **use components from office-translator-landing-page/components/hero-section.tsx and site-header.tsx** ### Story 4.3: Page Login As a **user**, I want **to login via a clean form**, So that **I can access my dashboard**. **Acceptance Criteria:** **Given** I navigate to /login **When** I submit email and password **Then** if successful, I am redirected to /dashboard **And** if failed, I see error toast with message from API **And** JWT tokens are stored in httpOnly cookies **And** **use LoginForm component pattern from office-translator-landing-page, migrate logic from frontend/src/app/auth/login/** ### Story 4.4: Page Register As a **new user**, I want **to create an account**, So that **I can start translating documents**. **Acceptance Criteria:** **Given** I navigate to /register **When** I submit email and password **Then** if successful, I am logged in and redirected to /dashboard **And** validation errors are shown inline **And** **use RegisterForm component pattern, colocate in app/(auth)/register/** ### Story 4.5: Dashboard Layout As a **logged-in user**, I want **a dashboard layout with sidebar navigation**, So that **I can navigate between features**. **Acceptance Criteria:** **Given** I am logged in **When** I navigate to /dashboard **Then** I see a sidebar with: Translate, API Keys, Glossaries (Pro only) **And** I see user info and logout button **And** layout uses Server Component for auth check **And** **use dashboard layout from office-translator-landing-page/app/dashboard/layout.tsx and dashboard-sidebar.tsx** ### Story 4.6: Page Translation - Upload As a **user**, I want **to upload a document via drag-and-drop**, So that **I can start a translation**. **Acceptance Criteria:** **Given** I am on /dashboard/translate **When** I drag a file onto the drop zone or click to browse **Then** the file is validated (format xlsx/docx/pptx) **And** if invalid, I see error "Format non supporté. Formats acceptés : .xlsx, .docx, .pptx" **And** I see file preview with name and size **And** **use translation-card.tsx from office-translator-landing-page/components/, colocate in app/dashboard/translate/** ### Story 4.7: Page Translation - Configuration As a **user**, I want **to select source and target languages and translation mode**, So that **the translation meets my needs**. **Acceptance Criteria:** **Given** I have uploaded a valid file **When** I select languages and mode **Then** I see available languages from GET /api/v1/languages **And** Free users only see "Classic" mode **And** Pro users see "Classic" and "LLM" modes with provider selection **And** **use LanguageSelector component, colocate in app/dashboard/translate/** ### Story 4.8: Page Translation - Progress & Download As a **user**, I want **to see translation progress and download the result**, So that **I know when my file is ready**. **Acceptance Criteria:** **Given** I submit a translation **When** the translation is processing **Then** I see a progress bar with percentage and current step **And** progress updates in real-time (polling every 2 seconds) **When** translation completes **Then** I see "Download" button **And** clicking download triggers file download and shows success message **And** **use TranslationProgress.tsx component, colocate in app/dashboard/translate/** ### Story 4.9: Dashboard - API Keys Management (Pro) As a **Pro user**, I want **to generate and revoke API keys from my dashboard**, So that **I can automate translations**. **Acceptance Criteria:** **Given** I am a Pro user on /dashboard/api-keys **When** I click "Generate new key" **Then** a new API key is displayed once (copy warning) **And** I see list of my keys with: last_used, created_at, status **When** I click "Revoke" on a key **Then** the key is immediately disabled **And** Free users see upgrade prompt instead of keys **And** **use api-automation-card.tsx from office-translator-landing-page/components/, colocate useApiKeys.ts hook** ### Story 4.10: Dashboard - Glossaries Editor (Pro) As a **Pro user**, I want **to create and edit glossaries in my dashboard**, So that **I can customize translations**. **Acceptance Criteria:** **Given** I am a Pro user on /dashboard/glossaries **When** I create a new glossary **Then** I can add term pairs (source → target) **And** I can import/export glossary as CSV **And** I can edit or delete existing glossaries **And** Free users see upgrade prompt **And** **use glossary-context-card.tsx from office-translator-landing-page/components/, colocate useGlossaries.ts hook** --- ## Epic 5: Frontend - Admin Dashboard (Merge) Admin can monitor system health, manage users/tiers, view provider status, trigger file cleanup, and view logs. **FRs covered:** FR39-FR45 **🚨 MERGE DIRECTIVE:** Use components from `office-translator-landing-page/app/admin/` and `components/admin-*` ### Story 5.1: Admin Login As an **Admin**, I want **to login to a secure admin dashboard**, So that **I can manage the system**. **Acceptance Criteria:** **Given** I navigate to /admin/login **When** I submit admin credentials **Then** I am authenticated with admin role **And** I am redirected to /admin **And** non-admin users are rejected with 403 ### Story 5.2: Admin Layout & Navigation As an **Admin**, I want **a dedicated admin layout with navigation**, So that **I can access admin features**. **Acceptance Criteria:** **Given** I am logged in as admin **When** I access /admin **Then** I see sidebar with: Dashboard, Users, System, Logs **And** layout checks admin role before rendering **And** **use admin layout from office-translator-landing-page/app/admin/layout.tsx, admin-sidebar.tsx, admin-header.tsx** ### Story 5.3: Admin - System Health Dashboard As an **Admin**, I want **to see system health metrics**, So that **I can monitor the platform**. **Acceptance Criteria:** **Given** I am on /admin **When** the page loads **Then** I see: disk space usage, memory usage, database status, Redis status **And** I see provider status: Google (ok/error), DeepL (ok/error), Ollama (ok/error), OpenAI (ok/error) **And** metrics refresh every 30 seconds **And** **use admin-system-health.tsx and admin-provider-status.tsx from office-translator-landing-page/components/** ### Story 5.4: Admin - User Management As an **Admin**, I want **to view and manage users**, So that **I can handle subscriptions and abuse**. **Acceptance Criteria:** **Given** I am on /admin/users **When** the page loads **Then** I see a table of users with: email, tier, daily count, created_at **And** I can filter by tier (Free/Pro) **And** I can search by email **When** I click on a user **Then** I can change their tier via dropdown **And** tier change takes effect immediately **And** **use admin-user-table.tsx from office-translator-landing-page/components/** ### Story 5.5: Admin - Translation Statistics As an **Admin**, I want **to see translation statistics**, So that **I can understand usage patterns**. **Acceptance Criteria:** **Given** I am on /admin **When** I view the statistics section **Then** I see: total translations today/week/month, error rate, top users by volume **And** I can see breakdown by provider used **And** I can see breakdown by file format ### Story 5.6: Admin - Manual File Cleanup As an **Admin**, I want **to trigger manual cleanup of orphaned temporary files**, So that **I can free disk space**. **Acceptance Criteria:** **Given** I am on /admin/system **When** I click "Cleanup orphaned files" **Then** cleanup job runs immediately **And** I see result: "X files deleted, Y MB freed" **And** button is disabled while cleanup runs ### Story 5.7: Admin - Error Logs Viewer As an **Admin**, I want **to view structured error logs**, So that **I can debug issues**. **Acceptance Criteria:** **Given** I am on /admin/logs **When** the page loads **Then** I see logs in JSON format with: timestamp, level, message, user_id, error_code **And** I can filter by level (error, warning, info) **And** I can search by error_code or user_id **And** logs are loaded from structlog JSON output **And** NO document content is visible in logs --- ## Epic 6: Infrastructure - Production Readiness System is deployable on VPS with Docker Compose, Redis, HTTPS, and structured logging. **NFRs covered:** NFR5, NFR8, NFR10-NFR14, NFR20 ### Story 6.1: Docker Compose Development As a **Developer**, I want **a Docker Compose setup for local development**, So that **I can run all services consistently**. **Acceptance Criteria:** **Given** docker-compose.dev.yml exists **When** I run `docker-compose -f docker-compose.dev.yml up` **Then** backend (FastAPI :8000), frontend (Next.js :3000), PostgreSQL, Redis start **And** volumes mount for hot reload **And** environment variables are loaded from .env files ### Story 6.2: Docker Compose Production As a **DevOps**, I want **a production Docker Compose setup**, So that **I can deploy to VPS**. **Acceptance Criteria:** **Given** docker-compose.yml exists **When** I deploy to VPS **Then** all services run in production mode **And** containers restart automatically on failure **And** health checks are configured **And** secrets are loaded from environment ### Story 6.3: Redis Configuration As a **system**, I want **Redis configured for rate limiting and caching**, So that **the application scales**. **Acceptance Criteria:** **Given** Redis container is running **When** the application starts **Then** Redis connection is established **And** rate limiting middleware uses Redis **And** translation progress is cached in Redis **And** Redis data is persisted to volume ### Story 6.4: structlog Integration As a **Developer**, I want **structured JSON logging with structlog**, So that **logs are parseable and searchable**. **Acceptance Criteria:** **Given** structlog is configured **When** the application logs **Then** logs are in JSON format with: timestamp, level, event, user_id, request_id **And** NO document content is logged **And** logs are written to stdout (Docker-friendly) **And** log level is configurable via environment ### Story 6.5: Reverse Proxy (Traefik/Nginx) As a **DevOps**, I want **a reverse proxy with HTTPS**, So that **traffic is secure and load is balanced**. **Acceptance Criteria:** **Given** Traefik or Nginx is configured **When** users access the application **Then** all HTTP traffic redirects to HTTPS **And** TLS 1.2+ is enforced **And** HSTS header is set **And** /api/* routes to backend container **And** /* routes to frontend container ### Story 6.6: Environment Configuration As a **Developer**, I want **all configuration via environment variables**, So that **secrets are never in code**. **Acceptance Criteria:** **Given** .env.example documents all required variables **When** I deploy **Then** all secrets come from environment: DATABASE_URL, SECRET_KEY, GOOGLE_API_KEY, DEEPL_API_KEY, OPENAI_API_KEY, OLLAMA_BASE_URL, REDIS_URL **And** missing required variables fail fast with clear error **And** defaults are provided for optional variables --- ## Summary | Epic | Stories | FRs Covered | |------|---------|-------------| | Epic 1: Backend Auth | 8 | FR17-FR28 (12) | | Epic 2: Backend Translation | 17 | FR1-FR16, FR50-FR57, FR62-FR64 (27) | | Epic 3: Backend API | 12 | FR29-FR38, FR58-FR61, FR65-FR66 (15 backend) | | Epic 4: Frontend Web | 10 | FR46-FR49, FR29-FR30 UI, FR58-FR59 UI | | Epic 5: Admin Dashboard | 7 | FR39-FR45 (7) | | Epic 6: Infrastructure | 6 | NFRs | | **TOTAL** | **60 stories** | **66 FRs + 21 NFRs** | --- *Document generated via BMAD create-epics-and-stories workflow — 2026-02-19*