# Projet Informatique - Documentation Complète ## 1. Contexte et Vision ### 1.1 Contexte organisationnel [Description de l'organisation, du département, de l'équipe] ### 1.2 Problématique métier [Analyse détaillée des problèmes business à résoudre] ### 1.3 Vision produit [Vision long-terme du produit et de son évolution] ### 1.4 Alignement stratégique [Comment le projet s'inscrit dans la stratégie de l'entreprise] ## 2. Analyse des besoins approfondie ### 2.1 Parties prenantes | Partie prenante | Rôle | Influence | Besoins | Contact | |-----------------|------|-----------|---------|---------| | Product Owner | | Élevée | | | | Tech Lead | | Élevée | | | | Utilisateurs finaux | | Moyenne | | | ### 2.2 User Stories détaillées ```gherkin Feature: Authentification utilisateur En tant qu'utilisateur Je veux pouvoir me connecter de manière sécurisée Afin d'accéder à mes données personnelles Scenario: Connexion réussie Given je suis sur la page de connexion When je saisis mes identifiants corrects Then je suis redirigé vers le dashboard And je vois mon nom d'utilisateur affiché Scenario: Connexion échouée Given je suis sur la page de connexion When je saisis des identifiants incorrects Then je vois un message d'erreur And je reste sur la page de connexion ``` ### 2.3 Acceptance Criteria détaillés [Critères d'acceptation pour chaque user story] ### 2.4 Contraintes détaillées #### Contraintes techniques - **Compatibilité** : IE11+, Chrome 80+, Firefox 75+, Safari 13+ - **Performance** : Temps de chargement < 2s, Core Web Vitals > 90 - **Accessibilité** : Conformité WCAG 2.1 AA - **SEO** : Structure sémantique, balises meta, sitemap #### Contraintes légales - **RGPD** : Consentement, droit à l'oubli, portabilité - **Sécurité** : ISO 27001, chiffrement bout-en-bout - **Audit** : Traçabilité des actions, journalisation ## 3. Architecture complète ### 3.1 Architecture métier [Domain-Driven Design, bounded contexts] ### 3.2 Architecture applicative ```mermaid graph TB A[Load Balancer] --> B[API Gateway] B --> C[Auth Service] B --> D[User Service] B --> E[Content Service] C --> F[(Auth DB)] D --> G[(User DB)] E --> H[(Content DB)] E --> I[File Storage] J[Frontend SPA] --> B K[Mobile App] --> B L[Admin Panel] --> B ``` ### 3.3 Architecture technique détaillée #### Microservices ```yaml services: auth-service: image: auth-service:latest environment: - JWT_SECRET=${JWT_SECRET} - DB_CONNECTION=${AUTH_DB_URL} dependencies: - postgres-auth healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 user-service: image: user-service:latest environment: - DB_CONNECTION=${USER_DB_URL} dependencies: - postgres-users healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3002/health"] ``` ### 3.4 Modèle de données complet ```sql -- Système de permissions CREATE TABLE roles ( id SERIAL PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE permissions ( id SERIAL PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, resource VARCHAR(100) NOT NULL, action VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE role_permissions ( role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE, PRIMARY KEY (role_id, permission_id) ); -- Audit trail CREATE TABLE audit_logs ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), action VARCHAR(100) NOT NULL, resource_type VARCHAR(100) NOT NULL, resource_id VARCHAR(100), old_values JSONB, new_values JSONB, ip_address INET, user_agent TEXT, created_at TIMESTAMP DEFAULT NOW() ); -- Système de notifications CREATE TABLE notifications ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, type VARCHAR(50) NOT NULL, title VARCHAR(255) NOT NULL, message TEXT, read_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); ``` ## 4. Conception détaillée par couche ### 4.1 Couche présentation #### Design System complet ```scss // Tokens de design $colors: ( primary: ( 50: #f0f9ff, 100: #e0f2fe, 500: #0ea5e9, 900: #0c4a6e ), semantic: ( success: #10b981, warning: #f59e0b, error: #ef4444, info: #3b82f6 ) ); $typography: ( h1: ( font-size: 2.25rem, line-height: 1.2, font-weight: 700 ), body: ( font-size: 1rem, line-height: 1.5, font-weight: 400 ) ); $spacing: ( xs: 0.25rem, sm: 0.5rem, md: 1rem, lg: 1.5rem, xl: 3rem ); ``` #### Composants React avancés ```typescript // Hook personnalisé pour la gestion d'état export const useApiData = (url: string, options?: RequestOptions) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await apiClient.get(url, options); setData(response.data); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error, refetch: fetchData }; }; // Composant de tableau réutilisable interface DataTableProps { data: T[]; columns: ColumnDef[]; loading?: boolean; onRowClick?: (row: T) => void; pagination?: PaginationOptions; } export function DataTable({ data, columns, loading = false, onRowClick, pagination }: DataTableProps) { // Implémentation avec react-table } ``` ### 4.2 Couche métier #### Domain Services ```typescript // Service métier pour la gestion des utilisateurs export class UserDomainService { constructor( private userRepository: IUserRepository, private emailService: IEmailService, private auditService: IAuditService ) {} async createUser(userData: CreateUserCommand): Promise { // Validation des règles métier await this.validateUserCreation(userData); // Création de l'utilisateur const user = await this.userRepository.create({ ...userData, status: UserStatus.PENDING_VERIFICATION, createdAt: new Date() }); // Envoi email de vérification await this.emailService.sendVerificationEmail(user.email); // Audit await this.auditService.log({ action: 'USER_CREATED', userId: user.id, details: { email: user.email } }); return user; } private async validateUserCreation(userData: CreateUserCommand): Promise { // Vérification unicité email const existingUser = await this.userRepository.findByEmail(userData.email); if (existingUser) { throw new DomainError('EMAIL_ALREADY_EXISTS', 'Cet email est déjà utilisé'); } // Validation format email if (!this.isValidEmail(userData.email)) { throw new DomainError('INVALID_EMAIL_FORMAT', 'Format email invalide'); } // Validation force mot de passe if (!this.isStrongPassword(userData.password)) { throw new DomainError('WEAK_PASSWORD', 'Le mot de passe n\'est pas assez fort'); } } } ``` ### 4.3 Couche données #### Repositories avec patterns avancés ```typescript // Pattern Repository avec Unit of Work export class UserRepository implements IUserRepository { constructor(private db: Database) {} async findById(id: string): Promise { const query = ` SELECT u.*, r.name as role_name FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE u.id = $1 AND u.deleted_at IS NULL `; const result = await this.db.query(query, [id]); return result.rows[0] ? this.mapToUser(result.rows[0]) : null; } async findWithPagination( filters: UserFilters, pagination: PaginationOptions ): Promise> { const whereClause = this.buildWhereClause(filters); const orderClause = this.buildOrderClause(pagination.sortBy, pagination.sortOrder); const [dataResult, countResult] = await Promise.all([ this.db.query(` SELECT u.*, r.name as role_name FROM users u LEFT JOIN roles r ON u.role_id = r.id ${whereClause} ${orderClause} LIMIT $1 OFFSET $2 `, [pagination.limit, pagination.offset]), this.db.query(` SELECT COUNT(*) as total FROM users u ${whereClause} `) ]); return { data: dataResult.rows.map(this.mapToUser), total: parseInt(countResult.rows[0].total), page: pagination.page, limit: pagination.limit }; } } ``` ## 5. Qualité et tests approfondis ### 5.1 Stratégie de tests complète ```typescript // Tests unitaires avec mocks describe('UserDomainService', () => { let userService: UserDomainService; let mockUserRepository: jest.Mocked; let mockEmailService: jest.Mocked; beforeEach(() => { mockUserRepository = { create: jest.fn(), findByEmail: jest.fn(), } as any; mockEmailService = { sendVerificationEmail: jest.fn(), } as any; userService = new UserDomainService( mockUserRepository, mockEmailService, mockAuditService ); }); describe('createUser', () => { it('should create user successfully', async () => { // Arrange const userData = { email: 'test@example.com', password: 'StrongPassword123!', firstName: 'John', lastName: 'Doe' }; mockUserRepository.findByEmail.mockResolvedValue(null); mockUserRepository.create.mockResolvedValue(mockUser); // Act const result = await userService.createUser(userData); // Assert expect(mockUserRepository.create).toHaveBeenCalledWith( expect.objectContaining({ email: userData.email, status: UserStatus.PENDING_VERIFICATION }) ); expect(mockEmailService.sendVerificationEmail).toHaveBeenCalledWith(userData.email); }); }); }); ``` ### 5.2 Tests d'intégration ```typescript // Tests d'API avec base de données de test describe('Users API Integration', () => { let app: Application; let testDb: TestDatabase; beforeAll(async () => { testDb = await TestDatabase.create(); app = createApp({ database: testDb }); }); afterAll(async () => { await testDb.cleanup(); }); describe('POST /api/users', () => { it('should create user and return 201', async () => { // Arrange const userData = { email: 'test@example.com', password: 'StrongPassword123!', firstName: 'John', lastName: 'Doe' }; // Act const response = await request(app) .post('/api/users') .send(userData) .expect(201); // Assert expect(response.body.data).toMatchObject({ email: userData.email, firstName: userData.firstName, lastName: userData.lastName, status: 'PENDING_VERIFICATION' }); // Vérifier en base const userInDb = await testDb.query( 'SELECT * FROM users WHERE email = $1', [userData.email] ); expect(userInDb.rows).toHaveLength(1); }); }); }); ``` ### 5.3 Tests E2E avec Cypress ```typescript // Tests end-to-end describe('User Management Flow', () => { beforeEach(() => { cy.resetDatabase(); cy.seedTestData(); }); it('should allow admin to create, edit and delete users', () => { // Login as admin cy.login('admin@example.com', 'password'); // Navigate to users page cy.visit('/admin/users'); cy.get('[data-testid="users-page"]').should('be.visible'); // Create new user cy.get('[data-testid="create-user-btn"]').click(); cy.get('[data-testid="user-form"]').should('be.visible'); cy.get('[data-testid="email-input"]').type('newuser@example.com'); cy.get('[data-testid="firstname-input"]').type('New'); cy.get('[data-testid="lastname-input"]').type('User'); cy.get('[data-testid="role-select"]').select('User'); cy.get('[data-testid="submit-btn"]').click(); // Verify user was created cy.get('[data-testid="success-message"]') .should('contain', 'Utilisateur créé avec succès'); cy.get('[data-testid="users-table"]') .should('contain', 'newuser@example.com'); }); }); ``` ## 6. Sécurité approfondie ### 6.1 Authentification et autorisation ```typescript // JWT avec refresh tokens export class AuthService { async authenticateUser(credentials: LoginCredentials): Promise { // Validation et authentification const user = await this.validateCredentials(credentials); // Génération des tokens const accessToken = this.generateAccessToken(user); const refreshToken = this.generateRefreshToken(user); // Stockage du refresh token await this.storeRefreshToken(user.id, refreshToken); // Audit de connexion await this.auditService.logAuth(user.id, 'LOGIN_SUCCESS'); return { user, accessToken, refreshToken, expiresIn: this.accessTokenTTL }; } async refreshAccessToken(refreshToken: string): Promise { // Validation du refresh token const tokenData = await this.validateRefreshToken(refreshToken); // Génération nouveau access token const newAccessToken = this.generateAccessToken(tokenData.user); return { accessToken: newAccessToken, expiresIn: this.accessTokenTTL }; } } // Middleware d'autorisation basé sur les rôles export const requirePermission = (resource: string, action: string) => { return async (req: Request, res: Response, next: NextFunction) => { const user = req.user; const hasPermission = await permissionService.userHasPermission( user.id, resource, action ); if (!hasPermission) { return res.status(403).json({ error: { code: 'INSUFFICIENT_PERMISSIONS', message: 'Permission insuffisante pour cette action' } }); } next(); }; }; ``` ### 6.2 Validation et sanitisation ```typescript // Schémas de validation avec Joi const userCreationSchema = Joi.object({ email: Joi.string() .email() .max(255) .required() .messages({ 'string.email': 'Format email invalide', 'string.max': 'Email trop long (max 255 caractères)', 'any.required': 'Email obligatoire' }), password: Joi.string() .min(8) .max(128) .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/) .required() .messages({ 'string.min': 'Mot de passe trop court (min 8 caractères)', 'string.pattern.base': 'Le mot de passe doit contenir au moins: 1 minuscule, 1 majuscule, 1 chiffre, 1 caractère spécial' }), firstName: Joi.string() .trim() .min(1) .max(100) .required(), lastName: Joi.string() .trim() .min(1) .max(100) .required() }); // Middleware de validation export const validateRequest = (schema: Joi.ObjectSchema) => { return (req: Request, res: Response, next: NextFunction) => { const { error, value } = schema.validate(req.body, { abortEarly: false, stripUnknown: true }); if (error) { return res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Données invalides', details: error.details.map(detail => ({ field: detail.path.join('.'), message: detail.message })) } }); } req.body = value; next(); }; }; ``` ## 7. Performance et monitoring ### 7.1 Optimisations performance ```typescript // Cache Redis pour améliorer les performances export class CachedUserService { constructor( private userService: UserDomainService, private cache: RedisClient ) {} async getUser(id: string): Promise { // Tentative de récupération depuis le cache const cacheKey = `user:${id}`; const cachedUser = await this.cache.get(cacheKey); if (cachedUser) { return JSON.parse(cachedUser); } // Récupération depuis la base de données const user = await this.userService.getUser(id); if (user) { // Mise en cache pour 1 heure await this.cache.setex(cacheKey, 3600, JSON.stringify(user)); } return user; } } // Pagination et filtrage optimisés export class UserQueryService { async searchUsers(filters: UserSearchFilters): Promise> { // Construction de la requête avec index appropriés const query = this.queryBuilder .select([ 'u.id', 'u.email', 'u.first_name', 'u.last_name', 'r.name as role_name' ]) .from('users', 'u') .leftJoin('roles', 'r', 'u.role_id = r.id') .where('u.deleted_at IS NULL'); // Application des filtres if (filters.email) { query.andWhere('u.email ILIKE :email', { email: `%${filters.email}%` }); } if (filters.role) { query.andWhere('r.name = :role', { role: filters.role }); } if (filters.status) { query.andWhere('u.status = :status', { status: filters.status }); } // Pagination const total = await query.getCount(); const users = await query .offset((filters.page - 1) * filters.limit) .limit(filters.limit) .orderBy('u.created_at', 'DESC') .getMany(); return { data: users, total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(total / filters.limit) }; } } ``` ### 7.2 Monitoring et observabilité ```typescript // Métriques custom avec Prometheus import { register, Counter, Histogram, Gauge } from 'prom-client'; export class MetricsService { private httpRequestDuration = new Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'], buckets: [0.1, 0.5, 1, 2, 5] }); private httpRequestsTotal = new Counter({ name: 'http_requests_total', help: 'Total number of HTTP requests', labelNames: ['method', 'route', 'status_code'] }); private activeConnections = new Gauge({ name: 'websocket_connections_active', help: 'Number of active WebSocket connections' }); recordHttpRequest(method: string, route: string, statusCode: number, duration: number) { this.httpRequestsTotal.inc({ method, route, status_code: statusCode }); this.httpRequestDuration.observe({ method, route, status_code: statusCode }, duration); } incrementActiveConnections() { this.activeConnections.inc(); } decrementActiveConnections() { this.activeConnections.dec(); } } // Middleware de monitoring export const monitoringMiddleware = (metricsService: MetricsService) => { return (req: Request, res: Response, next: NextFunction) => { const start = Date.now(); res.on('finish', () => { const duration = (Date.now() - start) / 1000; metricsService.recordHttpRequest( req.method, req.route?.path || req.path, res.statusCode, duration ); }); next(); }; }; ``` ## 8. DevOps et déploiement ### 8.1 Pipeline CI/CD complet ```yaml # .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:13 env: POSTGRES_PASSWORD: postgres POSTGRES_DB: test_db options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run type checking run: npm run type-check - name: Run unit tests run: npm run test:unit env: CI: true - name: Run integration tests run: npm run test:integration env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db - name: Run E2E tests run: npm run test:e2e env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db - name: Generate coverage report run: npm run test:coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run security audit run: npm audit --audit-level=high - name: Run SAST scan uses: securecodewarrior/github-action-add-sarif@v1 with: sarif-file: 'security-scan.sarif' build: needs: [test, security-scan] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build Docker images run: | docker build -t myapp/frontend:${{ github.sha }} ./frontend docker build -t myapp/backend:${{ github.sha }} ./backend - name: Push to registry run: | echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin docker push myapp/frontend:${{ github.sha }} docker push myapp/backend:${{ github.sha }} deploy-staging: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' steps: - name: Deploy to staging run: | # Déploiement sur l'environnement de staging kubectl set image deployment/frontend frontend=myapp/frontend:${{ github.sha }} kubectl set image deployment/backend backend=myapp/backend:${{ github.sha }} deploy-production: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: production steps: - name: Deploy to production run: | # Déploiement sur l'environnement de production kubectl set image deployment/frontend frontend=myapp/frontend:${{ github.sha }} kubectl set image deployment/backend backend=myapp/backend:${{ github.sha }} ``` ### 8.2 Configuration Kubernetes ```yaml # k8s/production/deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: backend namespace: production spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: myapp/backend:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: "production" - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url - name: JWT_SECRET valueFrom: secretKeyRef: name: app-secrets key: jwt-secret resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: backend-service namespace: production spec: selector: app: backend ports: - port: 80 targetPort: 3000 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: production annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/rate-limit: "100" spec: tls: - hosts: - api.myapp.com secretName: api-tls rules: - host: api.myapp.com http: paths: - path: / pathType: Prefix backend: service: name: backend-service port: number: 80 ``` ## 9. Évolution et maintenance ### 9.1 Roadmap technique #### Q1 2025 - [ ] Migration vers TypeScript 5.0 - [ ] Upgrade React 18 avec Concurrent Features - [ ] Implémentation GraphQL - [ ] Microservices avec Service Mesh #### Q2 2025 - [ ] Migration cloud-native (Kubernetes) - [ ] Intégration IA/ML pour recommandations - [ ] PWA avec support offline - [ ] Monitoring avancé avec OpenTelemetry #### Q3 2025 - [ ] Architecture event-driven avec Kafka - [ ] Support multi-tenant - [ ] API versioning complet - [ ] Edge computing avec CDN ### 9.2 Plan de migration ```typescript // Migration de données avec versioning export class MigrationService { async migrateToV2(): Promise { const batch = await this.getMigrationBatch(); for (const user of batch) { try { // Transformation des données const v2User = this.transformUserToV2(user); // Sauvegarde avec nouvelle structure await this.saveV2User(v2User); // Marquer comme migré await this.markAsMigrated(user.id); } catch (error) { await this.logMigrationError(user.id, error); } } } private transformUserToV2(v1User: V1User): V2User { return { id: v1User.id, profile: { email: v1User.email, firstName: v1User.firstName, lastName: v1User.lastName, avatar: v1User.avatar }, preferences: { language: v1User.language || 'fr', timezone: v1User.timezone || 'Europe/Paris', notifications: { email: v1User.emailNotifications ?? true, push: false } }, createdAt: v1User.createdAt, updatedAt: new Date() }; } } ``` ## 10. Documentation et formation ### 10.1 Documentation technique complète - [ ] Architecture Decision Records (ADR) - [ ] API Documentation (OpenAPI 3.0) - [ ] Code documentation (JSDoc/TSDoc) - [ ] Runbooks opérationnels - [ ] Guide de contribution ### 10.2 Documentation utilisateur - [ ] Guide d'onboarding - [ ] Manuel utilisateur complet - [ ] FAQ interactive - [ ] Tutoriels vidéo - [ ] Base de connaissances ### 10.3 Formation équipe - [ ] Sessions de formation technique - [ ] Code reviews guidelines - [ ] Best practices documentation - [ ] Mentoring plan - [ ] Certification interne ## 11. Mesures de succès ### 11.1 KPIs techniques - **Performance** : P95 response time < 500ms - **Disponibilité** : SLA 99.9% - **Qualité** : Code coverage > 90% - **Sécurité** : 0 vulnérabilité critique ### 11.2 KPIs business - **Adoption** : 80% des utilisateurs actifs mensuels - **Satisfaction** : NPS > 50 - **Support** : < 2% de tickets critiques - **ROI** : Retour sur investissement en 18 mois ## 12. Journal détaillé du projet ### [Date] - Kickoff projet [Notes sur le lancement, équipe, planning] ### [Date] - Architecture review [Décisions architecturales, trade-offs, consensus équipe] ### [Date] - Milestone 1 [Réalisations, défis, ajustements nécessaires] ### [Date] - Production deployment [Métriques de lancement, feedback utilisateurs, actions correctives] ## 13. Annexes techniques ### Annexe A : Schémas de base de données complets ### Annexe B : Diagrammes d'architecture ### Annexe C : Spécifications d'API détaillées ### Annexe D : Guide de déploiement ### Annexe E : Procédures de récupération après incident