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

13 KiB
Raw Blame History

Story 1.8: Tracking Usage pour Billing

Status: done

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

  • Task 1 : Créer l'entrée Translation après succès (AC: 2, 3)
    • 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.
    • 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.
    • 1.3 S'assurer que le provider utilisé (google, deepl, ollama, openai, openrouter, libre) est bien enregistré (variable provider dans l'endpoint).
  • Task 2 : Synchronisation daily_translation_count (AC: 1)
    • 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).
    • 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.
  • Task 3 : Tests (AC: 13)
    • 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.
    • 3.2 Test : aucun champ ne contient de contenu de document (vérifier schéma et politiques de log).
    • 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é.