๐Ÿ—๏ธ LEONI Quality Management System

Software Engineering Design Documentation & Architecture

๐ŸŽฏ System Overview & Requirements

๐Ÿ“‹ Business Requirements Analysis

The LEONI Quality Management System (QMS) is designed to streamline quality control processes in automotive wire harness manufacturing. The system addresses critical business needs:

๐Ÿ” Defect Management

Real-time defect tracking and reporting with automated severity assessment and escalation workflows.

๐Ÿ“Š QRQC Analysis

Quick Response Quality Control with structured root cause analysis, corrective actions, and verification tracking.

๐Ÿ” Audit Systems

5S and AFP audit management with automated scoring, action plans, and progress tracking.

๐Ÿ“ˆ KPI Monitoring

Real-time production quality metrics with dashboard visualization and trend analysis.

๐ŸŽจ Design Philosophy

User-Driven Architecture

The system is designed around actual user workflows rather than abstract service layers. AQL agents directly record defects, quality technicians perform QRQC analysis, and team leaders track implementation - no unnecessary intermediary services.

Hybrid Data Strategy

Critical queryable data is stored in structured columns for performance, while evolving form data uses JSONB for flexibility. This approach eliminates the need for schema migrations when forms change.

Scalable JSONB Architecture

The Analysis table uses a consolidated approach with only 7 columns: 4 structured columns (id, analysis_date, defect_id, production_line_id, inspector_id), 1 comprehensive JSONB column (qrqc_form) containing all 10 form sections as nested data, and 2 system columns (is_archived, created_at). This consolidation enables perfect archival where each analysis record contains complete form data as a single atomic unit.

๐Ÿ“Š System Metrics

15

Database Tables

4

User Roles

13

JSONB Sections

30+

API Endpoints

๐Ÿ›๏ธ System Architecture

๐Ÿ—๏ธ High-Level Architecture

graph TB subgraph "๐Ÿ–ฅ๏ธ Presentation Layer" A[Web Frontend - HTML/CSS/JS] B[๐Ÿ“Š Dashboard Components] C[๐Ÿ“ Forms & UI Components] D[๐Ÿ“ฑ Responsive Design] end subgraph "๐Ÿง  Business Logic Layer" E[๐Ÿ” Authentication Service] F[๐Ÿ“‹ QRQC Analysis Engine] G[๐Ÿ” Audit Management] H[๐Ÿ“Š KPI Calculator] I[๐Ÿ“ง Notification System] J[๐Ÿ“ File Management] end subgraph "๐Ÿ”Œ Data Access Layer" K[๐ŸŽฎ Database Controllers] L[๐ŸŒ API Endpoints] M[๐Ÿ” Query Optimization] N[๐Ÿ’พ Caching Layer] end subgraph "๐Ÿ—„๏ธ Data Storage Layer" O[(PostgreSQL - Primary)] P[(Redis - Cache)] Q[๐Ÿ“ File Storage] R[๐Ÿ“Š JSONB Indexing] end A --> E B --> H C --> F D --> G E --> K F --> K G --> K H --> K I --> L J --> M K --> O L --> O M --> P N --> P K --> R

๐Ÿข Layered Architecture

๐Ÿ–ฅ๏ธ Presentation Layer

Technologies: HTML5, CSS3, JavaScript ES6+, Responsive Design

Responsibility: User interface, form validation, dashboard visualization, user experience

๐Ÿง  Business Logic Layer

Technologies: JavaScript/Node.js, Express.js, Business Rules Engine

Responsibility: Application logic, workflow management, calculations, validations

๐Ÿ”Œ Data Access Layer

Technologies: RESTful APIs, ORM/Query Builder, Connection Pooling

Responsibility: Data operations, API endpoints, query optimization, caching

๐Ÿ—„๏ธ Data Storage Layer

Technologies: PostgreSQL, Redis, File System, JSONB, GIN Indexes

Responsibility: Data persistence, indexing, backup, archival

๐Ÿ”„ Process Flow Architecture

sequenceDiagram participant AQL as ๐Ÿ‘ค AQL Agent participant QT as ๐Ÿ‘ค Quality Technician participant System as ๐Ÿ–ฅ๏ธ QMS System participant DB as ๐Ÿ—„๏ธ Database participant TL as ๐Ÿ‘ค Team Leader Note over AQL,TL: User-Driven QRQC Workflow AQL->>System: ๐Ÿ“ Report Defect System->>System: โœ… Validate Data System->>DB: ๐Ÿ’พ Store Defect System->>QT: ๐Ÿ“ง Notify Critical Defect QT->>System: ๐Ÿ” Create QRQC Analysis System->>System: ๐Ÿงฎ Calculate Tri Totals System->>System: ๐Ÿค– Auto-decide Retouche System->>DB: ๐Ÿ’พ Store Analysis (Consolidated qrqc_form) QT->>System: ๐Ÿ“Š Complete Analysis Sections System->>DB: ๐Ÿ”„ Update Consolidated Form (jsonb_set operations) System->>TL: ๐Ÿ“ง Notify for Review TL->>System: โœ… Review & Approve System->>DB: ๐Ÿ”’ Close Analysis (Archive with complete form data)

๐Ÿ—„๏ธ Database Design Strategy

๐Ÿ“Š Entity Relationship Design

erDiagram USERS { int id PK string username string email string role boolean is_active datetime created_at } ANALYSIS { int id PK date analysis_date int defect_id FK int production_line_id FK int inspector_id FK jsonb qrqc_form boolean is_archived datetime created_at } DEFECTS { int id PK int production_line_id FK int project_id FK string description string status datetime created_at } PRODUCTION_LINES { int id PK string name string code boolean is_active } AUDIT_5S { int id PK int line_id FK int auditor_id FK date audit_date jsonb audit_data int total_score } KPI { int id PK int line_id FK date metric_date decimal fpy decimal defect_rate } VALUES { int id PK int production_line_id FK int entered_by FK string shift date date jsonb quality_data text notes string status datetime created_at datetime updated_at } USERS ||--o{ ANALYSIS : creates DEFECTS ||--o{ ANALYSIS : analyzes PRODUCTION_LINES ||--o{ DEFECTS : occurs_on PRODUCTION_LINES ||--o{ ANALYSIS : performed_on USERS ||--o{ AUDIT_5S : conducts PRODUCTION_LINES ||--o{ KPI : measures PRODUCTION_LINES ||--o{ VALUES : measures_quality USERS ||--o{ VALUES : enters

๐ŸŽฏ Hybrid Storage Strategy

Structured Columns for Performance

-- Key queryable fields as structured columns analysis_date DATE NOT NULL, defect_id INTEGER REFERENCES defects(id), production_line_id INTEGER REFERENCES production_lines(id), inspector_id INTEGER REFERENCES users(id)

Consolidated QRQC Form Structure

-- Single consolidated JSONB column for all form data qrqc_form JSONB NOT NULL DEFAULT '{}' CHECK ( jsonb_typeof(qrqc_form) = 'object' ), -- Contains all 10 sections as nested structure: -- document_info, caracterisation, personnel_notification, -- tri_data, pre_analyse, actions_securisation, -- analysis_data, corrective_actions, verification_tracking, metadata is_archived BOOLEAN DEFAULT FALSE

VALUES Table - Quality Metrics JSONB Structure

-- Quality metrics with flexible JSONB storage quality_data JSONB NOT NULL DEFAULT '{ "scrap": { "total_scrap": 0, "total_production": 0, "rate": 0.0 }, "rework": { "total_rework": 0, "total_production": 0, "rate": 0.0 }, "defects": { "total_defects": 0, "total_production": 0, "rate_ppm": 0.0 } }' -- Enables scalable quality metrics entry with automatic calculations -- AQL agents enter raw data, system calculates rates via triggers

๐Ÿš€ Performance Optimization

๐Ÿ“‡ Consolidated GIN Indexing

Comprehensive GIN indexes on JSONB columns for both ANALYSIS qrqc_form and VALUES quality_data, plus specific path indexes for frequently accessed form sections and quality metrics.

๐ŸŽฏ Optimized Path Indexes

Targeted JSONB path indexes for common query patterns like metadata status, TRI decisions, and actions tracking.

โšก Atomic Operations

Consolidated structure enables atomic QRQC form updates and perfect archival with complete form reconstruction from single column.

๐Ÿ’พ Connection Pooling

Database connection pooling for optimal resource utilization and scalability.

-- Consolidated indexing strategy for optimized performance -- ANALYSIS table indexes CREATE INDEX idx_analysis_date ON analysis(analysis_date); CREATE INDEX idx_qrqc_form_gin ON analysis USING GIN (qrqc_form); CREATE INDEX idx_qrqc_status ON analysis USING GIN ((qrqc_form->'metadata'->>'status')); CREATE INDEX idx_qrqc_tri_decision ON analysis USING GIN ((qrqc_form->'tri_data'->>'decisionRetouche')); CREATE INDEX idx_qrqc_actions_status ON analysis USING GIN ((qrqc_form->'actions_securisation'->>'overall_status')); -- VALUES table JSONB indexes CREATE INDEX idx_values_quality_data ON values USING GIN (quality_data); CREATE INDEX idx_values_scrap_rate ON values USING BTREE ((quality_data->'scrap'->'rate')::DECIMAL); CREATE INDEX idx_values_rework_rate ON values USING BTREE ((quality_data->'rework'->'rate')::DECIMAL); CREATE INDEX idx_values_defect_rate_ppm ON values USING BTREE ((quality_data->'defects'->'rate_ppm')::DECIMAL);

๐Ÿ”Œ API Design & Architecture

๐ŸŒ RESTful API Design

graph TB subgraph "๐Ÿ” Authentication" A1[POST /api/auth/login] A2[POST /api/auth/logout] A3[GET /api/auth/profile] end subgraph "๐Ÿ”ง Defect Management" B1[GET /api/defects] B2[POST /api/defects] B3[PUT /api/defects/:id] B4[DELETE /api/defects/:id] end subgraph "๐Ÿ“‹ QRQC Analysis" C1[GET /api/analysis] C2[POST /api/analysis] C3[PUT /api/analysis/:id/section] C4[GET /api/analysis/:id/report] end subgraph "๐Ÿ” Audit Systems" D1[GET /api/audits/5s] D2[POST /api/audits/5s] D3[GET /api/audits/afp] D4[POST /api/audits/afp] end subgraph "๐Ÿ“Š KPI & Reports" E1[GET /api/kpi/dashboard] E2[GET /api/reports/export] E3[GET /api/analytics/trends] end subgraph "๐Ÿ“Š Quality Metrics" F1[GET /api/values] F2[POST /api/values/quality-metrics] F3[PUT /api/values/:id] F4[GET /api/values/production-line/:id/current] F5[GET /api/values/trends] end

๐Ÿ“ Consolidated QRQC Form Updates

// QRQC Analysis - Update Consolidated Form Section PUT /api/analysis/:id/form { "section": "tri_data", "data": { "magasinProduitFini": {"bon": 15, "mauvais": 2}, "paletteFinLigne": {"bon": 12, "mauvais": 5}, "totals": {"totalBon": 27, "totalMauvais": 7}, "decisionRetouche": false } } // Updates: UPDATE analysis SET qrqc_form = jsonb_set(qrqc_form, '{tri_data}', $1) // QRQC Analysis - Update Pre-Analyse Section PUT /api/analysis/:id/form { "section": "pre_analyse", "data": { "operatorId": "OP123", "postMachine": "Station 5 - Assembly Line", "defectDate": "2025-06-18", "defectHour": "14:30" } } // QRQC Analysis - Complete Form Retrieval (Perfect for Archives) GET /api/analysis/:id Response: { "id": 123, "analysis_date": "2025-06-18", "defect_id": 456, "production_line_id": 1, "inspector_id": 789, "qrqc_form": { "document_info": { "qrqc_reference_id": "DEF-1703123456789" }, "caracterisation": { "defect_description": "Detailed description" }, "personnel_notification": { "informed_personnel": ["operateur", "team-speaker"] }, "tri_data": { "totals": {"totalBon": 27, "totalMauvais": 7} }, "pre_analyse": { "operatorId": "OP123" }, "actions_securisation": { "action1": {"responsable": "Quality Supervisor"} }, "analysis_data": { "why1": {"field1": "Root cause analysis"} }, "corrective_actions": { "occurrence": {"pilote": "Production Manager"} }, "verification_tracking": { "teams": [{"team": "Team 1", "occurrence": "No"}] }, "metadata": { "status": "OPEN", "priority": "MEDIUM" } }, "is_archived": false, "created_at": "2025-06-18T10:30:00Z" } "responsable": "Maintenance Team", "date": "2025-06-25", "status": "PLANNED" } } } // Response { "success": true, "message": "Section updated successfully", "analysisId": 123, "section": "actions_securisation", "autoCalculated": { "overall_status": "PLANNED", "next_due_date": "2025-06-20" } }

๐Ÿ“Š Quality Metrics API (VALUES Table)

// Quality Metrics Entry - JSONB Structure POST /api/values/quality-metrics { "production_line_id": 1, "shift": "Morning", "date": "2025-06-18", "quality_data": { "scrap": { "total_scrap": 15, "total_production": 1000 }, "rework": { "total_rework": 8, "total_production": 1000 }, "defects": { "total_defects": 3, "total_production": 1000 } }, "notes": "Morning shift quality metrics" } // Database Operation: INSERT INTO values with automatic rate calculation // Trigger calculates: scrap_rate=1.5%, rework_rate=0.8%, defect_rate_ppm=3000 // Get Current Quality Metrics for Production Line GET /api/values/production-line/1/current Response: { "success": true, "data": { "id": 123, "production_line_id": 1, "shift": "Morning", "date": "2025-06-18", "quality_data": { "scrap": {"total_scrap": 15, "total_production": 1000, "rate": 1.5}, "rework": {"total_rework": 8, "total_production": 1000, "rate": 0.8}, "defects": {"total_defects": 3, "total_production": 1000, "rate_ppm": 3000.0} }, "notes": "Morning shift quality metrics" } } // Quality Trends Analysis GET /api/values/trends?production_line_id=1&days=7 Response: { "success": true, "data": { "scrap_trend": [1.2, 1.5, 1.3, 1.8, 1.4, 1.6, 1.5], "rework_trend": [0.8, 0.9, 0.7, 1.1, 0.8, 0.9, 0.8], "defect_ppm_trend": [2500, 3000, 2800, 3200, 2900, 3100, 3000], "trend_analysis": { "scrap_direction": "stable", "rework_direction": "stable", "defects_direction": "increasing" } } }

๐Ÿ”„ API Response Strategy

๐Ÿ“Š Consistent Response Format

Standardized JSON response structure with success/error states, data payload, and metadata.

๐Ÿ”’ Security Headers

CORS, CSRF protection, rate limiting, and authentication tokens for secure API access.

๐Ÿ“ˆ Performance Monitoring

API response time monitoring, error tracking, and usage analytics for optimization.

๐Ÿ“ Documentation

Comprehensive API documentation with examples, schemas, and interactive testing.

๐Ÿงฉ Design Patterns & Best Practices

๐Ÿ—๏ธ Architectural Patterns

๐Ÿ“ฆ Repository Pattern

class AnalysisRepository { async create(analysisData) { const query = ` INSERT INTO analysis ( analysis_date, defect_id, production_line_id, inspector_id, qrqc_form, is_archived ) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `; return await this.db.query(query, [ analysisData.date, analysisData.defectId, analysisData.productionLineId, analysisData.inspectorId, JSON.stringify(analysisData.qrqcForm), // Consolidated form data false ]); } async updateFormSection(id, section, data) { const query = ` UPDATE analysis SET qrqc_form = jsonb_set(qrqc_form, $2, $3) WHERE id = $1 RETURNING * `; return await this.db.query(query, [ id, `{${section}}`, JSON.stringify(data) ]); } async getCompleteForm(id) { const query = ` SELECT id, analysis_date, defect_id, production_line_id, inspector_id, qrqc_form, is_archived, created_at FROM analysis WHERE id = $1 `; return await this.db.query(query, [id]); } }

๐Ÿญ Factory Pattern for Validation

class QRQCValidatorFactory { static createValidator(section) { const validators = { 'document_info': DocumentInfoValidator, 'caracterisation': CaracterisationValidator, 'tri_data': TriDataValidator, 'analysis_data': AnalysisDataValidator, 'corrective_actions': CorrectiveActionValidator, 'verification_tracking': VerificationValidator, 'metadata': MetadataValidator }; const ValidatorClass = validators[section]; if (!ValidatorClass) { throw new Error(`Unknown section: ${section}`); } return new ValidatorClass(); } } class TriDataValidator { validate(data) { const required = ['magasinProduitFini', 'paletteFinLigne', 'ligneAssemblage']; const errors = []; for (const stage of required) { if (!data[stage] || typeof data[stage] !== 'object') { errors.push(`Missing or invalid ${stage} data`); } } return { isValid: errors.length === 0, errors }; } }

๐Ÿ”„ Data Flow Patterns

graph LR A[๐Ÿ“ QRQC Form] --> B[โœ… Validation Layer] B --> C[๐Ÿง  Business Logic] C --> D[๐Ÿ”„ Data Transform] D --> E[๐Ÿ—„๏ธ Database Layer] E --> F[(PostgreSQL)] C --> G[๐Ÿ“Š JSONB Processor] G --> H[๐Ÿ” GIN Indexing] H --> F F --> I[โšก Query Optimizer] I --> J[๐ŸŒ Dashboard APIs] J --> K[๐Ÿ“ฑ Frontend Views] style A fill:#e1f5fe style F fill:#f3e5f5 style K fill:#e8f5e8

๐Ÿ›ก๏ธ Error Handling Patterns

class ErrorHandler { static async handleDatabaseError(error, context) { const errorMap = { '23505': 'Duplicate entry detected', '23503': 'Referenced record not found', '23514': 'Data validation failed', '42P01': 'Table does not exist' }; const message = errorMap[error.code] || 'Database operation failed'; logger.error('Database Error', { code: error.code, message: error.message, context: context, timestamp: new Date().toISOString() }); return { success: false, error: message, code: error.code, timestamp: new Date().toISOString() }; } }

โšก Performance Optimization Strategy

๐Ÿ“Š Database Performance

๐Ÿ” Indexing Strategy

Strategic GIN indexes on JSONB columns, B-tree indexes on structured columns, and composite indexes for complex queries.

๐Ÿ’พ Query Optimization

Optimized SQL queries with proper JOINs, WHERE clauses, and JSONB operators for fast data retrieval.

๐Ÿ”„ Connection Pooling

Database connection pooling to handle concurrent users efficiently and reduce connection overhead.

๐Ÿ“ˆ Monitoring

Query performance monitoring, slow query analysis, and automatic index recommendations.

๐Ÿ’พ Caching Architecture

graph TB A[๐ŸŒ API Request] --> B{๐Ÿ’พ Cache Check} B -->|Hit| C[๐Ÿ“ฆ Return Cached Data] B -->|Miss| D[๐Ÿ—„๏ธ Database Query] D --> E[๐Ÿ’พ Store in Cache] E --> F[๐Ÿ“ฆ Return Fresh Data] subgraph "๐Ÿ”„ Cache Strategy" G[๐Ÿ“Š KPI Data - 5min TTL] H[๐Ÿ‘ฅ User Permissions - 1hr TTL] I[๐Ÿ“‹ Production Lines - 24hr TTL] J[๐Ÿ“ˆ Dashboard Data - 2min TTL] end style C fill:#e8f5e8 style F fill:#e1f5fe
class CacheService { constructor() { this.redis = new Redis(process.env.REDIS_URL); this.defaultTTL = 300; // 5 minutes } async get(key) { try { const cached = await this.redis.get(key); return cached ? JSON.parse(cached) : null; } catch (error) { console.error('Cache get error:', error); return null; } } async set(key, data, ttl = this.defaultTTL) { try { await this.redis.setex(key, ttl, JSON.stringify(data)); } catch (error) { console.error('Cache set error:', error); } } async getProductionLines() { const cacheKey = 'production_lines_active'; let lines = await this.get(cacheKey); if (!lines) { lines = await db.query( 'SELECT * FROM production_lines WHERE is_active = true' ); await this.set(cacheKey, lines, 86400); // 24 hours } return lines; } }

๐Ÿ“Š Performance Metrics

< 100ms

API Response Time

< 50ms

Database Queries

95%

Cache Hit Rate

1000+

Concurrent Users

๐Ÿ”’ Security Architecture

๐Ÿ›ก๏ธ Role-Based Access Control (RBAC)

graph TB subgraph "๐Ÿ‘ฅ User Roles" A[๐Ÿ‘ค AQL Agent] B[๐Ÿ”ง Quality Technician] C[๐Ÿ‘จโ€๐Ÿ’ผ Team Leader] D[๐Ÿ‘ฉโ€๐Ÿ’ผ Quality Manager] end subgraph "๐Ÿ” Permissions" E[๐Ÿ“ Can Add Defects] F[๐Ÿ” Can View QRQC] G[๐Ÿ“‹ Can Create QRQC] H[โœ๏ธ Can Edit QRQC] I[๐Ÿ” Can Perform Audits] J[๐Ÿ‘ฅ Can Manage Staff] K[โš™๏ธ Can System Admin] end A --> E A --> F B --> E B --> F B --> G B --> H B --> I C --> E C --> F C --> G C --> H C --> I C --> J D --> E D --> F D --> G D --> H D --> I D --> J D --> K

๐Ÿ” Authentication & Authorization

-- Automated permission management CREATE OR REPLACE FUNCTION update_user_permissions() RETURNS TRIGGER AS $$ BEGIN DELETE FROM user_permissions WHERE user_id = NEW.id; CASE NEW.role WHEN 'AQL_AGENT' THEN INSERT INTO user_permissions (user_id, can_add_defect, can_view_qrqc) VALUES (NEW.id, TRUE, TRUE); WHEN 'QUALITY_TECHNICIAN' THEN INSERT INTO user_permissions (user_id, can_add_defect, can_view_qrqc, can_add_qrqc, can_edit_qrqc, can_perform_5s_audit) VALUES (NEW.id, TRUE, TRUE, TRUE, TRUE, TRUE); WHEN 'QUALITY_MANAGER' THEN -- Full permissions for quality manager INSERT INTO user_permissions (user_id, can_system_admin, can_user_management) VALUES (NEW.id, TRUE, TRUE); END CASE; RETURN NEW; END; $$ LANGUAGE plpgsql;

๐Ÿ›ก๏ธ Security Measures

๐Ÿ” Data Encryption

Password hashing with bcrypt, HTTPS/TLS encryption, and secure session management with signed tokens.

๐Ÿšซ Input Validation

Server-side validation, SQL injection prevention, XSS protection, and CSRF token validation.

๐Ÿ“ Audit Trail

Comprehensive logging of user actions, data changes, and system events for security monitoring.

โฐ Session Management

Secure session tokens, automatic session expiry, and concurrent session monitoring.

๐Ÿš€ Deployment & Infrastructure

๐Ÿ—๏ธ Deployment Architecture

graph TB subgraph "๐ŸŒ Load Balancer" A[Nginx/HAProxy] end subgraph "๐Ÿ–ฅ๏ธ Application Servers" B1[App Server 1] B2[App Server 2] B3[App Server N] end subgraph "๐Ÿ—„๏ธ Database Cluster" C1[(PostgreSQL Primary)] C2[(PostgreSQL Replica)] end subgraph "๐Ÿ’พ Cache Layer" D1[Redis Primary] D2[Redis Replica] end subgraph "๐Ÿ“ Storage" E[File Storage] F[Backup Storage] end A --> B1 A --> B2 A --> B3 B1 --> C1 B2 --> C1 B3 --> C1 C1 --> C2 B1 --> D1 B2 --> D1 B3 --> D1 D1 --> D2 B1 --> E B2 --> E B3 --> E C1 --> F

๐Ÿ”„ CI/CD Pipeline

๐Ÿ”ง

Development

Git, VS Code, Local PostgreSQL, Hot Reload

๐Ÿงช

Testing

Jest, Supertest, Database Fixtures, Automated Testing

๐Ÿ—๏ธ

Build

GitHub Actions, Docker, Asset Optimization

๐Ÿš€

Deploy

Docker Compose, Environment Config, Health Checks

๐Ÿ“Š Monitoring & Observability

// Application Health Check app.get('/health', async (req, res) => { const health = { status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: process.env.NODE_ENV, version: process.env.APP_VERSION, checks: { database: await checkDatabase(), redis: await checkRedis(), diskSpace: await checkDiskSpace(), memory: process.memoryUsage() } }; const hasFailures = Object.values(health.checks) .some(check => check.status === 'ERROR'); res.status(hasFailures ? 503 : 200).json(health); }); // Performance Monitoring const performanceMonitor = { trackApiResponse: (req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; console.log(`${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`); // Send metrics to monitoring service metrics.timing('api.response_time', duration, { method: req.method, path: req.path, status: res.statusCode }); }); next(); } };