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