Files
office_translator/_bmad-output/implementation-artifacts/1-8-tracking-usage-pour-billing.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

153 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story 1.8: Tracking Usage pour Billing
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a **system**,
I want **to track translation usage per user**,
so that **I can bill Pro users and monitor usage**.
## Acceptance Criteria
1. **AC1 : Incrément du compteur quotidien** — Given a user completes a translation, when the translation succeeds, then the user's `daily_translation_count` is incremented (déjà assuré par Story 1.6 / tier_quota + sync DB dans `/translate`).
2. **AC2 : Entrée translation_logs** — When the translation succeeds, a `translation_logs` entry is created (table `translations`) with: `user_id`, `file_name` (original_filename), `file_size` (file_size_bytes), `timestamp` (created_at), `status` ("completed"), `provider_used` (provider). Aucun contenu de fichier n'est jamais enregistré (NFR11, NFR16).
3. **AC3 : Pas de contenu dans les logs** — No file content is logged; only metadata (file name, size, timestamp, status, provider). NFR11 et NFR16 respectés.
## Tasks / Subtasks
- [x] **Task 1 : Créer l'entrée Translation après succès** (AC: 2, 3)
- [x] 1.1 Après une traduction réussie dans `POST /translate`, si l'utilisateur est authentifié (`current_user`), créer un enregistrement dans la table `translations` (modèle `Translation`) avec : user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status="completed", completed_at=now. Ne jamais stocker ni logger de contenu de fichier.
- [x] 1.2 Utiliser le repository existant (TranslationRepository.create puis update_status "completed" ou créer en "completed" directement si l'API le permet). Vérifier la signature de `create` dans database/repositories.py.
- [x] 1.3 S'assurer que le provider utilisé (google, deepl, ollama, openai, openrouter, libre) est bien enregistré (variable `provider` dans l'endpoint).
- [x] **Task 2 : Synchronisation daily_translation_count** (AC: 1)
- [x] 2.1 Vérifier que l'incrément Redis (tier_quota_service.increment_on_success) et la mise à jour DB (auth_update_user avec daily_translation_count) restent en place après succès (déjà présents dans main.py).
- [x] 2.2 Si le backend utilise uniquement le stockage JSON (sans DB SQL), documenter le comportement : soit pas d'entrée Translation (optionnel), soit un fichier JSON de "translation_logs" avec les mêmes champs métadonnées uniquement.
- [x] **Task 3 : Tests** (AC: 13)
- [x] 3.1 Test : après une traduction réussie par un utilisateur authentifié, une entrée existe dans `translations` avec user_id, original_filename, file_size_bytes, status="completed", provider correct.
- [x] 3.2 Test : aucun champ ne contient de contenu de document (vérifier schéma et politiques de log).
- [x] 3.3 Test : daily_translation_count utilisateur est incrémenté (déjà couvert par tests 1.6/1.7 si besoin de régression).
## Dev Notes
- **Contexte** : La table `translations` (modèle `Translation`) existe déjà dans database/models.py avec user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status, created_at, completed_at. TranslationRepository a une méthode `create()` et `update_status()`. Actuellement, après succès de `/translate`, on incrémente le quota (Redis + daily_translation_count en DB) mais on ne crée pas d'entrée dans `translations`. La story consiste à créer cette entrée à chaque traduction réussie (utilisateur authentifié).
- **Provider** : Dans main.py l'endpoint reçoit `provider` (openrouter, google, ollama, deepl, libre, openai). Utiliser cette valeur pour `provider_used`.
- **Fichier** : file.filename (original), file_size peut être obtenu après save ou via validation_result / output_info. Utiliser file_size_bytes du fichier source (input_path) ou de la réponse si disponible.
### Project Structure Notes
- Backend : main.py (POST /translate) ; database/models.py (Translation) ; database/repositories.py (TranslationRepository.create, update_status). Pas de nouveau module requis ; brancher lappel au repository après la traduction réussie (après tier_quota increment et avant/suite auth_update_user).
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.8]
- [Source: database/models.py — Translation model]
- [Source: database/repositories.py — TranslationRepository]
- [Source: main.py — POST /translate, tier_quota increment]
- [Source: NFR11, NFR16 — no document content in logs]
---
## Developer Context (Guardrails)
### Technical requirements
- **Backend** : FastAPI. Après une traduction réussie dans `POST /translate`, si `current_user` est défini : (1) garder lappel à `tier_quota_service.increment_on_success(current_user.id)` et `auth_update_user(..., daily_translation_count + 1)` ; (2) créer un enregistrement `Translation` via le repository (DB) avec user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status="completed", completed_at=now. Ne jamais persister ni logger de contenu de fichier.
- **DB** : Utiliser la table existante `translations`. TranslationRepository.create() attend source_language, target_language, provider, file_type, file_size_bytes, page_count (optionnel). Puis appeler update_status(id, "completed") ou adapter create pour accepter status="completed" et completed_at si besoin.
- **Session/DB** : Sassurer dutiliser la même session/engine que le reste de lapp (get_db ou équivalent dans main.py). Si lapp utilise uniquement auth en JSON sans DB, ne pas créer dentrée Translation (ou prévoir un stockage fichier JSON pour “translation_logs” métadonnées uniquement).
### Architecture compliance
- Conventions API : pas de changement dAPI publique ; comportement interne uniquement (enregistrement pour billing/analytics).
- Format des données : snake_case en DB et en JSON. Pas dexposition du contenu des documents (NFR11, NFR16).
### Library / framework
- SQLAlchemy / repository existant (TranslationRepository). Pas de nouvelle lib.
### File structure
- main.py : après succès de la traduction (après excel_translator.translate_file / word / pptx), appeler le repository pour créer lentrée Translation (avec get_db ou session injectée). Si plusieurs chemins (sync/async), utiliser la même session que pour auth_update_user (asyncio.to_thread si blocage).
- database/repositories.py : utiliser create() existant ; ajouter si besoin une méthode create_completed() qui crée directement en status "completed" avec completed_at pour éviter deux appels (create + update_status).
### Testing requirements
- Tests dintégration ou unitaires : (1) traduction réussie avec utilisateur authentifié → une ligne dans `translations` avec les bons champs (user_id, original_filename, file_size_bytes, status="completed", provider) ; (2) aucun champ ne contient de texte traduit ou contenu binaire ; (3) régression : daily_translation_count et quota Redis inchangés (toujours incrémentés). Utiliser un mock du translate_file pour ne pas dépendre des vrais providers.
---
## Previous Story Intelligence (1-7 Admin - Changement de Tier Manuel)
- **Fichiers modifiés** : main.py (PATCH /admin/users/{user_id}), services/auth_service.py (update_user_plan), database/repositories.py (updated_at UTC), tests/test_admin_tier_change.py.
- **Patterns** : Réponses erreur `{ error, message, details? }` sans champ `data`. Utiliser Depends(require_admin) pour les routes admin. Pour la story 1.8, pas de route admin ; uniquement logique côté POST /translate et DB.
- **DB** : User avec tier/plan ; pas de changement de schéma User pour 1.8. Table Translation déjà présente.
---
## Git Intelligence Summary
- Codebase : POST /translate dans main.py fait déjà increment_on_success + auth_update_user(daily_translation_count+1). Il manque la création de lentrée Translation (table `translations`) pour le billing/analytics. TranslationRepository.create() et update_status() existent ; il reste à les appeler au bon endroit avec les paramètres disponibles (file.filename, provider, target_language, source_language, file size depuis input_path ou file).
---
## Latest Tech Information
- **FastAPI** : Pour accéder à la DB dans un endpoint async, utiliser soit `Depends(get_db)` (session synchrone) puis asyncio.to_thread pour les appels repository, soit une session async si le projet est passé en SQLAlchemy async. Vérifier database/connection.py et comment get_db est utilisé dans main.py.
- **SQLAlchemy** : Translation model a déjà les champs nécessaires ; completed_at doit être renseigné manuellement si on crée en "completed" (datetime.now(timezone.utc)).
---
## Project Context Reference
- **Structure** : main.py contient POST /translate ; après la traduction (excel_translator.translate_file, etc.), le bloc actuel fait tier_quota increment et auth_update_user. Insérer la création de lenregistrement Translation dans ce même bloc (après succès, avec user_id, filename, size, provider, status completed). get_db : vérifier si disponible dans main (FastAPI Depends) pour obtenir une session et appeler TranslationRepository(session).create(...).
- **Auth** : current_user peut être None (traduction sans auth) ; dans ce cas ne pas créer dentrée Translation (ou créer avec user_id nullable si le schéma le permet — actuellement Translation.user_id est non nullable, donc ne créer que si current_user présent).
- **Périmètre** : Le translation log (AC2) sapplique à **POST /translate** uniquement. **POST /translate-batch** est hors scope pour cette story (pas dauth utilisateur ni de log par fichier dans limplémentation actuelle).
---
## Story Completion Status
- **Status** : done
- **Note** : Code review (adversarial) 2026-02-21 : correctifs HIGH/MEDIUM/LOW appliqués ; AC2 limité à POST /translate (batch hors scope).
## Change Log
- 2026-02-20: Story 1.8 created from epics + architecture + previous story 1-7.
- 2026-02-20: Story 1.8 implémentée : création entrée Translation après succès, get_sync_session, create_completed, tests unitaires et intégration.
- 2026-02-21: Code review (option 1 correctifs auto) : timezone UTC dans repositories, logger.exception pour échec log, tests unauthenticated + échec log, file_type String(20), double commit supprimé, périmètre batch documenté. Corrections compatibilité auth_service/repository (lazy import get_sync_session, create/update, _db_user_to_model) pour tests intégration.
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Debug Log References
### Completion Notes List
- Task 1 : Après succès de POST /translate, si current_user et DB (USE_DATABASE + DATABASE_AVAILABLE), création dun enregistrement Translation via TranslationRepository.create_completed (user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status=completed, completed_at). Taille fichier source via input_path.stat().st_size. Aucun contenu de fichier stocké (AC2, AC3, NFR11, NFR16).
- Task 2 : Vérifié : tier_quota_service.increment_on_success et auth_update_user(daily_translation_count+1) restent en place dans main.py. Sans DB (JSON only), pas dentrée Translation.
- Task 3 : Tests ajoutés dans tests/test_translation_log_1_8.py : test_create_completed_inserts_row_with_metadata_only, test_translation_model_has_no_content_columns, test_translate_creates_translation_log_when_authenticated_and_db (intégration, skip si requests absent).
- database/connection.py : ajout sync_engine et get_sync_session (context manager) pour utilisation par auth_service et translation log.
- database/repositories.py : ajout TranslationRepository.create_completed() pour créer directement en status "completed" avec completed_at.
### File List
- database/connection.py (sync_engine, get_sync_session)
- database/repositories.py (create_completed, completed_at timezone UTC dans update_status)
- database/models.py (Translation.file_type String(20))
- main.py (création translation log après succès si current_user + DB ; logger.exception si échec)
- tests/test_translation_log_1_8.py (nouveau ; tests unauthenticated + échec log ; fixture client_with_db moteur dédié)
- services/auth_service.py (lazy import get_sync_session/UserRepository ; create/update compat repo ; _db_user_to_model getattr champs optionnels — pour tests intégration 1.8)
- _bmad-output/implementation-artifacts/sprint-status.yaml (1-8 → done)
- _bmad-output/implementation-artifacts/1-8-tracking-usage-pour-billing.md (statut, tâches, change log, file list)
## Senior Developer Review (AI)
- **Date :** 2026-02-21
- **Résultat :** Approuvé après correctifs automatiques (option 1).
- **Problèmes traités :** H2 timezone UTC dans update_status ; H3 test sans auth → pas de log ; H4 statut story aligné ; H1 batch documenté hors scope ; M1 logger.exception ; M2/M3 tests ; L1 file_type String(20), L2 double commit, L3 importorskip supprimé. Corrections additionnelles pour faire passer les tests intégration : auth_service lazy import get_sync_session, repo.create/update et _db_user_to_model champs optionnels, fixture client_with_db avec moteur dédié.