Files
office_translator/_bmad-output/planning-artifacts/epics.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:47 +02:00

49 KiB

stepsCompleted, status, completedAt, inputDocuments
stepsCompleted status completedAt inputDocuments
1
2
3
4
complete 2026-02-19
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