From 0dc12f4b93e5aa414d01d8081db7b903cf8da344 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Fri, 25 Jul 2025 13:31:54 +0200 Subject: [PATCH] Start generating pytest for training service --- services/training/requirements.txt | 2 + services/training/tests/README.md | 263 - services/training/tests/conftest.py | 1851 ++++- .../test_data/madrid_traffic_sample.json | 7022 +++++++++++++++++ .../test_data/madrid_weather_sample.json | 1802 +++++ .../tests/results/coverage_end_to_end.xml | 1670 ++++ .../tests/results/coverage_integration.xml | 1670 ++++ .../tests/results/coverage_performance.xml | 1670 ++++ .../training/tests/results/coverage_unit.xml | 1670 ++++ .../tests/results/junit_end_to_end.xml | 5 + .../tests/results/junit_integration.xml | 1 + .../tests/results/junit_performance.xml | 8 + .../training/tests/results/junit_unit.xml | 649 ++ .../training/tests/results/test_report.json | 53 + services/training/tests/run_tests.py | 673 ++ services/training/tests/test_end_to_end.py | 311 + .../tests/test_ml_pipeline_integration.py | 0 services/training/tests/test_performance.py | 630 ++ 18 files changed, 19378 insertions(+), 572 deletions(-) delete mode 100644 services/training/tests/README.md create mode 100644 services/training/tests/fixtures/test_data/madrid_traffic_sample.json create mode 100644 services/training/tests/fixtures/test_data/madrid_weather_sample.json create mode 100644 services/training/tests/results/coverage_end_to_end.xml create mode 100644 services/training/tests/results/coverage_integration.xml create mode 100644 services/training/tests/results/coverage_performance.xml create mode 100644 services/training/tests/results/coverage_unit.xml create mode 100644 services/training/tests/results/junit_end_to_end.xml create mode 100644 services/training/tests/results/junit_integration.xml create mode 100644 services/training/tests/results/junit_performance.xml create mode 100644 services/training/tests/results/junit_unit.xml create mode 100644 services/training/tests/results/test_report.json create mode 100644 services/training/tests/run_tests.py create mode 100644 services/training/tests/test_end_to_end.py create mode 100644 services/training/tests/test_ml_pipeline_integration.py create mode 100644 services/training/tests/test_performance.py diff --git a/services/training/requirements.txt b/services/training/requirements.txt index 5ba5f5ff..11a4d17a 100644 --- a/services/training/requirements.txt +++ b/services/training/requirements.txt @@ -41,6 +41,8 @@ pytest==7.4.3 pytest-asyncio==0.21.1 pytest-mock==3.12.0 httpx==0.25.2 +pytest-cov==4.1.0 +coverage==7.3.2 # Utilities python-dateutil==2.8.2 diff --git a/services/training/tests/README.md b/services/training/tests/README.md deleted file mode 100644 index 67f008fd..00000000 --- a/services/training/tests/README.md +++ /dev/null @@ -1,263 +0,0 @@ -# Training Service - Complete Testing Suite - -## πŸ“ Test Structure - -``` -services/training/tests/ -β”œβ”€β”€ conftest.py # Test configuration and fixtures -β”œβ”€β”€ test_api.py # API endpoint tests -β”œβ”€β”€ test_ml.py # ML component tests -β”œβ”€β”€ test_service.py # Service layer tests -β”œβ”€β”€ test_messaging.py # Messaging tests -└── test_integration.py # Integration tests -``` - -## πŸ§ͺ Test Coverage - -### **1. API Tests (`test_api.py`)** -- βœ… Health check endpoints (`/health`, `/health/ready`, `/health/live`) -- βœ… Metrics endpoint (`/metrics`) -- βœ… Training job creation and management -- βœ… Single product training -- βœ… Job status tracking and cancellation -- βœ… Data validation endpoints -- βœ… Error handling and edge cases -- βœ… Authentication integration - -**Key Test Classes:** -- `TestTrainingAPI` - Basic API functionality -- `TestTrainingJobsAPI` - Training job management -- `TestSingleProductTrainingAPI` - Single product workflows -- `TestErrorHandling` - Error scenarios -- `TestAuthenticationIntegration` - Security tests - -### **2. ML Component Tests (`test_ml.py`)** -- βœ… Data processor functionality -- βœ… Prophet manager operations -- βœ… ML trainer orchestration -- βœ… Feature engineering validation -- βœ… Model training and validation - -**Key Test Classes:** -- `TestBakeryDataProcessor` - Data preparation and feature engineering -- `TestBakeryProphetManager` - Prophet model management -- `TestBakeryMLTrainer` - ML training orchestration -- `TestIntegrationML` - ML component integration - -**Key Features Tested:** -- Spanish holiday detection -- Temporal feature engineering -- Weather and traffic data integration -- Model validation and metrics -- Data quality checks - -### **3. Service Layer Tests (`test_service.py`)** -- βœ… Training service business logic -- βœ… Database operations -- βœ… External service integration -- βœ… Job lifecycle management -- βœ… Error recovery and resilience - -**Key Test Classes:** -- `TestTrainingService` - Core business logic -- `TestTrainingServiceDataFetching` - External API integration -- `TestTrainingServiceExecution` - Training workflow execution -- `TestTrainingServiceEdgeCases` - Edge cases and error conditions - -### **4. Messaging Tests (`test_messaging.py`)** -- βœ… Event publishing functionality -- βœ… Message structure validation -- βœ… Error handling in messaging -- βœ… Integration with shared components - -**Key Test Classes:** -- `TestTrainingMessaging` - Basic messaging operations -- `TestMessagingErrorHandling` - Error scenarios -- `TestMessagingIntegration` - Shared component integration -- `TestMessagingPerformance` - Performance and reliability - -### **5. Integration Tests (`test_integration.py`)** -- βœ… End-to-end workflow testing -- βœ… Service interaction validation -- βœ… Error handling across boundaries -- βœ… Performance and scalability -- βœ… Security and compliance - -**Key Test Classes:** -- `TestTrainingWorkflowIntegration` - Complete workflows -- `TestServiceInteractionIntegration` - Cross-service communication -- `TestErrorHandlingIntegration` - Error propagation -- `TestPerformanceIntegration` - Performance characteristics -- `TestSecurityIntegration` - Security validation -- `TestRecoveryIntegration` - Recovery scenarios -- `TestComplianceIntegration` - GDPR and audit compliance - -## πŸ”§ Test Configuration (`conftest.py`) - -### **Fixtures Provided:** -- `test_engine` - Test database engine -- `test_db_session` - Database session for tests -- `test_client` - HTTP test client -- `mock_messaging` - Mocked messaging system -- `mock_data_service` - Mocked external data services -- `mock_ml_trainer` - Mocked ML trainer -- `mock_prophet_manager` - Mocked Prophet manager -- `mock_data_processor` - Mocked data processor -- `training_job_in_db` - Sample training job in database -- `trained_model_in_db` - Sample trained model in database - -### **Helper Functions:** -- `assert_training_job_structure()` - Validate job data structure -- `assert_model_structure()` - Validate model data structure - -## πŸš€ Running Tests - -### **Run All Tests:** -```bash -cd services/training -pytest tests/ -v -``` - -### **Run Specific Test Categories:** -```bash -# API tests only -pytest tests/test_api.py -v - -# ML component tests -pytest tests/test_ml.py -v - -# Service layer tests -pytest tests/test_service.py -v - -# Messaging tests -pytest tests/test_messaging.py -v - -# Integration tests -pytest tests/test_integration.py -v -``` - -### **Run with Coverage:** -```bash -pytest tests/ --cov=app --cov-report=html --cov-report=term -``` - -### **Run Performance Tests:** -```bash -pytest tests/test_integration.py::TestPerformanceIntegration -v -``` - -### **Skip Slow Tests:** -```bash -pytest tests/ -v -m "not slow" -``` - -## πŸ“Š Test Scenarios Covered - -### **Happy Path Scenarios:** -- βœ… Complete training workflow (start β†’ progress β†’ completion) -- βœ… Single product training -- βœ… Data validation and preprocessing -- βœ… Model training and storage -- βœ… Event publishing and messaging -- βœ… Job status tracking and cancellation - -### **Error Scenarios:** -- βœ… Database connection failures -- βœ… External service unavailability -- βœ… Invalid input data -- βœ… ML training failures -- βœ… Messaging system failures -- βœ… Authentication and authorization errors - -### **Edge Cases:** -- βœ… Concurrent job execution -- βœ… Large datasets -- βœ… Malformed configurations -- βœ… Network timeouts -- βœ… Memory pressure scenarios -- βœ… Rapid successive requests - -### **Security Tests:** -- βœ… Tenant isolation -- βœ… Input validation -- βœ… SQL injection protection -- βœ… Authentication enforcement -- βœ… Data access controls - -### **Compliance Tests:** -- βœ… Audit trail creation -- βœ… Data retention policies -- βœ… GDPR compliance features -- βœ… Backward compatibility - -## 🎯 Test Quality Metrics - -### **Coverage Goals:** -- **API Layer:** 95%+ coverage -- **Service Layer:** 90%+ coverage -- **ML Components:** 85%+ coverage -- **Integration:** 80%+ coverage - -### **Test Types Distribution:** -- **Unit Tests:** ~60% (isolated component testing) -- **Integration Tests:** ~30% (service interaction testing) -- **End-to-End Tests:** ~10% (complete workflow testing) - -### **Performance Benchmarks:** -- All unit tests complete in <5 seconds -- Integration tests complete in <30 seconds -- End-to-end tests complete in <60 seconds - -## πŸ”§ Mocking Strategy - -### **External Dependencies Mocked:** -- βœ… **Data Service:** HTTP calls mocked with realistic responses -- βœ… **RabbitMQ:** Message publishing mocked for isolation -- βœ… **Database:** SQLite in-memory for fast testing -- βœ… **Prophet Models:** Training mocked for speed -- βœ… **File System:** Model storage mocked - -### **Real Components Tested:** -- βœ… **FastAPI Application:** Real app instance -- βœ… **Pydantic Validation:** Real validation logic -- βœ… **SQLAlchemy ORM:** Real database operations -- βœ… **Business Logic:** Real service layer code - -## πŸ›‘οΈ Continuous Integration - -### **CI Pipeline Tests:** -```yaml -# Example CI configuration -test_matrix: - - python: "3.11" - database: "postgresql" - - python: "3.11" - database: "sqlite" - -test_commands: - - pytest tests/ --cov=app --cov-fail-under=85 - - pytest tests/test_integration.py -m "not slow" - - pytest tests/ --maxfail=1 --tb=short -``` - -### **Quality Gates:** -- βœ… All tests must pass -- βœ… Coverage must be >85% -- βœ… No critical security issues -- βœ… Performance benchmarks met - -## πŸ“ˆ Test Maintenance - -### **Regular Updates:** -- βœ… Add tests for new features -- βœ… Update mocks when APIs change -- βœ… Review and update test data -- βœ… Maintain realistic test scenarios - -### **Monitoring:** -- βœ… Test execution time tracking -- βœ… Flaky test identification -- βœ… Coverage trend monitoring -- βœ… Test failure analysis - -This comprehensive test suite ensures the training service is robust, reliable, and ready for production deployment! πŸŽ‰ \ No newline at end of file diff --git a/services/training/tests/conftest.py b/services/training/tests/conftest.py index 6f03f588..b50da08f 100644 --- a/services/training/tests/conftest.py +++ b/services/training/tests/conftest.py @@ -1,362 +1,1595 @@ +# ================================================================ # services/training/tests/conftest.py +# ================================================================ """ -Pytest configuration and fixtures for training service tests +Test configuration and fixtures for Training Service +Provides shared fixtures, mock data, and test utilities """ import pytest import asyncio +import pandas as pd +import numpy as np +import tempfile import os -from typing import AsyncGenerator, Generator -from unittest.mock import AsyncMock, Mock, patch -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine -from sqlalchemy.orm import sessionmaker -from httpx import AsyncClient -from fastapi.testclient import TestClient +import json +from datetime import datetime, timedelta +from unittest.mock import Mock, AsyncMock, patch +from typing import Dict, List, Any, Generator +from pathlib import Path +import logging -# Add app to Python path -import sys -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +# Configure pytest-asyncio +pytestmark = pytest.mark.asyncio -from app.main import app -from app.core.database import Base, get_db -from app.core.config import settings -from app.models.training import ModelTrainingLog, TrainedModel -from app.ml.trainer import BakeryMLTrainer -from app.ml.prophet_manager import BakeryProphetManager -from app.ml.data_processor import BakeryDataProcessor +# Suppress Prophet logging during tests +logging.getLogger('prophet').setLevel(logging.WARNING) +logging.getLogger('cmdstanpy').setLevel(logging.WARNING) -# Test database URL -TEST_DATABASE_URL = "sqlite+aiosqlite:///./test_training.db" -@pytest.fixture(scope="session") -def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]: - """Create an instance of the default event loop for the test session.""" - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - -@pytest.fixture(scope="session") -async def test_engine(): - """Create test database engine""" - engine = create_async_engine(TEST_DATABASE_URL, echo=False) - - # Create all tables - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - - yield engine - - # Cleanup - await engine.dispose() - - # Remove test database file - try: - os.remove("./test_training.db") - except FileNotFoundError: - pass +# ================================================================ +# PYTEST CONFIGURATION +# ================================================================ @pytest.fixture -async def test_db_session(test_engine) -> AsyncGenerator[AsyncSession, None]: - """Create test database session""" - async_session = sessionmaker( - test_engine, class_=AsyncSession, expire_on_commit=False - ) +def large_dataset_for_performance(): + """Generate large dataset for performance testing""" + # Generate 2 years of data with 15 products + start_date = datetime(2022, 1, 1) + end_date = datetime(2024, 1, 1) + date_range = pd.date_range(start=start_date, end=end_date, freq='D') - async with async_session() as session: - yield session - await session.rollback() - -@pytest.fixture -def override_get_db(test_db_session): - """Override the get_db dependency""" - async def _override_get_db(): - yield test_db_session + products = [ + "Pan Integral", "Pan Blanco", "Croissant", "Magdalenas", + "Empanadas", "Tarta Chocolate", "Roscon Reyes", "Palmeras", + "Donuts", "Berlinas", "Napolitanas", "Ensaimadas", + "Baguette", "Pan de Molde", "Bizcocho" + ] - app.dependency_overrides[get_db] = _override_get_db - yield - app.dependency_overrides.clear() + data = [] + for date in date_range: + for product in products: + # Realistic sales with patterns + base_quantity = np.random.randint(5, 150) + + # Seasonal patterns + if date.month in [12, 1]: # Winter/Holiday season + base_quantity *= 1.4 + elif date.month in [6, 7, 8]: # Summer + base_quantity *= 0.8 + + # Weekly patterns + if date.weekday() >= 5: # Weekends + base_quantity *= 1.2 + elif date.weekday() == 0: # Monday + base_quantity *= 0.7 + + # Add noise + quantity = max(1, int(base_quantity + np.random.normal(0, base_quantity * 0.1))) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": quantity, + "revenue": round(quantity * np.random.uniform(1.5, 8.0), 2), + "temperature": round(15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) + np.random.normal(0, 3), 1), + "precipitation": max(0, np.random.exponential(0.8)), + "is_weekend": date.weekday() >= 5, + "is_holiday": _is_spanish_holiday(date) + }) + + return pd.DataFrame(data) + @pytest.fixture -async def test_client(override_get_db) -> AsyncGenerator[AsyncClient, None]: - """Create test HTTP client""" - async with AsyncClient(app=app, base_url="http://test") as client: - yield client +def memory_monitor(): + """Memory monitoring utility for performance tests""" + import psutil + import gc + + class MemoryMonitor: + def __init__(self): + self.process = psutil.Process() + self.snapshots = [] + + def snapshot(self, label: str): + gc.collect() # Force garbage collection + memory_mb = self.process.memory_info().rss / 1024 / 1024 + self.snapshots.append({ + 'label': label, + 'memory_mb': memory_mb, + 'timestamp': datetime.now() + }) + return memory_mb + + def get_peak_usage(self): + if not self.snapshots: + return 0 + return max(s['memory_mb'] for s in self.snapshots) + + def get_usage_increase(self): + if len(self.snapshots) < 2: + return 0 + return self.snapshots[-1]['memory_mb'] - self.snapshots[0]['memory_mb'] + + def report(self): + print("\n=== Memory Usage Report ===") + for snapshot in self.snapshots: + print(f"{snapshot['label']}: {snapshot['memory_mb']:.2f} MB") + print(f"Peak Usage: {self.get_peak_usage():.2f} MB") + print(f"Total Increase: {self.get_usage_increase():.2f} MB") + + return MemoryMonitor() + @pytest.fixture -def sync_test_client() -> Generator[TestClient, None, None]: - """Create synchronous test client for simple tests""" - with TestClient(app) as client: - yield client +def timing_monitor(): + """Timing monitoring utility for performance tests""" + import time + + class TimingMonitor: + def __init__(self): + self.timings = [] + self.start_time = None + + def start(self, label: str): + self.start_time = time.time() + self.current_label = label + + def stop(self): + if self.start_time is None: + return 0 + + duration = time.time() - self.start_time + self.timings.append({ + 'label': self.current_label, + 'duration': duration + }) + self.start_time = None + return duration + + def get_total_time(self): + return sum(t['duration'] for t in self.timings) + + def report(self): + print("\n=== Timing Report ===") + for timing in self.timings: + print(f"{timing['label']}: {timing['duration']:.2f}s") + print(f"Total Time: {self.get_total_time():.2f}s") + + return TimingMonitor() + + +# ================================================================ +# INTEGRATION TEST FIXTURES +# ================================================================ + +@pytest.fixture +async def integration_test_setup( + mock_external_services, + sample_bakery_sales_data, + temp_model_storage +): + """Complete setup for integration tests""" + + # Patch model storage path + with patch('app.core.config.settings.MODEL_STORAGE_PATH', str(temp_model_storage)): + + # Patch data fetching to use sample data + with patch('app.services.training_service.TrainingService._fetch_sales_data') as mock_fetch: + mock_fetch.return_value = sample_bakery_sales_data + + yield { + 'external_services': mock_external_services, + 'sales_data': sample_bakery_sales_data, + 'model_storage': temp_model_storage, + 'mock_fetch': mock_fetch + } + @pytest.fixture def mock_messaging(): - """Mock messaging for tests""" - with patch('app.services.messaging.setup_messaging') as mock_setup, \ - patch('app.services.messaging.cleanup_messaging') as mock_cleanup, \ - patch('app.services.messaging.publish_job_started') as mock_start, \ - patch('app.services.messaging.publish_job_completed') as mock_complete, \ - patch('app.services.messaging.publish_job_failed') as mock_failed: - - mock_setup.return_value = AsyncMock() - mock_cleanup.return_value = AsyncMock() - mock_start.return_value = AsyncMock(return_value=True) - mock_complete.return_value = AsyncMock(return_value=True) - mock_failed.return_value = AsyncMock(return_value=True) + """Mock messaging system for testing""" + with patch('app.services.messaging.publish_job_started') as mock_started, \ + patch('app.services.messaging.publish_job_completed') as mock_completed, \ + patch('app.services.messaging.publish_job_failed') as mock_failed, \ + patch('app.services.messaging.publish_model_trained') as mock_model: yield { - 'setup': mock_setup, - 'cleanup': mock_cleanup, - 'start': mock_start, - 'complete': mock_complete, - 'failed': mock_failed + 'publish_job_started': mock_started, + 'publish_job_completed': mock_completed, + 'publish_job_failed': mock_failed, + 'publish_model_trained': mock_model } + +# ================================================================ +# API TEST FIXTURES +# ================================================================ + @pytest.fixture -def mock_data_service(): - """Mock external data service responses""" - mock_sales_data = [ - { - "date": "2024-01-01", - "product_name": "Pan Integral", - "quantity": 45 - }, - { - "date": "2024-01-02", - "product_name": "Pan Integral", - "quantity": 52 - } - ] +async def test_app(): + """Test FastAPI application instance""" + from app.main import app + return app + + +@pytest.fixture +async def test_client(test_app): + """Test client for API testing""" + from httpx import AsyncClient - mock_weather_data = [ - { - "date": "2024-01-01", - "temperature": 15.2, - "precipitation": 0.0, - "humidity": 65 - }, - { - "date": "2024-01-02", - "temperature": 18.1, - "precipitation": 2.5, - "humidity": 72 - } - ] - - mock_traffic_data = [ - { - "date": "2024-01-01", - "traffic_volume": 120 - }, - { - "date": "2024-01-02", - "traffic_volume": 95 - } - ] - - with patch('httpx.AsyncClient') as mock_client: - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "sales": mock_sales_data, - "weather": mock_weather_data, - "traffic": mock_traffic_data - } + async with AsyncClient(app=test_app, base_url="http://test") as client: + yield client + + +@pytest.fixture +def auth_headers(): + """Mock authentication headers""" + return { + "Authorization": "Bearer test_token_123", + "X-Tenant-ID": "test_tenant_123" + } + + +# ================================================================ +# ERROR SIMULATION FIXTURES +# ================================================================ + +@pytest.fixture +def failing_external_services(): + """Mock external services that fail for error testing""" + with patch('app.external.aemet.AEMETClient') as mock_aemet, \ + patch('app.external.madrid_opendata.MadridOpenDataClient') as mock_madrid: - mock_client.return_value.__aenter__.return_value.get.return_value = mock_response + # Configure to raise exceptions + mock_aemet_instance = AsyncMock() + mock_aemet.return_value = mock_aemet_instance + mock_aemet_instance.get_historical_weather.side_effect = Exception("AEMET API Error") + + mock_madrid_instance = AsyncMock() + mock_madrid.return_value = mock_madrid_instance + mock_madrid_instance.get_historical_traffic.side_effect = Exception("Madrid API Error") yield { - 'sales': mock_sales_data, - 'weather': mock_weather_data, - 'traffic': mock_traffic_data + 'aemet': mock_aemet_instance, + 'madrid': mock_madrid_instance } + +@pytest.fixture +def corrupted_sales_data(sample_bakery_sales_data): + """Sales data with various quality issues for testing""" + corrupted_data = sample_bakery_sales_data.copy() + + # Introduce missing values (20% of quantity data) + missing_mask = np.random.random(len(corrupted_data)) < 0.2 + corrupted_data.loc[missing_mask, 'quantity'] = np.nan + + # Introduce extreme outliers (1% of data) + outlier_mask = np.random.random(len(corrupted_data)) < 0.01 + corrupted_data.loc[outlier_mask, 'quantity'] *= 100 + + # Introduce inconsistent dates (0.5% of data) + future_mask = np.random.random(len(corrupted_data)) < 0.005 + corrupted_data.loc[future_mask, 'date'] = "2025-12-31" + + # Introduce negative values (0.2% of data) + negative_mask = np.random.random(len(corrupted_data)) < 0.002 + corrupted_data.loc[negative_mask, 'quantity'] = -10 + + return corrupted_data + + +# ================================================================ +# VALIDATION TEST FIXTURES +# ================================================================ + +@pytest.fixture +def insufficient_sales_data(): + """Sales data with insufficient volume for training""" + # Only 10 days of data + start_date = datetime(2023, 1, 1) + dates = [start_date + timedelta(days=i) for i in range(10)] + + data = [] + for date in dates: + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": "Pan Integral", + "quantity": np.random.randint(10, 50), + "revenue": round(np.random.uniform(20, 100), 2), + "temperature": round(np.random.uniform(10, 25), 1), + "precipitation": 0.0, + "is_weekend": date.weekday() >= 5, + "is_holiday": False + }) + + return pd.DataFrame(data) + + +@pytest.fixture +def seasonal_product_data(): + """Data for seasonal product (Roscon Reyes) testing""" + start_date = datetime(2023, 1, 1) + dates = [start_date + timedelta(days=i) for i in range(365)] + + data = [] + for date in dates: + # Roscon Reyes has strong seasonal pattern (Christmas specialty) + base_qty = 5 # Very low base + + if date.month == 12: # December - high sales + base_qty = 20 + (date.day - 1) * 2 # Increasing through December + elif date.month == 1 and date.day <= 6: # Until Epiphany + base_qty = 50 + + # Add some noise + quantity = max(1, int(base_qty + np.random.normal(0, base_qty * 0.2))) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": "Roscon Reyes", + "quantity": quantity, + "revenue": round(quantity * 25.0, 2), # Expensive specialty item + "temperature": round(15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi), 1), + "precipitation": max(0, np.random.exponential(0.5)), + "is_weekend": date.weekday() >= 5, + "is_holiday": _is_spanish_holiday(date) + }) + + return pd.DataFrame(data) + + +# ================================================================ +# CLEANUP FIXTURES +# ================================================================ + +@pytest.fixture(autouse=True) +def cleanup_after_test(): + """Automatic cleanup after each test""" + yield + + # Clean up any test files + import tempfile + import shutil + + # Clear any temporary model files + temp_dirs = [d for d in os.listdir(tempfile.gettempdir()) if d.startswith('test_models_')] + for temp_dir in temp_dirs: + try: + shutil.rmtree(os.path.join(tempfile.gettempdir(), temp_dir)) + except: + pass + + +# ================================================================ +# TEST DATA VALIDATION UTILITIES +# ================================================================ + +class TestDataValidator: + """Utility class for validating test data quality""" + + @staticmethod + def validate_sales_data(df: pd.DataFrame) -> Dict[str, Any]: + """Validate sales data structure and quality""" + required_columns = ['date', 'product', 'quantity', 'revenue'] + missing_columns = [col for col in required_columns if col not in df.columns] + + if missing_columns: + return {'valid': False, 'error': f'Missing columns: {missing_columns}'} + + # Check data types + try: + pd.to_datetime(df['date']) + except: + return {'valid': False, 'error': 'Invalid date format'} + + if not pd.api.types.is_numeric_dtype(df['quantity']): + return {'valid': False, 'error': 'Quantity must be numeric'} + + if not pd.api.types.is_numeric_dtype(df['revenue']): + return {'valid': False, 'error': 'Revenue must be numeric'} + + # Check for negative values + if (df['quantity'] < 0).any(): + return {'valid': False, 'error': 'Negative quantities found'} + + if (df['revenue'] < 0).any(): + return {'valid': False, 'error': 'Negative revenue found'} + + return {'valid': True, 'rows': len(df), 'products': df['product'].nunique()} + + +@pytest.fixture +def test_data_validator(): + """Test data validator utility""" + return TestDataValidator() + + +# ================================================================ +# LOGGING CONFIGURATION FOR TESTS +# ================================================================ + +@pytest.fixture(autouse=True) +def configure_test_logging(): + """Configure logging for tests""" + import logging + + # Reduce log level for external libraries during tests + logging.getLogger('prophet').setLevel(logging.WARNING) + logging.getLogger('cmdstanpy').setLevel(logging.ERROR) + logging.getLogger('matplotlib').setLevel(logging.WARNING) + logging.getLogger('urllib3').setLevel(logging.WARNING) + + # Configure our app logging for tests + logger = logging.getLogger('app') + logger.setLevel(logging.INFO) + + yield + + # Reset logging after tests + logging.getLogger().handlers.clear() + + +# ================================================================ +# ENVIRONMENT SETUP +# ================================================================ + +@pytest.fixture(scope="session", autouse=True) +def setup_test_environment(): + """Setup test environment variables""" + os.environ.update({ + 'ENVIRONMENT': 'test', + 'LOG_LEVEL': 'INFO', + 'MODEL_STORAGE_PATH': '/tmp/test_models', + 'MAX_TRAINING_TIME_MINUTES': '5', + 'MIN_TRAINING_DATA_DAYS': '7', + 'PROPHET_SEASONALITY_MODE': 'additive', + 'ENABLE_SYNTHETIC_DATA': 'true', + 'SKIP_EXTERNAL_API_CALLS': 'true' + }) + + yield + + # Cleanup environment + test_vars = [ + 'ENVIRONMENT', 'LOG_LEVEL', 'MODEL_STORAGE_PATH', + 'MAX_TRAINING_TIME_MINUTES', 'MIN_TRAINING_DATA_DAYS', + 'PROPHET_SEASONALITY_MODE', 'ENABLE_SYNTHETIC_DATA', + 'SKIP_EXTERNAL_API_CALLS' + ] + + for var in test_vars: + os.environ.pop(var, None)(scope="session") +def event_loop(): + """Create an instance of the default event loop for the test session.""" + loop = asyncio.new_event_loop() + yield loop + loop.close() + + +def pytest_configure(config): + """Configure pytest with custom markers""" + config.addinivalue_line( + "markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')" + ) + config.addinivalue_line( + "markers", "integration: marks tests as integration tests" + ) + config.addinivalue_line( + "markers", "unit: marks tests as unit tests" + ) + config.addinivalue_line( + "markers", "performance: marks tests as performance tests" + ) + config.addinivalue_line( + "markers", "external: marks tests that require external services" + ) + + +def pytest_collection_modifyitems(config, items): + """Modify test collection to add markers automatically""" + for item in items: + # Mark performance tests + if "performance" in item.nodeid: + item.add_marker(pytest.mark.performance) + item.add_marker(pytest.mark.slow) + + # Mark integration tests + if "integration" in item.nodeid: + item.add_marker(pytest.mark.integration) + + # Mark end-to-end tests + if "end_to_end" in item.nodeid: + item.add_marker(pytest.mark.integration) + item.add_marker(pytest.mark.external) + + # Mark unit tests (default for others) + if not any(marker.name in ["integration", "performance"] for marker in item.iter_markers()): + item.add_marker(pytest.mark.unit) + + +# ================================================================ +# TEST DATABASE FIXTURES +# ================================================================ + +@pytest.fixture +async def test_db_session(): + """Mock database session for testing""" + mock_session = AsyncMock() + + # Mock common database operations + mock_session.add = Mock() + mock_session.commit = AsyncMock() + mock_session.rollback = AsyncMock() + mock_session.refresh = AsyncMock() + mock_session.close = AsyncMock() + mock_session.execute = AsyncMock() + mock_session.scalar = AsyncMock() + + return mock_session + + +@pytest.fixture +def training_job_in_db(): + """Mock training job already in database""" + from app.models.training import ModelTrainingLog + + job = ModelTrainingLog( + job_id="test_job_123", + tenant_id="test_tenant", + status="running", + progress=50, + current_step="Training model for Pan Integral", + config={"include_weather": True, "include_traffic": True}, + started_at=datetime.now(), + logs=["Started training", "Processing data"] + ) + + return job + + +# ================================================================ +# SAMPLE DATA FIXTURES +# ================================================================ + +@pytest.fixture +def sample_bakery_sales_data(): + """Generate comprehensive bakery sales data for testing""" + # Generate 1 year of data + start_date = datetime(2023, 1, 1) + dates = [start_date + timedelta(days=i) for i in range(365)] + + # Spanish bakery products with realistic patterns + products = [ + "Pan Integral", "Pan Blanco", "Croissant", "Magdalenas", + "Empanadas", "Tarta Chocolate", "Roscon Reyes", "Palmeras", + "Donuts", "Berlinas", "Napolitanas", "Ensaimadas" + ] + + # Product-specific configurations + product_config = { + "Pan Integral": {"base": 80, "price": 2.80, "weekend_boost": 1.1, "seasonal": False}, + "Pan Blanco": {"base": 120, "price": 2.50, "weekend_boost": 1.2, "seasonal": False}, + "Croissant": {"base": 45, "price": 1.50, "weekend_boost": 1.4, "seasonal": False}, + "Magdalenas": {"base": 30, "price": 1.20, "weekend_boost": 1.1, "seasonal": False}, + "Empanadas": {"base": 25, "price": 3.50, "weekend_boost": 0.9, "seasonal": False}, + "Tarta Chocolate": {"base": 15, "price": 18.00, "weekend_boost": 1.6, "seasonal": False}, + "Roscon Reyes": {"base": 8, "price": 25.00, "weekend_boost": 1.0, "seasonal": True}, + "Palmeras": {"base": 12, "price": 1.80, "weekend_boost": 1.2, "seasonal": False}, + "Donuts": {"base": 20, "price": 1.40, "weekend_boost": 1.3, "seasonal": False}, + "Berlinas": {"base": 18, "price": 1.60, "weekend_boost": 1.2, "seasonal": False}, + "Napolitanas": {"base": 22, "price": 1.70, "weekend_boost": 1.1, "seasonal": False}, + "Ensaimadas": {"base": 15, "price": 2.20, "weekend_boost": 1.0, "seasonal": False} + } + + data = [] + + for date in dates: + # Calculate date-specific factors + day_of_year = date.timetuple().tm_yday + is_weekend = date.weekday() >= 5 + is_holiday = _is_spanish_holiday(date) + + # Madrid weather simulation + temp = 14 + 12 * np.sin((day_of_year / 365) * 2 * np.pi) + np.random.normal(0, 3) + precip = max(0, np.random.exponential(0.8)) + + for product in products: + config = product_config[product] + + # Base quantity + base_qty = config["base"] + + # Apply weekend boost + if is_weekend: + base_qty *= config["weekend_boost"] + + # Apply holiday boost + if is_holiday: + base_qty *= 1.3 + + # Seasonal products (like Roscon Reyes for Christmas) + if config["seasonal"] and product == "Roscon Reyes": + if date.month == 12: + # Exponential increase through December + base_qty *= (1 + (date.day - 1) / 5) + elif date.month == 1 and date.day <= 6: + # High demand until Epiphany (Jan 6) + base_qty *= 3 + else: + # Very low demand rest of year + base_qty *= 0.1 + + # Weather effects + if temp > 30: # Very hot days + if product in ["Pan Integral", "Pan Blanco"]: + base_qty *= 0.7 # Less bread + elif product in ["Donuts", "Berlinas"]: + base_qty *= 0.8 # Less fried items + elif temp < 5: # Cold days + base_qty *= 1.15 # More baked goods + + # Add realistic noise and ensure minimum of 1 + quantity = max(1, int(base_qty + np.random.normal(0, base_qty * 0.12))) + revenue = round(quantity * config["price"], 2) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": quantity, + "revenue": revenue, + "temperature": round(temp, 1), + "precipitation": round(precip, 2), + "is_weekend": is_weekend, + "is_holiday": is_holiday + }) + + return pd.DataFrame(data) + + +@pytest.fixture +def sample_weather_data(): + """Generate realistic Madrid weather data""" + start_date = datetime(2023, 1, 1) + weather_data = [] + + for i in range(365): + date = start_date + timedelta(days=i) + day_of_year = date.timetuple().tm_yday + + # Madrid climate simulation + base_temp = 14 + 12 * np.sin((day_of_year / 365) * 2 * np.pi) + + # Seasonal humidity patterns + base_humidity = 50 + 20 * np.sin((day_of_year / 365) * 2 * np.pi + np.pi) + + weather_data.append({ + "date": date, + "temperature": round(base_temp + np.random.normal(0, 4), 1), + "precipitation": max(0, np.random.exponential(1.2)), + "humidity": np.random.uniform(25, 75), + "wind_speed": np.random.uniform(3, 20), + "pressure": np.random.uniform(995, 1025), + "description": np.random.choice([ + "Soleado", "Parcialmente nublado", "Nublado", + "Lluvia ligera", "Despejado", "Variable" + ]), + "source": "aemet_test" + }) + + return weather_data + + +@pytest.fixture +def sample_traffic_data(): + """Generate realistic Madrid traffic data""" + start_date = datetime(2023, 1, 1) + traffic_data = [] + + for i in range(365): + date = start_date + timedelta(days=i) + + # Generate multiple measurements per day + for hour in range(6, 22, 2): # Every 2 hours from 6 AM to 10 PM + measurement_time = date.replace(hour=hour) + + # Madrid traffic patterns + if hour in [7, 8, 9, 18, 19, 20]: # Rush hours + volume = np.random.randint(1200, 2000) + congestion = "high" + speed = np.random.randint(10, 25) + occupation = np.random.randint(60, 90) + elif hour in [12, 13, 14]: # Lunch time + volume = np.random.randint(800, 1200) + congestion = "medium" + speed = np.random.randint(20, 35) + occupation = np.random.randint(40, 70) + else: # Off-peak + volume = np.random.randint(300, 800) + congestion = "low" + speed = np.random.randint(30, 50) + occupation = np.random.randint(15, 50) + + # Weekend adjustment + if date.weekday() >= 5: + volume = int(volume * 0.8) # Less traffic on weekends + speed = min(50, int(speed * 1.2)) # Faster speeds + + traffic_data.append({ + "date": measurement_time, + "traffic_volume": volume, + "occupation_percentage": occupation, + "load_percentage": min(95, occupation + np.random.randint(5, 15)), + "average_speed": speed, + "congestion_level": congestion, + "pedestrian_count": np.random.randint(100, 800), + "measurement_point_id": "MADRID_TEST_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }) + + return traffic_data + + +# ================================================================ +# MOCK SERVICES FIXTURES +# ================================================================ + +@pytest.fixture +async def mock_aemet_client(sample_weather_data): + """Mock AEMET weather API client""" + with patch('app.external.aemet.AEMETClient') as mock_class: + mock_instance = AsyncMock() + mock_class.return_value = mock_instance + + # Configure mock responses + mock_instance.get_historical_weather.return_value = sample_weather_data + mock_instance.get_current_weather.return_value = sample_weather_data[-1] + mock_instance.get_weather_forecast.return_value = sample_weather_data[-7:] + + yield mock_instance + + +@pytest.fixture +async def mock_madrid_client(sample_traffic_data): + """Mock Madrid OpenData API client""" + with patch('app.external.madrid_opendata.MadridOpenDataClient') as mock_class: + mock_instance = AsyncMock() + mock_class.return_value = mock_instance + + # Configure mock responses + mock_instance.get_historical_traffic.return_value = sample_traffic_data + mock_instance.get_current_traffic.return_value = sample_traffic_data[-1] + + yield mock_instance + + +@pytest.fixture +async def mock_external_services(mock_aemet_client, mock_madrid_client): + """Combined mock for all external services""" + return { + 'aemet': mock_aemet_client, + 'madrid': mock_madrid_client + } + + +# ================================================================ +# ML COMPONENT FIXTURES +# ================================================================ + @pytest.fixture def mock_ml_trainer(): """Mock ML trainer for testing""" - with patch('app.ml.trainer.BakeryMLTrainer') as mock_trainer_class: - mock_trainer = Mock(spec=BakeryMLTrainer) + with patch('app.ml.trainer.BakeryMLTrainer') as mock_class: + mock_instance = AsyncMock() + mock_class.return_value = mock_instance - # Mock training results - mock_training_results = { - "job_id": "test-job-123", - "tenant_id": "test-tenant", + # Configure successful training responses + mock_instance.train_single_product.return_value = { "status": "completed", - "products_trained": 1, - "products_failed": 0, - "total_products": 1, - "training_results": { - "Pan Integral": { - "status": "success", - "model_info": { - "model_id": "test-model-123", - "model_path": "/test/models/test-model-123.pkl", - "type": "prophet", - "training_samples": 100, - "features": ["temperature", "humidity"], - "training_metrics": { - "mae": 5.2, - "rmse": 7.8, - "mape": 12.5, - "r2_score": 0.85 - }, - "data_period": { - "start_date": "2024-01-01", - "end_date": "2024-01-31" - } - }, - "data_points": 100 - } - }, - "summary": { - "success_rate": 100.0, - "total_products": 1, - "successful_products": 1, - "failed_products": 0 - } - } - - mock_trainer.train_tenant_models.return_value = AsyncMock(return_value=mock_training_results) - mock_trainer.train_single_product.return_value = AsyncMock(return_value={ - "status": "success", - "model_info": mock_training_results["training_results"]["Pan Integral"]["model_info"] - }) - - mock_trainer_class.return_value = mock_trainer - yield mock_trainer - -@pytest.fixture -def sample_training_job() -> dict: - """Sample training job data""" - return { - "job_id": "test-job-123", - "tenant_id": "test-tenant", - "status": "pending", - "progress": 0, - "current_step": "Initializing", - "config": { - "include_weather": True, - "include_traffic": True, - "min_data_points": 30 - } - } - -@pytest.fixture -def sample_trained_model() -> dict: - """Sample trained model data""" - return { - "model_id": "test-model-123", - "tenant_id": "test-tenant", - "product_name": "Pan Integral", - "model_type": "prophet", - "model_path": "/test/models/test-model-123.pkl", - "version": 1, - "training_samples": 100, - "features": ["temperature", "humidity", "traffic_volume"], - "hyperparameters": { - "seasonality_mode": "additive", - "daily_seasonality": True, - "weekly_seasonality": True - }, - "training_metrics": { - "mae": 5.2, - "rmse": 7.8, - "mape": 12.5, - "r2_score": 0.85 - }, - "is_active": True - } - -@pytest.fixture -async def training_job_in_db(test_db_session, sample_training_job): - """Create a training job in the test database""" - training_log = ModelTrainingLog(**sample_training_job) - test_db_session.add(training_log) - await test_db_session.commit() - await test_db_session.refresh(training_log) - return training_log - -@pytest.fixture -async def trained_model_in_db(test_db_session, sample_trained_model): - """Create a trained model in the test database""" - from datetime import datetime - - model_data = sample_trained_model.copy() - model_data.update({ - "data_period_start": datetime(2024, 1, 1), - "data_period_end": datetime(2024, 1, 31), - "created_at": datetime.now() - }) - - trained_model = TrainedModel(**model_data) - test_db_session.add(trained_model) - await test_db_session.commit() - await test_db_session.refresh(trained_model) - return trained_model - -@pytest.fixture -def mock_prophet_manager(): - """Mock Prophet manager for testing""" - with patch('app.ml.prophet_manager.BakeryProphetManager') as mock_manager_class: - mock_manager = Mock(spec=BakeryProphetManager) - - mock_model_info = { - "model_id": "test-model-123", - "model_path": "/test/models/test-model-123.pkl", - "type": "prophet", - "training_samples": 100, - "features": ["temperature", "humidity"], - "training_metrics": { - "mae": 5.2, - "rmse": 7.8, - "mape": 12.5, + "model_id": "test_model_123", + "metrics": { + "mape": 25.5, + "rmse": 12.3, + "mae": 8.7, "r2_score": 0.85 - } + }, + "training_duration": 45.2, + "data_points_used": 365 } - mock_manager.train_bakery_model.return_value = AsyncMock(return_value=mock_model_info) - mock_manager.generate_forecast.return_value = AsyncMock() + mock_instance.train_tenant_models.return_value = [ + { + "product_name": "Pan Integral", + "model_id": "model_pan_integral_123", + "metrics": {"mape": 22.1, "rmse": 10.5, "mae": 7.8}, + "training_completed": True + }, + { + "product_name": "Croissant", + "model_id": "model_croissant_456", + "metrics": {"mape": 28.3, "rmse": 8.9, "mae": 6.2}, + "training_completed": True + } + ] - mock_manager_class.return_value = mock_manager - yield mock_manager + yield mock_instance + @pytest.fixture def mock_data_processor(): """Mock data processor for testing""" - import pandas as pd - - with patch('app.ml.data_processor.BakeryDataProcessor') as mock_processor_class: - mock_processor = Mock(spec=BakeryDataProcessor) + with patch('app.ml.data_processor.BakeryDataProcessor') as mock_class: + mock_instance = AsyncMock() + mock_class.return_value = mock_instance - # Mock processed data - mock_processed_data = pd.DataFrame({ - 'ds': pd.date_range('2024-01-01', periods=30, freq='D'), - 'y': [45 + i for i in range(30)], - 'temperature': [15.0 + (i % 10) for i in range(30)], - 'humidity': [60.0 + (i % 20) for i in range(30)] + # Configure mock responses + mock_instance.validate_data_quality.return_value = { + "is_valid": True, + "data_points": 1000, + "missing_percentage": 2.5, + "issues": [] + } + + mock_instance.prepare_training_data.return_value = pd.DataFrame({ + "ds": pd.date_range("2023-01-01", periods=365), + "y": np.random.randint(10, 100, 365), + "temperature": np.random.uniform(0, 35, 365), + "traffic_volume": np.random.randint(100, 2000, 365) }) - mock_processor.prepare_training_data.return_value = AsyncMock(return_value=mock_processed_data) - mock_processor.prepare_prediction_features.return_value = AsyncMock(return_value=mock_processed_data) - - mock_processor_class.return_value = mock_processor - yield mock_processor + yield mock_instance + @pytest.fixture -def mock_auth(): - """Mock authentication for tests""" - with patch('shared.auth.decorators.require_auth') as mock_auth: - mock_auth.return_value = lambda func: func # Pass through without auth - yield mock_auth +def mock_prophet_manager(): + """Mock Prophet manager for testing""" + with patch('app.ml.prophet_manager.BakeryProphetManager') as mock_class: + mock_instance = AsyncMock() + mock_class.return_value = mock_instance + + # Configure mock responses + mock_instance.train_model.return_value = { + "model": Mock(), # Mock Prophet model + "metrics": { + "mape": 23.7, + "rmse": 11.2, + "mae": 8.1 + }, + "cross_validation": { + "cv_mape_mean": 25.1, + "cv_mape_std": 3.2 + } + } + + mock_instance.generate_predictions.return_value = pd.DataFrame({ + "ds": pd.date_range("2024-01-01", periods=30), + "yhat": np.random.uniform(20, 80, 30), + "yhat_lower": np.random.uniform(10, 60, 30), + "yhat_upper": np.random.uniform(30, 100, 30) + }) + + yield mock_instance -# Helper functions for tests -def assert_training_job_structure(job_data: dict): - """Assert that training job data has correct structure""" - required_fields = ["job_id", "status", "tenant_id", "created_at"] - for field in required_fields: - assert field in job_data, f"Missing required field: {field}" -def assert_model_structure(model_data: dict): - """Assert that model data has correct structure""" - required_fields = ["model_id", "model_type", "training_samples", "features"] - for field in required_fields: - assert field in model_data, f"Missing required field: {field}" \ No newline at end of file +# ================================================================ +# UTILITY FIXTURES +# ================================================================ + +@pytest.fixture +def temp_model_storage(): + """Temporary directory for model storage during tests""" + with tempfile.TemporaryDirectory() as temp_dir: + yield Path(temp_dir) + + +@pytest.fixture +def test_config(): + """Test configuration settings""" + return { + "MODEL_STORAGE_PATH": "/tmp/test_models", + "MAX_TRAINING_TIME_MINUTES": 5, + "MIN_TRAINING_DATA_DAYS": 7, + "PROPHET_SEASONALITY_MODE": "additive", + "INCLUDE_SPANISH_HOLIDAYS": True, + "ENABLE_SYNTHETIC_DATA": True + } + + +@pytest.fixture +def sample_training_request(): + """Sample training request for API tests""" + return { + "products": ["Pan Integral", "Croissant"], + "include_weather": True, + "include_traffic": True, + "config": { + "seasonality_mode": "additive", + "changepoint_prior_scale": 0.05, + "seasonality_prior_scale": 10.0, + "validation_enabled": True + } + } + + +@pytest.fixture +def sample_single_product_request(): + """Sample single product training request""" + return { + "product_name": "Pan Integral", + "include_weather": True, + "include_traffic": False, + "config": { + "seasonality_mode": "multiplicative", + "include_holidays": True, + "holiday_prior_scale": 15.0 + } + } + + +# ================================================================ +# HELPER FUNCTIONS +# ================================================================ + +def _is_spanish_holiday(date: datetime) -> bool: + """Check if date is a Spanish holiday""" + spanish_holidays = [ + (1, 1), # AΓ±o Nuevo + (1, 6), # Reyes Magos + (5, 1), # DΓ­a del Trabajo + (8, 15), # AsunciΓ³n de la Virgen + (10, 12), # Fiesta Nacional de EspaΓ±a + (11, 1), # Todos los Santos + (12, 6), # DΓ­a de la ConstituciΓ³n + (12, 8), # Inmaculada ConcepciΓ³n + (12, 25), # Navidad + ] + return (date.month, date.day) in spanish_holidays + + +@pytest.fixture +def spanish_holidays_2023(): + """List of Spanish holidays for 2023""" + holidays = [] + for month, day in [ + (1, 1), (1, 6), (5, 1), (8, 15), (10, 12), + (11, 1), (12, 6), (12, 8), (12, 25) + ]: + holidays.append(datetime(2023, month, day)) + return holidays + + +# ================================================================ +# PERFORMANCE TESTING FIXTURES +# ================================================================ + +@pytest.fixture +def large_dataset_for_performance(): + """Generate large dataset for performance testing""" + # Generate 2 years of data with 15 products + start_date = datetime(2022, 1, 1) + end_date = datetime(2024, 1, 1) + date_range = pd.date_range(start=start_date, end=end_date, freq='D') + + products = [ + "Pan Integral", "Pan Blanco", "Croissant", "Magdalenas", + "Empanadas", "Tarta Chocolate", "Roscon Reyes", "Palmeras", + "Donuts", "Berlinas", "Napolitanas", "Ensaimadas", + "Baguette", "Pan de Molde", "Bizcocho" + ] + + data = [] + for date in date_range: + for product in products: + # Realistic sales with patterns + base_quantity = np.random.randint(5, 150) + + # Seasonal patterns + if date.month in [12, 1]: # Winter/Holiday season + base_quantity *= 1.4 + elif date.month in [6, 7, 8]: # Summer + base_quantity *= 0.8 + + # Weekly patterns + if date.weekday() >= 5: # Weekends + base_quantity *= 1.2 + elif date.weekday() == 0: # Monday + base_quantity *= 0.7 + + # Add noise + quantity = max(1, int(base_quantity + np.random.normal(0, base_quantity * 0.1))) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": quantity, + "revenue": round(quantity * np.random.uniform(1.5, 8.0), 2), + "temperature": round(15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) + np.random.normal(0, 3), 1), + "precipitation": max(0, np.random.exponential(0.8)), + "is_weekend": date.weekday() >= 5, + "is_holiday": _is_spanish_holiday(date) + }) + + return pd.DataFrame(data) + + +@pytest.fixture +def memory_monitor(): + """Memory monitoring utility for performance tests""" + import psutil + import gc + + class MemoryMonitor: + def __init__(self): + self.process = psutil.Process() + self.snapshots = [] + + def snapshot(self, label: str): + gc.collect() # Force garbage collection + memory_mb = self.process.memory_info().rss / 1024 / 1024 + self.snapshots.append({ + 'label': label, + 'memory_mb': memory_mb, + 'timestamp': datetime.now() + }) + return memory_mb + + def get_peak_usage(self): + if not self.snapshots: + return 0 + return max(s['memory_mb'] for s in self.snapshots) + + def get_usage_increase(self): + if len(self.snapshots) < 2: + return 0 + return self.snapshots[-1]['memory_mb'] - self.snapshots[0]['memory_mb'] + + def report(self): + print("\n=== Memory Usage Report ===") + for snapshot in self.snapshots: + print(f"{snapshot['label']}: {snapshot['memory_mb']:.2f} MB") + print(f"Peak Usage: {self.get_peak_usage():.2f} MB") + print(f"Total Increase: {self.get_usage_increase():.2f} MB") + + return MemoryMonitor() + + +@pytest.fixture +def timing_monitor(): + """Timing monitoring utility for performance tests""" + import time + + class TimingMonitor: + def __init__(self): + self.timings = [] + self.start_time = None + + def start(self, label: str): + self.start_time = time.time() + self.current_label = label + + def stop(self): + if self.start_time is None: + return 0 + + duration = time.time() - self.start_time + self.timings.append({ + 'label': self.current_label, + 'duration': duration + }) + self.start_time = None + return duration + + def get_total_time(self): + return sum(t['duration'] for t in self.timings) + + def report(self): + print("\n=== Timing Report ===") + for timing in self.timings: + print(f"{timing['label']}: {timing['duration']:.2f}s") + print(f"Total Time: {self.get_total_time():.2f}s") + + return TimingMonitor() + + +# ================================================================ +# ADDITIONAL FIXTURES FOR COMPREHENSIVE TESTING +# ================================================================ + +@pytest.fixture +def mock_job_scheduler(): + """Mock job scheduler for testing""" + with patch('app.services.job_scheduler.JobScheduler') as mock_scheduler: + mock_instance = Mock() + mock_scheduler.return_value = mock_instance + + mock_instance.schedule_job.return_value = "scheduled_job_123" + mock_instance.cancel_job.return_value = True + mock_instance.get_job_status.return_value = "running" + + yield mock_instance + + +@pytest.fixture +def sample_model_metadata(): + """Sample model metadata for testing""" + return { + "model_id": "test_model_123", + "tenant_id": "test_tenant", + "product_name": "Pan Integral", + "model_type": "prophet", + "training_date": datetime.now().isoformat(), + "data_points_used": 365, + "features_used": ["temperature", "is_weekend", "is_holiday"], + "metrics": { + "mape": 23.5, + "rmse": 12.3, + "mae": 8.7, + "r2_score": 0.85 + }, + "hyperparameters": { + "seasonality_mode": "additive", + "changepoint_prior_scale": 0.05, + "seasonality_prior_scale": 10.0 + }, + "version": "1.0", + "status": "active" + } + + +@pytest.fixture +def training_progress_states(): + """Different training progress states for testing""" + return [ + {"status": "pending", "progress": 0, "current_step": "Initializing training job"}, + {"status": "running", "progress": 10, "current_step": "Fetching sales data"}, + {"status": "running", "progress": 25, "current_step": "Processing weather data"}, + {"status": "running", "progress": 40, "current_step": "Processing traffic data"}, + {"status": "running", "progress": 55, "current_step": "Engineering features"}, + {"status": "running", "progress": 70, "current_step": "Training Pan Integral model"}, + {"status": "running", "progress": 85, "current_step": "Validating model performance"}, + {"status": "running", "progress": 95, "current_step": "Saving model artifacts"}, + {"status": "completed", "progress": 100, "current_step": "Training completed successfully"} + ] + + +@pytest.fixture +def error_scenarios(): + """Different error scenarios for testing""" + return { + "insufficient_data": { + "error_type": "DataError", + "error_message": "Insufficient training data: only 15 days available, minimum 30 required", + "error_code": "INSUFFICIENT_DATA" + }, + "external_api_failure": { + "error_type": "ExternalAPIError", + "error_message": "Failed to fetch weather data from AEMET API", + "error_code": "WEATHER_API_ERROR" + }, + "model_training_failure": { + "error_type": "ModelTrainingError", + "error_message": "Prophet model training failed: unable to fit data", + "error_code": "MODEL_TRAINING_FAILED" + }, + "data_quality_error": { + "error_type": "DataQualityError", + "error_message": "Data quality issues detected: 45% missing values in quantity column", + "error_code": "DATA_QUALITY_POOR" + } + } + + +@pytest.fixture +def performance_benchmarks(): + """Performance benchmarks for testing""" + return { + "single_product_training": { + "max_duration_seconds": 120, + "max_memory_mb": 500, + "min_accuracy_mape": 50 + }, + "multi_product_training": { + "max_duration_seconds": 300, + "max_memory_mb": 1000, + "min_accuracy_mape": 55 + }, + "data_processing": { + "max_throughput_rows_per_second": 1000, + "max_memory_per_1k_rows_mb": 10 + }, + "concurrent_jobs": { + "max_concurrent_jobs": 5, + "max_queue_time_seconds": 30 + } + } + + +@pytest.fixture +def mock_model_storage(): + """Mock model storage system for testing""" + storage = {} + + class MockModelStorage: + def save_model(self, model_id: str, model_data: Any, metadata: Dict[str, Any]): + storage[model_id] = { + "model_data": model_data, + "metadata": metadata, + "saved_at": datetime.now() + } + return f"/models/{model_id}.pkl" + + def load_model(self, model_id: str): + if model_id in storage: + return storage[model_id]["model_data"] + raise FileNotFoundError(f"Model {model_id} not found") + + def get_metadata(self, model_id: str): + if model_id in storage: + return storage[model_id]["metadata"] + raise FileNotFoundError(f"Model {model_id} not found") + + def delete_model(self, model_id: str): + if model_id in storage: + del storage[model_id] + return True + return False + + def list_models(self, tenant_id: str = None): + models = [] + for model_id, data in storage.items(): + if tenant_id is None or data["metadata"].get("tenant_id") == tenant_id: + models.append({ + "model_id": model_id, + "metadata": data["metadata"], + "saved_at": data["saved_at"] + }) + return models + + return MockModelStorage() + + +@pytest.fixture +def real_world_scenarios(): + """Real-world bakery scenarios for testing""" + return { + "holiday_rush": { + "description": "Christmas season with high demand for seasonal products", + "date_range": ("2023-12-15", "2023-12-31"), + "expected_patterns": { + "Roscon Reyes": {"multiplier": 5.0, "trend": "increasing"}, + "Pan Integral": {"multiplier": 1.3, "trend": "stable"}, + "Tarta Chocolate": {"multiplier": 2.0, "trend": "increasing"} + } + }, + "summer_slowdown": { + "description": "Summer period with generally lower sales", + "date_range": ("2023-07-01", "2023-08-31"), + "expected_patterns": { + "Pan Integral": {"multiplier": 0.8, "trend": "decreasing"}, + "Croissant": {"multiplier": 0.9, "trend": "stable"}, + "Cold_drinks": {"multiplier": 1.5, "trend": "increasing"} + } + }, + "weekend_patterns": { + "description": "Weekend shopping patterns", + "expected_patterns": { + "weekend_boost": 1.2, + "peak_hours": ["10:00", "11:00", "18:00", "19:00"], + "popular_products": ["Croissant", "Palmeras", "Tarta Chocolate"] + } + }, + "weather_impact": { + "description": "Weather impact on sales", + "scenarios": { + "rainy_day": {"bread_sales": 1.1, "pastry_sales": 0.9}, + "hot_day": {"bread_sales": 0.8, "cold_items": 1.3}, + "cold_day": {"bread_sales": 1.2, "hot_items": 1.4} + } + } + } + + +@pytest.fixture +def data_quality_test_cases(): + """Various data quality test cases""" + return { + "missing_values": { + "quantity_missing_5pct": 0.05, + "quantity_missing_20pct": 0.20, + "quantity_missing_50pct": 0.50, + "revenue_missing_10pct": 0.10 + }, + "outliers": { + "extreme_high": 100, # 100x normal values + "extreme_low": 0.01, # Near-zero values + "negative_values": -1, + "outlier_percentage": 0.01 + }, + "inconsistencies": { + "future_dates": ["2025-12-31", "2026-01-01"], + "invalid_dates": ["2023-13-01", "2023-02-30"], + "mismatched_revenue": True, # Revenue doesn't match quantity * price + "duplicate_records": True + }, + "insufficient_data": { + "too_few_days": 10, + "too_few_products": 1, + "sporadic_data": 0.3 # Only 30% of expected data points + } + } + + +@pytest.fixture +def api_test_scenarios(): + """API testing scenarios""" + return { + "authentication": { + "valid_token": "Bearer valid_test_token_123", + "invalid_token": "Bearer invalid_token", + "expired_token": "Bearer expired_token_456", + "missing_token": None + }, + "request_validation": { + "valid_request": { + "products": ["Pan Integral"], + "include_weather": True, + "include_traffic": True, + "config": {"seasonality_mode": "additive"} + }, + "invalid_products": { + "products": [], # Empty products list + "include_weather": True + }, + "invalid_config": { + "products": ["Pan Integral"], + "config": {"seasonality_mode": "invalid_mode"} + }, + "missing_required_fields": { + "include_weather": True # Missing products + } + }, + "rate_limiting": { + "max_requests_per_minute": 60, + "burst_requests": 100 + } + } + + +@pytest.fixture +def integration_test_dependencies(): + """Dependencies for integration testing""" + + class IntegrationDependencies: + def __init__(self): + self.external_services = {} + self.databases = {} + self.message_queues = {} + self.storage_systems = {} + + def register_external_service(self, name: str, mock_instance): + self.external_services[name] = mock_instance + + def register_database(self, name: str, mock_session): + self.databases[name] = mock_session + + def register_message_queue(self, name: str, mock_queue): + self.message_queues[name] = mock_queue + + def register_storage(self, name: str, mock_storage): + self.storage_systems[name] = mock_storage + + def get_service(self, name: str): + return self.external_services.get(name) + + def get_database(self, name: str): + return self.databases.get(name) + + def are_all_services_healthy(self): + # Mock health check for all registered services + return len(self.external_services) > 0 + + return IntegrationDependencies() + + +@pytest.fixture +def load_test_configuration(): + """Configuration for load testing""" + return { + "concurrent_users": { + "light_load": 5, + "medium_load": 15, + "heavy_load": 30, + "stress_load": 50 + }, + "test_duration": { + "quick_test": 60, # 1 minute + "standard_test": 300, # 5 minutes + "extended_test": 900 # 15 minutes + }, + "request_patterns": { + "constant_rate": "steady", + "ramp_up": "increasing", + "spike": "burst", + "random": "variable" + }, + "success_criteria": { + "min_success_rate": 0.95, + "max_response_time": 30.0, # seconds + "max_error_rate": 0.05 + } + } + + +@pytest.fixture +def mock_notification_system(): + """Mock notification system for testing""" + notifications_sent = [] + + class MockNotificationSystem: + def send_training_started(self, tenant_id: str, job_id: str, products: List[str]): + notification = { + "type": "training_started", + "tenant_id": tenant_id, + "job_id": job_id, + "products": products, + "timestamp": datetime.now() + } + notifications_sent.append(notification) + return notification + + def send_training_completed(self, tenant_id: str, job_id: str, results: Dict[str, Any]): + notification = { + "type": "training_completed", + "tenant_id": tenant_id, + "job_id": job_id, + "results": results, + "timestamp": datetime.now() + } + notifications_sent.append(notification) + return notification + + def send_training_failed(self, tenant_id: str, job_id: str, error: str): + notification = { + "type": "training_failed", + "tenant_id": tenant_id, + "job_id": job_id, + "error": error, + "timestamp": datetime.now() + } + notifications_sent.append(notification) + return notification + + def get_notifications(self, tenant_id: str = None): + if tenant_id: + return [n for n in notifications_sent if n["tenant_id"] == tenant_id] + return notifications_sent + + def clear_notifications(self): + notifications_sent.clear() + + return MockNotificationSystem() + + +@pytest.fixture +def test_metrics_collector(): + """Test metrics collector for monitoring test performance""" + metrics = {} + + class TestMetricsCollector: + def __init__(self): + self.start_times = {} + self.counters = {} + self.gauges = {} + self.histograms = {} + + def start_timer(self, metric_name: str): + self.start_times[metric_name] = time.time() + + def end_timer(self, metric_name: str): + if metric_name in self.start_times: + duration = time.time() - self.start_times[metric_name] + if metric_name not in self.histograms: + self.histograms[metric_name] = [] + self.histograms[metric_name].append(duration) + del self.start_times[metric_name] + return duration + return 0 + + def increment_counter(self, counter_name: str, value: int = 1): + self.counters[counter_name] = self.counters.get(counter_name, 0) + value + + def set_gauge(self, gauge_name: str, value: float): + self.gauges[gauge_name] = value + + def get_counter(self, counter_name: str): + return self.counters.get(counter_name, 0) + + def get_gauge(self, gauge_name: str): + return self.gauges.get(gauge_name, 0) + + def get_histogram_stats(self, histogram_name: str): + if histogram_name not in self.histograms: + return {} + + values = self.histograms[histogram_name] + return { + "count": len(values), + "min": min(values) if values else 0, + "max": max(values) if values else 0, + "avg": sum(values) / len(values) if values else 0, + "p50": sorted(values)[len(values)//2] if values else 0, + "p95": sorted(values)[int(len(values)*0.95)] if values else 0, + "p99": sorted(values)[int(len(values)*0.99)] if values else 0 + } + + def get_all_metrics(self): + return { + "counters": self.counters, + "gauges": self.gauges, + "histograms": {name: self.get_histogram_stats(name) for name in self.histograms} + } + + def reset(self): + self.start_times.clear() + self.counters.clear() + self.gauges.clear() + self.histograms.clear() + + import time + return TestMetricsCollector() + + +# ================================================================ +# PYTEST PLUGINS AND HOOKS +# ================================================================ + +def pytest_runtest_setup(item): + """Setup before each test""" + # Add any pre-test setup logic here + pass + + +def pytest_runtest_teardown(item, nextitem): + """Teardown after each test""" + # Add any post-test cleanup logic here + import gc + gc.collect() # Force garbage collection after each test + + +def pytest_sessionstart(session): + """Called after the Session object has been created""" + print("\n" + "="*80) + print("TRAINING SERVICE TEST SESSION STARTING") + print("="*80) + + +def pytest_sessionfinish(session, exitstatus): + """Called after whole test run finished""" + print("\n" + "="*80) + print("TRAINING SERVICE TEST SESSION FINISHED") + print(f"Exit Status: {exitstatus}") + print("="*80) + + +# ================================================================ +# FINAL CONFIGURATION +# ================================================================ + +# Ensure numpy doesn't use too many threads during testing +import numpy as np +np.seterr(all='ignore') # Ignore numpy warnings during tests + +# Configure pandas for testing +import pandas as pd +pd.set_option('display.max_columns', None) +pd.set_option('display.width', None) +pd.set_option('display.max_colwidth', 50) + +# Set random seeds for reproducible tests +np.random.seed(42) +import random +random.seed(42) \ No newline at end of file diff --git a/services/training/tests/fixtures/test_data/madrid_traffic_sample.json b/services/training/tests/fixtures/test_data/madrid_traffic_sample.json new file mode 100644 index 00000000..9675b057 --- /dev/null +++ b/services/training/tests/fixtures/test_data/madrid_traffic_sample.json @@ -0,0 +1,7022 @@ +[ + { + "date": "2023-06-01T08:00:00", + "traffic_volume": 1379, + "occupation_percentage": 81, + "load_percentage": 64, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 126, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-01T12:00:00", + "traffic_volume": 631, + "occupation_percentage": 44, + "load_percentage": 32, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 296, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-01T18:00:00", + "traffic_volume": 957, + "occupation_percentage": 30, + "load_percentage": 72, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 89, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-02T08:00:00", + "traffic_volume": 1174, + "occupation_percentage": 31, + "load_percentage": 42, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 53, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-02T12:00:00", + "traffic_volume": 749, + "occupation_percentage": 60, + "load_percentage": 38, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 310, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-02T18:00:00", + "traffic_volume": 1305, + "occupation_percentage": 56, + "load_percentage": 27, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 338, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-03T08:00:00", + "traffic_volume": 859, + "occupation_percentage": 67, + "load_percentage": 35, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 315, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-03T12:00:00", + "traffic_volume": 645, + "occupation_percentage": 21, + "load_percentage": 41, + "average_speed": 35, + "congestion_level": "medium", + "pedestrian_count": 399, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-03T18:00:00", + "traffic_volume": 811, + "occupation_percentage": 32, + "load_percentage": 82, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 331, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-04T08:00:00", + "traffic_volume": 1228, + "occupation_percentage": 37, + "load_percentage": 77, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 370, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-04T12:00:00", + "traffic_volume": 505, + "occupation_percentage": 44, + "load_percentage": 86, + "average_speed": 45, + "congestion_level": "medium", + "pedestrian_count": 426, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-04T18:00:00", + "traffic_volume": 1150, + "occupation_percentage": 13, + "load_percentage": 74, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 225, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-05T08:00:00", + "traffic_volume": 1200, + "occupation_percentage": 78, + "load_percentage": 41, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 259, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-05T12:00:00", + "traffic_volume": 439, + "occupation_percentage": 75, + "load_percentage": 68, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 322, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-05T18:00:00", + "traffic_volume": 1201, + "occupation_percentage": 75, + "load_percentage": 30, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 306, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-06T08:00:00", + "traffic_volume": 1269, + "occupation_percentage": 72, + "load_percentage": 67, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 370, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-06T12:00:00", + "traffic_volume": 535, + "occupation_percentage": 80, + "load_percentage": 75, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 430, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-06T18:00:00", + "traffic_volume": 859, + "occupation_percentage": 84, + "load_percentage": 49, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 175, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-07T08:00:00", + "traffic_volume": 1402, + "occupation_percentage": 63, + "load_percentage": 83, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 124, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-07T12:00:00", + "traffic_volume": 608, + "occupation_percentage": 69, + "load_percentage": 72, + "average_speed": 37, + "congestion_level": "medium", + "pedestrian_count": 67, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-07T18:00:00", + "traffic_volume": 1277, + "occupation_percentage": 55, + "load_percentage": 72, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 87, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-08T08:00:00", + "traffic_volume": 1454, + "occupation_percentage": 76, + "load_percentage": 28, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 174, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-08T12:00:00", + "traffic_volume": 692, + "occupation_percentage": 21, + "load_percentage": 56, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 447, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-08T18:00:00", + "traffic_volume": 1186, + "occupation_percentage": 21, + "load_percentage": 89, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 388, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-09T08:00:00", + "traffic_volume": 1056, + "occupation_percentage": 80, + "load_percentage": 84, + "average_speed": 39, + "congestion_level": "high", + "pedestrian_count": 151, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-09T12:00:00", + "traffic_volume": 779, + "occupation_percentage": 67, + "load_percentage": 46, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 72, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-09T18:00:00", + "traffic_volume": 1292, + "occupation_percentage": 55, + "load_percentage": 20, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 233, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-10T08:00:00", + "traffic_volume": 840, + "occupation_percentage": 71, + "load_percentage": 56, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-10T12:00:00", + "traffic_volume": 744, + "occupation_percentage": 70, + "load_percentage": 76, + "average_speed": 30, + "congestion_level": "medium", + "pedestrian_count": 139, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-10T18:00:00", + "traffic_volume": 1062, + "occupation_percentage": 61, + "load_percentage": 88, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 215, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-11T08:00:00", + "traffic_volume": 1084, + "occupation_percentage": 87, + "load_percentage": 67, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 170, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-11T12:00:00", + "traffic_volume": 470, + "occupation_percentage": 69, + "load_percentage": 29, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 390, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-11T18:00:00", + "traffic_volume": 1351, + "occupation_percentage": 70, + "load_percentage": 34, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 438, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-12T08:00:00", + "traffic_volume": 1137, + "occupation_percentage": 10, + "load_percentage": 83, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 284, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-12T12:00:00", + "traffic_volume": 669, + "occupation_percentage": 38, + "load_percentage": 86, + "average_speed": 48, + "congestion_level": "medium", + "pedestrian_count": 366, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-12T18:00:00", + "traffic_volume": 807, + "occupation_percentage": 13, + "load_percentage": 40, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 409, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-13T08:00:00", + "traffic_volume": 1385, + "occupation_percentage": 87, + "load_percentage": 51, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 404, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-13T12:00:00", + "traffic_volume": 710, + "occupation_percentage": 47, + "load_percentage": 45, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 226, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-13T18:00:00", + "traffic_volume": 1358, + "occupation_percentage": 28, + "load_percentage": 28, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 217, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-14T08:00:00", + "traffic_volume": 981, + "occupation_percentage": 35, + "load_percentage": 84, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 408, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-14T12:00:00", + "traffic_volume": 712, + "occupation_percentage": 26, + "load_percentage": 80, + "average_speed": 26, + "congestion_level": "medium", + "pedestrian_count": 106, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-14T18:00:00", + "traffic_volume": 894, + "occupation_percentage": 66, + "load_percentage": 93, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 183, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-15T08:00:00", + "traffic_volume": 1497, + "occupation_percentage": 29, + "load_percentage": 46, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 499, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-15T12:00:00", + "traffic_volume": 650, + "occupation_percentage": 76, + "load_percentage": 44, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 406, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-15T18:00:00", + "traffic_volume": 1328, + "occupation_percentage": 56, + "load_percentage": 32, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 155, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-16T08:00:00", + "traffic_volume": 813, + "occupation_percentage": 79, + "load_percentage": 54, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 251, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-16T12:00:00", + "traffic_volume": 555, + "occupation_percentage": 13, + "load_percentage": 87, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 258, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-16T18:00:00", + "traffic_volume": 865, + "occupation_percentage": 72, + "load_percentage": 88, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 97, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-17T08:00:00", + "traffic_volume": 1103, + "occupation_percentage": 52, + "load_percentage": 34, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 251, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-17T12:00:00", + "traffic_volume": 674, + "occupation_percentage": 30, + "load_percentage": 64, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 282, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-17T18:00:00", + "traffic_volume": 1227, + "occupation_percentage": 75, + "load_percentage": 32, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 497, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-18T08:00:00", + "traffic_volume": 1266, + "occupation_percentage": 23, + "load_percentage": 87, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 446, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-18T12:00:00", + "traffic_volume": 460, + "occupation_percentage": 31, + "load_percentage": 75, + "average_speed": 39, + "congestion_level": "medium", + "pedestrian_count": 276, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-18T18:00:00", + "traffic_volume": 1133, + "occupation_percentage": 26, + "load_percentage": 32, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 214, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-19T08:00:00", + "traffic_volume": 994, + "occupation_percentage": 76, + "load_percentage": 86, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 183, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-19T12:00:00", + "traffic_volume": 582, + "occupation_percentage": 17, + "load_percentage": 66, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 363, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-19T18:00:00", + "traffic_volume": 1267, + "occupation_percentage": 45, + "load_percentage": 72, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 343, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-20T08:00:00", + "traffic_volume": 1074, + "occupation_percentage": 32, + "load_percentage": 44, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 241, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-20T12:00:00", + "traffic_volume": 679, + "occupation_percentage": 75, + "load_percentage": 49, + "average_speed": 39, + "congestion_level": "medium", + "pedestrian_count": 436, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-20T18:00:00", + "traffic_volume": 887, + "occupation_percentage": 45, + "load_percentage": 86, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 460, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-21T08:00:00", + "traffic_volume": 1331, + "occupation_percentage": 63, + "load_percentage": 29, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 170, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-21T12:00:00", + "traffic_volume": 775, + "occupation_percentage": 75, + "load_percentage": 43, + "average_speed": 36, + "congestion_level": "medium", + "pedestrian_count": 149, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-21T18:00:00", + "traffic_volume": 1380, + "occupation_percentage": 52, + "load_percentage": 38, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 59, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-22T08:00:00", + "traffic_volume": 1052, + "occupation_percentage": 41, + "load_percentage": 53, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 85, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-22T12:00:00", + "traffic_volume": 756, + "occupation_percentage": 30, + "load_percentage": 28, + "average_speed": 25, + "congestion_level": "medium", + "pedestrian_count": 249, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-22T18:00:00", + "traffic_volume": 1171, + "occupation_percentage": 56, + "load_percentage": 76, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 98, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-23T08:00:00", + "traffic_volume": 812, + "occupation_percentage": 55, + "load_percentage": 26, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 393, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-23T12:00:00", + "traffic_volume": 746, + "occupation_percentage": 10, + "load_percentage": 32, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 379, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-23T18:00:00", + "traffic_volume": 1429, + "occupation_percentage": 41, + "load_percentage": 42, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 204, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-24T08:00:00", + "traffic_volume": 1492, + "occupation_percentage": 33, + "load_percentage": 64, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 145, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-24T12:00:00", + "traffic_volume": 463, + "occupation_percentage": 21, + "load_percentage": 30, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 245, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-24T18:00:00", + "traffic_volume": 851, + "occupation_percentage": 62, + "load_percentage": 86, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 363, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-25T08:00:00", + "traffic_volume": 1151, + "occupation_percentage": 89, + "load_percentage": 81, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 380, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-25T12:00:00", + "traffic_volume": 618, + "occupation_percentage": 29, + "load_percentage": 49, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 322, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-25T18:00:00", + "traffic_volume": 1318, + "occupation_percentage": 39, + "load_percentage": 46, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 347, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-26T08:00:00", + "traffic_volume": 1101, + "occupation_percentage": 60, + "load_percentage": 56, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 171, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-26T12:00:00", + "traffic_volume": 485, + "occupation_percentage": 39, + "load_percentage": 78, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 359, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-26T18:00:00", + "traffic_volume": 1075, + "occupation_percentage": 51, + "load_percentage": 20, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 294, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-27T08:00:00", + "traffic_volume": 943, + "occupation_percentage": 16, + "load_percentage": 43, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 175, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-27T12:00:00", + "traffic_volume": 426, + "occupation_percentage": 84, + "load_percentage": 45, + "average_speed": 35, + "congestion_level": "medium", + "pedestrian_count": 69, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-27T18:00:00", + "traffic_volume": 1460, + "occupation_percentage": 85, + "load_percentage": 73, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 368, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-28T08:00:00", + "traffic_volume": 1123, + "occupation_percentage": 28, + "load_percentage": 89, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 347, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-28T12:00:00", + "traffic_volume": 461, + "occupation_percentage": 53, + "load_percentage": 35, + "average_speed": 49, + "congestion_level": "medium", + "pedestrian_count": 196, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-28T18:00:00", + "traffic_volume": 948, + "occupation_percentage": 52, + "load_percentage": 94, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 393, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-29T08:00:00", + "traffic_volume": 1162, + "occupation_percentage": 72, + "load_percentage": 93, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 61, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-29T12:00:00", + "traffic_volume": 573, + "occupation_percentage": 21, + "load_percentage": 31, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 119, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-29T18:00:00", + "traffic_volume": 1401, + "occupation_percentage": 64, + "load_percentage": 65, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 264, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-30T08:00:00", + "traffic_volume": 911, + "occupation_percentage": 27, + "load_percentage": 65, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 287, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-30T12:00:00", + "traffic_volume": 738, + "occupation_percentage": 57, + "load_percentage": 48, + "average_speed": 46, + "congestion_level": "medium", + "pedestrian_count": 90, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-06-30T18:00:00", + "traffic_volume": 1130, + "occupation_percentage": 74, + "load_percentage": 59, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 169, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-01T08:00:00", + "traffic_volume": 1018, + "occupation_percentage": 23, + "load_percentage": 56, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 438, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-01T12:00:00", + "traffic_volume": 735, + "occupation_percentage": 19, + "load_percentage": 45, + "average_speed": 30, + "congestion_level": "medium", + "pedestrian_count": 389, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-01T18:00:00", + "traffic_volume": 1455, + "occupation_percentage": 65, + "load_percentage": 60, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 149, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-02T08:00:00", + "traffic_volume": 906, + "occupation_percentage": 52, + "load_percentage": 58, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 221, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-02T12:00:00", + "traffic_volume": 410, + "occupation_percentage": 12, + "load_percentage": 21, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 74, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-02T18:00:00", + "traffic_volume": 1287, + "occupation_percentage": 86, + "load_percentage": 50, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 397, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-03T08:00:00", + "traffic_volume": 894, + "occupation_percentage": 31, + "load_percentage": 72, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 386, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-03T12:00:00", + "traffic_volume": 763, + "occupation_percentage": 72, + "load_percentage": 89, + "average_speed": 44, + "congestion_level": "medium", + "pedestrian_count": 217, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-03T18:00:00", + "traffic_volume": 1430, + "occupation_percentage": 34, + "load_percentage": 41, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 210, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-04T08:00:00", + "traffic_volume": 800, + "occupation_percentage": 38, + "load_percentage": 86, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 280, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-04T12:00:00", + "traffic_volume": 517, + "occupation_percentage": 35, + "load_percentage": 86, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 66, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-04T18:00:00", + "traffic_volume": 1250, + "occupation_percentage": 14, + "load_percentage": 72, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 447, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-05T08:00:00", + "traffic_volume": 834, + "occupation_percentage": 82, + "load_percentage": 87, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 392, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-05T12:00:00", + "traffic_volume": 778, + "occupation_percentage": 60, + "load_percentage": 78, + "average_speed": 40, + "congestion_level": "medium", + "pedestrian_count": 214, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-05T18:00:00", + "traffic_volume": 1144, + "occupation_percentage": 59, + "load_percentage": 81, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 347, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-06T08:00:00", + "traffic_volume": 1281, + "occupation_percentage": 81, + "load_percentage": 39, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 273, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-06T12:00:00", + "traffic_volume": 796, + "occupation_percentage": 28, + "load_percentage": 25, + "average_speed": 46, + "congestion_level": "medium", + "pedestrian_count": 141, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-06T18:00:00", + "traffic_volume": 1458, + "occupation_percentage": 65, + "load_percentage": 84, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 377, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-07T08:00:00", + "traffic_volume": 1108, + "occupation_percentage": 72, + "load_percentage": 86, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 321, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-07T12:00:00", + "traffic_volume": 435, + "occupation_percentage": 58, + "load_percentage": 80, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 81, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-07T18:00:00", + "traffic_volume": 949, + "occupation_percentage": 38, + "load_percentage": 37, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 460, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-08T08:00:00", + "traffic_volume": 1231, + "occupation_percentage": 87, + "load_percentage": 29, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 405, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-08T12:00:00", + "traffic_volume": 746, + "occupation_percentage": 27, + "load_percentage": 64, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 442, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-08T18:00:00", + "traffic_volume": 914, + "occupation_percentage": 31, + "load_percentage": 91, + "average_speed": 39, + "congestion_level": "high", + "pedestrian_count": 286, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-09T08:00:00", + "traffic_volume": 1401, + "occupation_percentage": 65, + "load_percentage": 25, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 438, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-09T12:00:00", + "traffic_volume": 680, + "occupation_percentage": 62, + "load_percentage": 89, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 78, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-09T18:00:00", + "traffic_volume": 806, + "occupation_percentage": 27, + "load_percentage": 70, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 196, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-10T08:00:00", + "traffic_volume": 1042, + "occupation_percentage": 14, + "load_percentage": 20, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 379, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-10T12:00:00", + "traffic_volume": 447, + "occupation_percentage": 60, + "load_percentage": 34, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 289, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-10T18:00:00", + "traffic_volume": 1036, + "occupation_percentage": 36, + "load_percentage": 59, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 72, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-11T08:00:00", + "traffic_volume": 876, + "occupation_percentage": 85, + "load_percentage": 82, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 287, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-11T12:00:00", + "traffic_volume": 734, + "occupation_percentage": 78, + "load_percentage": 50, + "average_speed": 23, + "congestion_level": "medium", + "pedestrian_count": 421, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-11T18:00:00", + "traffic_volume": 881, + "occupation_percentage": 49, + "load_percentage": 75, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 379, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-12T08:00:00", + "traffic_volume": 954, + "occupation_percentage": 11, + "load_percentage": 85, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 334, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-12T12:00:00", + "traffic_volume": 509, + "occupation_percentage": 66, + "load_percentage": 60, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 194, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-12T18:00:00", + "traffic_volume": 808, + "occupation_percentage": 14, + "load_percentage": 68, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 268, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-13T08:00:00", + "traffic_volume": 1495, + "occupation_percentage": 43, + "load_percentage": 93, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 488, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-13T12:00:00", + "traffic_volume": 583, + "occupation_percentage": 69, + "load_percentage": 68, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 169, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-13T18:00:00", + "traffic_volume": 847, + "occupation_percentage": 13, + "load_percentage": 87, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 197, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-14T08:00:00", + "traffic_volume": 1168, + "occupation_percentage": 72, + "load_percentage": 53, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 185, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-14T12:00:00", + "traffic_volume": 675, + "occupation_percentage": 74, + "load_percentage": 87, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 442, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-14T18:00:00", + "traffic_volume": 1455, + "occupation_percentage": 23, + "load_percentage": 67, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 163, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-15T08:00:00", + "traffic_volume": 1250, + "occupation_percentage": 64, + "load_percentage": 58, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 422, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-15T12:00:00", + "traffic_volume": 620, + "occupation_percentage": 43, + "load_percentage": 35, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 212, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-15T18:00:00", + "traffic_volume": 1212, + "occupation_percentage": 79, + "load_percentage": 23, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 352, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-16T08:00:00", + "traffic_volume": 803, + "occupation_percentage": 23, + "load_percentage": 38, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 152, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-16T12:00:00", + "traffic_volume": 586, + "occupation_percentage": 33, + "load_percentage": 82, + "average_speed": 33, + "congestion_level": "medium", + "pedestrian_count": 346, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-16T18:00:00", + "traffic_volume": 1370, + "occupation_percentage": 36, + "load_percentage": 90, + "average_speed": 39, + "congestion_level": "high", + "pedestrian_count": 322, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-17T08:00:00", + "traffic_volume": 864, + "occupation_percentage": 65, + "load_percentage": 76, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 464, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-17T12:00:00", + "traffic_volume": 737, + "occupation_percentage": 27, + "load_percentage": 90, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 210, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-17T18:00:00", + "traffic_volume": 1065, + "occupation_percentage": 39, + "load_percentage": 42, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 194, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-18T08:00:00", + "traffic_volume": 1299, + "occupation_percentage": 59, + "load_percentage": 72, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 388, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-18T12:00:00", + "traffic_volume": 774, + "occupation_percentage": 44, + "load_percentage": 58, + "average_speed": 44, + "congestion_level": "medium", + "pedestrian_count": 52, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-18T18:00:00", + "traffic_volume": 1227, + "occupation_percentage": 85, + "load_percentage": 28, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 288, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-19T08:00:00", + "traffic_volume": 1230, + "occupation_percentage": 63, + "load_percentage": 36, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 277, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-19T12:00:00", + "traffic_volume": 416, + "occupation_percentage": 80, + "load_percentage": 35, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 415, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-19T18:00:00", + "traffic_volume": 945, + "occupation_percentage": 32, + "load_percentage": 73, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 103, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-20T08:00:00", + "traffic_volume": 830, + "occupation_percentage": 24, + "load_percentage": 42, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 331, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-20T12:00:00", + "traffic_volume": 497, + "occupation_percentage": 56, + "load_percentage": 43, + "average_speed": 31, + "congestion_level": "medium", + "pedestrian_count": 314, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-20T18:00:00", + "traffic_volume": 991, + "occupation_percentage": 15, + "load_percentage": 35, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 343, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-21T08:00:00", + "traffic_volume": 1019, + "occupation_percentage": 17, + "load_percentage": 93, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 362, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-21T12:00:00", + "traffic_volume": 582, + "occupation_percentage": 13, + "load_percentage": 77, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 278, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-21T18:00:00", + "traffic_volume": 1179, + "occupation_percentage": 32, + "load_percentage": 69, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 207, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-22T08:00:00", + "traffic_volume": 1270, + "occupation_percentage": 24, + "load_percentage": 62, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 67, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-22T12:00:00", + "traffic_volume": 555, + "occupation_percentage": 85, + "load_percentage": 88, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 53, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-22T18:00:00", + "traffic_volume": 944, + "occupation_percentage": 18, + "load_percentage": 67, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 233, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-23T08:00:00", + "traffic_volume": 1309, + "occupation_percentage": 21, + "load_percentage": 43, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 173, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-23T12:00:00", + "traffic_volume": 560, + "occupation_percentage": 62, + "load_percentage": 45, + "average_speed": 36, + "congestion_level": "medium", + "pedestrian_count": 439, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-23T18:00:00", + "traffic_volume": 964, + "occupation_percentage": 10, + "load_percentage": 22, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 55, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-24T08:00:00", + "traffic_volume": 1187, + "occupation_percentage": 49, + "load_percentage": 69, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 476, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-24T12:00:00", + "traffic_volume": 737, + "occupation_percentage": 46, + "load_percentage": 75, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 441, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-24T18:00:00", + "traffic_volume": 1409, + "occupation_percentage": 51, + "load_percentage": 84, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 486, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-25T08:00:00", + "traffic_volume": 870, + "occupation_percentage": 14, + "load_percentage": 69, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 468, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-25T12:00:00", + "traffic_volume": 405, + "occupation_percentage": 55, + "load_percentage": 41, + "average_speed": 48, + "congestion_level": "medium", + "pedestrian_count": 294, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-25T18:00:00", + "traffic_volume": 997, + "occupation_percentage": 73, + "load_percentage": 80, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 168, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-26T08:00:00", + "traffic_volume": 1196, + "occupation_percentage": 54, + "load_percentage": 71, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 422, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-26T12:00:00", + "traffic_volume": 788, + "occupation_percentage": 75, + "load_percentage": 20, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 499, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-26T18:00:00", + "traffic_volume": 1290, + "occupation_percentage": 67, + "load_percentage": 54, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 484, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-27T08:00:00", + "traffic_volume": 988, + "occupation_percentage": 49, + "load_percentage": 38, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 247, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-27T12:00:00", + "traffic_volume": 483, + "occupation_percentage": 80, + "load_percentage": 42, + "average_speed": 33, + "congestion_level": "medium", + "pedestrian_count": 62, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-27T18:00:00", + "traffic_volume": 934, + "occupation_percentage": 60, + "load_percentage": 39, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 432, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-28T08:00:00", + "traffic_volume": 1013, + "occupation_percentage": 25, + "load_percentage": 74, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-28T12:00:00", + "traffic_volume": 744, + "occupation_percentage": 16, + "load_percentage": 31, + "average_speed": 39, + "congestion_level": "medium", + "pedestrian_count": 305, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-28T18:00:00", + "traffic_volume": 823, + "occupation_percentage": 60, + "load_percentage": 31, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 131, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-29T08:00:00", + "traffic_volume": 1150, + "occupation_percentage": 52, + "load_percentage": 85, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 420, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-29T12:00:00", + "traffic_volume": 605, + "occupation_percentage": 24, + "load_percentage": 76, + "average_speed": 39, + "congestion_level": "medium", + "pedestrian_count": 304, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-29T18:00:00", + "traffic_volume": 908, + "occupation_percentage": 84, + "load_percentage": 33, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 396, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-30T08:00:00", + "traffic_volume": 1213, + "occupation_percentage": 53, + "load_percentage": 22, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 294, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-30T12:00:00", + "traffic_volume": 460, + "occupation_percentage": 83, + "load_percentage": 29, + "average_speed": 31, + "congestion_level": "medium", + "pedestrian_count": 385, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-30T18:00:00", + "traffic_volume": 957, + "occupation_percentage": 46, + "load_percentage": 20, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 71, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-31T08:00:00", + "traffic_volume": 818, + "occupation_percentage": 25, + "load_percentage": 79, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 59, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-31T12:00:00", + "traffic_volume": 590, + "occupation_percentage": 18, + "load_percentage": 35, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 257, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-07-31T18:00:00", + "traffic_volume": 865, + "occupation_percentage": 60, + "load_percentage": 28, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-01T08:00:00", + "traffic_volume": 1300, + "occupation_percentage": 89, + "load_percentage": 23, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 427, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-01T12:00:00", + "traffic_volume": 518, + "occupation_percentage": 53, + "load_percentage": 61, + "average_speed": 37, + "congestion_level": "medium", + "pedestrian_count": 304, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-01T18:00:00", + "traffic_volume": 1001, + "occupation_percentage": 79, + "load_percentage": 50, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 236, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-02T08:00:00", + "traffic_volume": 1435, + "occupation_percentage": 38, + "load_percentage": 24, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 450, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-02T12:00:00", + "traffic_volume": 610, + "occupation_percentage": 11, + "load_percentage": 35, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 225, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-02T18:00:00", + "traffic_volume": 1332, + "occupation_percentage": 35, + "load_percentage": 81, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 366, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-03T08:00:00", + "traffic_volume": 1331, + "occupation_percentage": 11, + "load_percentage": 25, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 274, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-03T12:00:00", + "traffic_volume": 691, + "occupation_percentage": 23, + "load_percentage": 74, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 447, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-03T18:00:00", + "traffic_volume": 1473, + "occupation_percentage": 46, + "load_percentage": 92, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 195, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-04T08:00:00", + "traffic_volume": 1260, + "occupation_percentage": 26, + "load_percentage": 50, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 242, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-04T12:00:00", + "traffic_volume": 618, + "occupation_percentage": 46, + "load_percentage": 33, + "average_speed": 22, + "congestion_level": "medium", + "pedestrian_count": 453, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-04T18:00:00", + "traffic_volume": 1296, + "occupation_percentage": 32, + "load_percentage": 38, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 262, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-05T08:00:00", + "traffic_volume": 928, + "occupation_percentage": 75, + "load_percentage": 30, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 338, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-05T12:00:00", + "traffic_volume": 708, + "occupation_percentage": 40, + "load_percentage": 76, + "average_speed": 39, + "congestion_level": "medium", + "pedestrian_count": 133, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-05T18:00:00", + "traffic_volume": 1077, + "occupation_percentage": 20, + "load_percentage": 90, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 471, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-06T08:00:00", + "traffic_volume": 1497, + "occupation_percentage": 72, + "load_percentage": 59, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 363, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-06T12:00:00", + "traffic_volume": 580, + "occupation_percentage": 21, + "load_percentage": 64, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 431, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-06T18:00:00", + "traffic_volume": 1162, + "occupation_percentage": 27, + "load_percentage": 31, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 259, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-07T08:00:00", + "traffic_volume": 1119, + "occupation_percentage": 32, + "load_percentage": 93, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 204, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-07T12:00:00", + "traffic_volume": 784, + "occupation_percentage": 85, + "load_percentage": 55, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 467, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-07T18:00:00", + "traffic_volume": 875, + "occupation_percentage": 32, + "load_percentage": 66, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 331, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-08T08:00:00", + "traffic_volume": 1157, + "occupation_percentage": 36, + "load_percentage": 59, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 118, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-08T12:00:00", + "traffic_volume": 500, + "occupation_percentage": 62, + "load_percentage": 65, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 220, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-08T18:00:00", + "traffic_volume": 1006, + "occupation_percentage": 17, + "load_percentage": 42, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 318, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-09T08:00:00", + "traffic_volume": 847, + "occupation_percentage": 59, + "load_percentage": 27, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 149, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-09T12:00:00", + "traffic_volume": 711, + "occupation_percentage": 66, + "load_percentage": 91, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 421, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-09T18:00:00", + "traffic_volume": 1414, + "occupation_percentage": 89, + "load_percentage": 38, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 410, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-10T08:00:00", + "traffic_volume": 952, + "occupation_percentage": 83, + "load_percentage": 91, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 194, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-10T12:00:00", + "traffic_volume": 732, + "occupation_percentage": 33, + "load_percentage": 28, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 215, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-10T18:00:00", + "traffic_volume": 1117, + "occupation_percentage": 31, + "load_percentage": 20, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 414, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-11T08:00:00", + "traffic_volume": 1101, + "occupation_percentage": 87, + "load_percentage": 77, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 368, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-11T12:00:00", + "traffic_volume": 639, + "occupation_percentage": 12, + "load_percentage": 81, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 103, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-11T18:00:00", + "traffic_volume": 978, + "occupation_percentage": 13, + "load_percentage": 61, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 168, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-12T08:00:00", + "traffic_volume": 1271, + "occupation_percentage": 15, + "load_percentage": 51, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 118, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-12T12:00:00", + "traffic_volume": 670, + "occupation_percentage": 47, + "load_percentage": 47, + "average_speed": 46, + "congestion_level": "medium", + "pedestrian_count": 82, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-12T18:00:00", + "traffic_volume": 1139, + "occupation_percentage": 50, + "load_percentage": 69, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 325, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-13T08:00:00", + "traffic_volume": 1121, + "occupation_percentage": 16, + "load_percentage": 61, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 428, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-13T12:00:00", + "traffic_volume": 646, + "occupation_percentage": 16, + "load_percentage": 84, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 373, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-13T18:00:00", + "traffic_volume": 921, + "occupation_percentage": 51, + "load_percentage": 58, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 409, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-14T08:00:00", + "traffic_volume": 1185, + "occupation_percentage": 13, + "load_percentage": 21, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 347, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-14T12:00:00", + "traffic_volume": 685, + "occupation_percentage": 61, + "load_percentage": 91, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-14T18:00:00", + "traffic_volume": 1018, + "occupation_percentage": 20, + "load_percentage": 60, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 293, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-15T08:00:00", + "traffic_volume": 1413, + "occupation_percentage": 70, + "load_percentage": 91, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 122, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-15T12:00:00", + "traffic_volume": 709, + "occupation_percentage": 89, + "load_percentage": 86, + "average_speed": 44, + "congestion_level": "medium", + "pedestrian_count": 358, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-15T18:00:00", + "traffic_volume": 1360, + "occupation_percentage": 57, + "load_percentage": 53, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 99, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-16T08:00:00", + "traffic_volume": 816, + "occupation_percentage": 51, + "load_percentage": 55, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 463, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-16T12:00:00", + "traffic_volume": 636, + "occupation_percentage": 83, + "load_percentage": 33, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 300, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-16T18:00:00", + "traffic_volume": 1324, + "occupation_percentage": 13, + "load_percentage": 42, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 218, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-17T08:00:00", + "traffic_volume": 997, + "occupation_percentage": 80, + "load_percentage": 27, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 453, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-17T12:00:00", + "traffic_volume": 636, + "occupation_percentage": 62, + "load_percentage": 71, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 118, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-17T18:00:00", + "traffic_volume": 906, + "occupation_percentage": 61, + "load_percentage": 30, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 202, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-18T08:00:00", + "traffic_volume": 1442, + "occupation_percentage": 47, + "load_percentage": 53, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 402, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-18T12:00:00", + "traffic_volume": 493, + "occupation_percentage": 34, + "load_percentage": 74, + "average_speed": 35, + "congestion_level": "medium", + "pedestrian_count": 69, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-18T18:00:00", + "traffic_volume": 821, + "occupation_percentage": 15, + "load_percentage": 38, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 454, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-19T08:00:00", + "traffic_volume": 1068, + "occupation_percentage": 48, + "load_percentage": 22, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 443, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-19T12:00:00", + "traffic_volume": 401, + "occupation_percentage": 72, + "load_percentage": 87, + "average_speed": 30, + "congestion_level": "medium", + "pedestrian_count": 229, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-19T18:00:00", + "traffic_volume": 1124, + "occupation_percentage": 65, + "load_percentage": 54, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 178, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-20T08:00:00", + "traffic_volume": 1230, + "occupation_percentage": 80, + "load_percentage": 93, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 186, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-20T12:00:00", + "traffic_volume": 412, + "occupation_percentage": 10, + "load_percentage": 85, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 74, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-20T18:00:00", + "traffic_volume": 1059, + "occupation_percentage": 35, + "load_percentage": 63, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 284, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-21T08:00:00", + "traffic_volume": 895, + "occupation_percentage": 59, + "load_percentage": 31, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 60, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-21T12:00:00", + "traffic_volume": 795, + "occupation_percentage": 41, + "load_percentage": 69, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 93, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-21T18:00:00", + "traffic_volume": 1077, + "occupation_percentage": 82, + "load_percentage": 89, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 288, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-22T08:00:00", + "traffic_volume": 936, + "occupation_percentage": 30, + "load_percentage": 67, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 340, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-22T12:00:00", + "traffic_volume": 557, + "occupation_percentage": 34, + "load_percentage": 73, + "average_speed": 45, + "congestion_level": "medium", + "pedestrian_count": 198, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-22T18:00:00", + "traffic_volume": 1037, + "occupation_percentage": 59, + "load_percentage": 20, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 456, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-23T08:00:00", + "traffic_volume": 1062, + "occupation_percentage": 82, + "load_percentage": 85, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 137, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-23T12:00:00", + "traffic_volume": 638, + "occupation_percentage": 45, + "load_percentage": 22, + "average_speed": 23, + "congestion_level": "medium", + "pedestrian_count": 400, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-23T18:00:00", + "traffic_volume": 1152, + "occupation_percentage": 11, + "load_percentage": 71, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 195, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-24T08:00:00", + "traffic_volume": 1244, + "occupation_percentage": 55, + "load_percentage": 94, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 122, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-24T12:00:00", + "traffic_volume": 671, + "occupation_percentage": 68, + "load_percentage": 62, + "average_speed": 48, + "congestion_level": "medium", + "pedestrian_count": 359, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-24T18:00:00", + "traffic_volume": 941, + "occupation_percentage": 79, + "load_percentage": 28, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 334, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-25T08:00:00", + "traffic_volume": 1465, + "occupation_percentage": 35, + "load_percentage": 72, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 468, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-25T12:00:00", + "traffic_volume": 456, + "occupation_percentage": 49, + "load_percentage": 33, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 360, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-25T18:00:00", + "traffic_volume": 1444, + "occupation_percentage": 14, + "load_percentage": 65, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 411, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-26T08:00:00", + "traffic_volume": 1096, + "occupation_percentage": 10, + "load_percentage": 53, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 385, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-26T12:00:00", + "traffic_volume": 752, + "occupation_percentage": 56, + "load_percentage": 80, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 241, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-26T18:00:00", + "traffic_volume": 973, + "occupation_percentage": 87, + "load_percentage": 77, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 262, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-27T08:00:00", + "traffic_volume": 955, + "occupation_percentage": 14, + "load_percentage": 47, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 311, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-27T12:00:00", + "traffic_volume": 721, + "occupation_percentage": 25, + "load_percentage": 32, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 207, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-27T18:00:00", + "traffic_volume": 1333, + "occupation_percentage": 19, + "load_percentage": 71, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 368, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-28T08:00:00", + "traffic_volume": 1399, + "occupation_percentage": 22, + "load_percentage": 54, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 88, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-28T12:00:00", + "traffic_volume": 725, + "occupation_percentage": 75, + "load_percentage": 63, + "average_speed": 48, + "congestion_level": "medium", + "pedestrian_count": 494, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-28T18:00:00", + "traffic_volume": 1128, + "occupation_percentage": 10, + "load_percentage": 79, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 129, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-29T08:00:00", + "traffic_volume": 1168, + "occupation_percentage": 49, + "load_percentage": 23, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 408, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-29T12:00:00", + "traffic_volume": 631, + "occupation_percentage": 14, + "load_percentage": 82, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 167, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-29T18:00:00", + "traffic_volume": 1077, + "occupation_percentage": 47, + "load_percentage": 32, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 107, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-30T08:00:00", + "traffic_volume": 909, + "occupation_percentage": 63, + "load_percentage": 67, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 240, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-30T12:00:00", + "traffic_volume": 587, + "occupation_percentage": 81, + "load_percentage": 24, + "average_speed": 25, + "congestion_level": "medium", + "pedestrian_count": 450, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-30T18:00:00", + "traffic_volume": 1101, + "occupation_percentage": 48, + "load_percentage": 53, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 113, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-31T08:00:00", + "traffic_volume": 865, + "occupation_percentage": 29, + "load_percentage": 65, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 78, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-31T12:00:00", + "traffic_volume": 412, + "occupation_percentage": 34, + "load_percentage": 30, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 303, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-08-31T18:00:00", + "traffic_volume": 1239, + "occupation_percentage": 56, + "load_percentage": 46, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 232, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-01T08:00:00", + "traffic_volume": 1033, + "occupation_percentage": 38, + "load_percentage": 52, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 188, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-01T12:00:00", + "traffic_volume": 652, + "occupation_percentage": 36, + "load_percentage": 32, + "average_speed": 36, + "congestion_level": "medium", + "pedestrian_count": 439, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-01T18:00:00", + "traffic_volume": 1441, + "occupation_percentage": 52, + "load_percentage": 54, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 82, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-02T08:00:00", + "traffic_volume": 897, + "occupation_percentage": 45, + "load_percentage": 77, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 249, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-02T12:00:00", + "traffic_volume": 638, + "occupation_percentage": 22, + "load_percentage": 75, + "average_speed": 33, + "congestion_level": "medium", + "pedestrian_count": 160, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-02T18:00:00", + "traffic_volume": 1270, + "occupation_percentage": 34, + "load_percentage": 31, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 339, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-03T08:00:00", + "traffic_volume": 1025, + "occupation_percentage": 64, + "load_percentage": 56, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 407, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-03T12:00:00", + "traffic_volume": 723, + "occupation_percentage": 14, + "load_percentage": 65, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 125, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-03T18:00:00", + "traffic_volume": 1066, + "occupation_percentage": 10, + "load_percentage": 20, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 218, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-04T08:00:00", + "traffic_volume": 874, + "occupation_percentage": 39, + "load_percentage": 45, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 222, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-04T12:00:00", + "traffic_volume": 480, + "occupation_percentage": 76, + "load_percentage": 70, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 87, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-04T18:00:00", + "traffic_volume": 882, + "occupation_percentage": 39, + "load_percentage": 60, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 88, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-05T08:00:00", + "traffic_volume": 1200, + "occupation_percentage": 13, + "load_percentage": 23, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 405, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-05T12:00:00", + "traffic_volume": 767, + "occupation_percentage": 67, + "load_percentage": 56, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 244, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-05T18:00:00", + "traffic_volume": 1384, + "occupation_percentage": 40, + "load_percentage": 52, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 327, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-06T08:00:00", + "traffic_volume": 1250, + "occupation_percentage": 22, + "load_percentage": 63, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 51, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-06T12:00:00", + "traffic_volume": 764, + "occupation_percentage": 22, + "load_percentage": 66, + "average_speed": 31, + "congestion_level": "medium", + "pedestrian_count": 51, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-06T18:00:00", + "traffic_volume": 940, + "occupation_percentage": 72, + "load_percentage": 93, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 225, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-07T08:00:00", + "traffic_volume": 1076, + "occupation_percentage": 74, + "load_percentage": 54, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 485, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-07T12:00:00", + "traffic_volume": 488, + "occupation_percentage": 39, + "load_percentage": 68, + "average_speed": 46, + "congestion_level": "medium", + "pedestrian_count": 388, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-07T18:00:00", + "traffic_volume": 1123, + "occupation_percentage": 38, + "load_percentage": 28, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 342, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-08T08:00:00", + "traffic_volume": 890, + "occupation_percentage": 34, + "load_percentage": 67, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 467, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-08T12:00:00", + "traffic_volume": 684, + "occupation_percentage": 57, + "load_percentage": 57, + "average_speed": 38, + "congestion_level": "medium", + "pedestrian_count": 379, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-08T18:00:00", + "traffic_volume": 1370, + "occupation_percentage": 65, + "load_percentage": 44, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 152, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-09T08:00:00", + "traffic_volume": 826, + "occupation_percentage": 40, + "load_percentage": 63, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-09T12:00:00", + "traffic_volume": 476, + "occupation_percentage": 28, + "load_percentage": 26, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 145, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-09T18:00:00", + "traffic_volume": 1401, + "occupation_percentage": 12, + "load_percentage": 77, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 421, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-10T08:00:00", + "traffic_volume": 1051, + "occupation_percentage": 10, + "load_percentage": 62, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 177, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-10T12:00:00", + "traffic_volume": 775, + "occupation_percentage": 10, + "load_percentage": 88, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 261, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-10T18:00:00", + "traffic_volume": 1198, + "occupation_percentage": 40, + "load_percentage": 49, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 63, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-11T08:00:00", + "traffic_volume": 1350, + "occupation_percentage": 74, + "load_percentage": 87, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 308, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-11T12:00:00", + "traffic_volume": 742, + "occupation_percentage": 30, + "load_percentage": 94, + "average_speed": 45, + "congestion_level": "medium", + "pedestrian_count": 494, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-11T18:00:00", + "traffic_volume": 885, + "occupation_percentage": 70, + "load_percentage": 42, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 453, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-12T08:00:00", + "traffic_volume": 937, + "occupation_percentage": 14, + "load_percentage": 83, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 399, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-12T12:00:00", + "traffic_volume": 700, + "occupation_percentage": 72, + "load_percentage": 88, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 229, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-12T18:00:00", + "traffic_volume": 1345, + "occupation_percentage": 19, + "load_percentage": 22, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 484, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-13T08:00:00", + "traffic_volume": 1025, + "occupation_percentage": 35, + "load_percentage": 55, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 150, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-13T12:00:00", + "traffic_volume": 442, + "occupation_percentage": 56, + "load_percentage": 56, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 414, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-13T18:00:00", + "traffic_volume": 1383, + "occupation_percentage": 83, + "load_percentage": 22, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 315, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-14T08:00:00", + "traffic_volume": 1179, + "occupation_percentage": 79, + "load_percentage": 48, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 201, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-14T12:00:00", + "traffic_volume": 534, + "occupation_percentage": 50, + "load_percentage": 64, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 97, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-14T18:00:00", + "traffic_volume": 938, + "occupation_percentage": 10, + "load_percentage": 42, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 441, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-15T08:00:00", + "traffic_volume": 1174, + "occupation_percentage": 67, + "load_percentage": 23, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 162, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-15T12:00:00", + "traffic_volume": 630, + "occupation_percentage": 41, + "load_percentage": 71, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 326, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-15T18:00:00", + "traffic_volume": 1297, + "occupation_percentage": 24, + "load_percentage": 25, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 330, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-16T08:00:00", + "traffic_volume": 1021, + "occupation_percentage": 67, + "load_percentage": 68, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 91, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-16T12:00:00", + "traffic_volume": 662, + "occupation_percentage": 51, + "load_percentage": 91, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 145, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-16T18:00:00", + "traffic_volume": 875, + "occupation_percentage": 28, + "load_percentage": 20, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 445, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-17T08:00:00", + "traffic_volume": 1229, + "occupation_percentage": 45, + "load_percentage": 51, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 203, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-17T12:00:00", + "traffic_volume": 617, + "occupation_percentage": 89, + "load_percentage": 59, + "average_speed": 43, + "congestion_level": "medium", + "pedestrian_count": 208, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-17T18:00:00", + "traffic_volume": 1291, + "occupation_percentage": 32, + "load_percentage": 81, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 113, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-18T08:00:00", + "traffic_volume": 1135, + "occupation_percentage": 24, + "load_percentage": 30, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 131, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-18T12:00:00", + "traffic_volume": 615, + "occupation_percentage": 69, + "load_percentage": 31, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 212, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-18T18:00:00", + "traffic_volume": 830, + "occupation_percentage": 86, + "load_percentage": 23, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 407, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-19T08:00:00", + "traffic_volume": 892, + "occupation_percentage": 68, + "load_percentage": 78, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 362, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-19T12:00:00", + "traffic_volume": 662, + "occupation_percentage": 56, + "load_percentage": 45, + "average_speed": 38, + "congestion_level": "medium", + "pedestrian_count": 129, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-19T18:00:00", + "traffic_volume": 1336, + "occupation_percentage": 33, + "load_percentage": 57, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 476, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-20T08:00:00", + "traffic_volume": 1408, + "occupation_percentage": 77, + "load_percentage": 93, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 423, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-20T12:00:00", + "traffic_volume": 749, + "occupation_percentage": 19, + "load_percentage": 46, + "average_speed": 40, + "congestion_level": "medium", + "pedestrian_count": 182, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-20T18:00:00", + "traffic_volume": 873, + "occupation_percentage": 11, + "load_percentage": 22, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 207, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-21T08:00:00", + "traffic_volume": 1023, + "occupation_percentage": 17, + "load_percentage": 91, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 492, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-21T12:00:00", + "traffic_volume": 731, + "occupation_percentage": 78, + "load_percentage": 82, + "average_speed": 23, + "congestion_level": "medium", + "pedestrian_count": 287, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-21T18:00:00", + "traffic_volume": 1093, + "occupation_percentage": 17, + "load_percentage": 62, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 408, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-22T08:00:00", + "traffic_volume": 1076, + "occupation_percentage": 64, + "load_percentage": 76, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 57, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-22T12:00:00", + "traffic_volume": 757, + "occupation_percentage": 62, + "load_percentage": 84, + "average_speed": 26, + "congestion_level": "medium", + "pedestrian_count": 495, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-22T18:00:00", + "traffic_volume": 1345, + "occupation_percentage": 29, + "load_percentage": 49, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 320, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-23T08:00:00", + "traffic_volume": 1120, + "occupation_percentage": 30, + "load_percentage": 45, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 434, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-23T12:00:00", + "traffic_volume": 451, + "occupation_percentage": 51, + "load_percentage": 44, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 384, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-23T18:00:00", + "traffic_volume": 1181, + "occupation_percentage": 26, + "load_percentage": 72, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 310, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-24T08:00:00", + "traffic_volume": 952, + "occupation_percentage": 70, + "load_percentage": 42, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 492, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-24T12:00:00", + "traffic_volume": 628, + "occupation_percentage": 42, + "load_percentage": 75, + "average_speed": 32, + "congestion_level": "medium", + "pedestrian_count": 278, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-24T18:00:00", + "traffic_volume": 1120, + "occupation_percentage": 29, + "load_percentage": 52, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 167, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-25T08:00:00", + "traffic_volume": 1402, + "occupation_percentage": 38, + "load_percentage": 25, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 460, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-25T12:00:00", + "traffic_volume": 614, + "occupation_percentage": 10, + "load_percentage": 67, + "average_speed": 28, + "congestion_level": "medium", + "pedestrian_count": 355, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-25T18:00:00", + "traffic_volume": 1271, + "occupation_percentage": 62, + "load_percentage": 63, + "average_speed": 40, + "congestion_level": "high", + "pedestrian_count": 96, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-26T08:00:00", + "traffic_volume": 1412, + "occupation_percentage": 56, + "load_percentage": 88, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 144, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-26T12:00:00", + "traffic_volume": 718, + "occupation_percentage": 12, + "load_percentage": 56, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 169, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-26T18:00:00", + "traffic_volume": 1147, + "occupation_percentage": 31, + "load_percentage": 31, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 365, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-27T08:00:00", + "traffic_volume": 1132, + "occupation_percentage": 23, + "load_percentage": 22, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 326, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-27T12:00:00", + "traffic_volume": 705, + "occupation_percentage": 45, + "load_percentage": 32, + "average_speed": 26, + "congestion_level": "medium", + "pedestrian_count": 183, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-27T18:00:00", + "traffic_volume": 993, + "occupation_percentage": 24, + "load_percentage": 46, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 315, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-28T08:00:00", + "traffic_volume": 1272, + "occupation_percentage": 52, + "load_percentage": 93, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 296, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-28T12:00:00", + "traffic_volume": 539, + "occupation_percentage": 36, + "load_percentage": 69, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 245, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-28T18:00:00", + "traffic_volume": 1440, + "occupation_percentage": 50, + "load_percentage": 26, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 460, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-29T08:00:00", + "traffic_volume": 987, + "occupation_percentage": 86, + "load_percentage": 43, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 465, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-29T12:00:00", + "traffic_volume": 570, + "occupation_percentage": 28, + "load_percentage": 27, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 61, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-29T18:00:00", + "traffic_volume": 1163, + "occupation_percentage": 55, + "load_percentage": 23, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 390, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-30T08:00:00", + "traffic_volume": 854, + "occupation_percentage": 52, + "load_percentage": 77, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 209, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-30T12:00:00", + "traffic_volume": 763, + "occupation_percentage": 77, + "load_percentage": 65, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 372, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-09-30T18:00:00", + "traffic_volume": 1284, + "occupation_percentage": 24, + "load_percentage": 86, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 336, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-01T08:00:00", + "traffic_volume": 1439, + "occupation_percentage": 75, + "load_percentage": 69, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 175, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-01T12:00:00", + "traffic_volume": 720, + "occupation_percentage": 42, + "load_percentage": 27, + "average_speed": 25, + "congestion_level": "medium", + "pedestrian_count": 278, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-01T18:00:00", + "traffic_volume": 864, + "occupation_percentage": 53, + "load_percentage": 67, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 425, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-02T08:00:00", + "traffic_volume": 1486, + "occupation_percentage": 88, + "load_percentage": 78, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 459, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-02T12:00:00", + "traffic_volume": 753, + "occupation_percentage": 72, + "load_percentage": 71, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 418, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-02T18:00:00", + "traffic_volume": 1070, + "occupation_percentage": 31, + "load_percentage": 41, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 67, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-03T08:00:00", + "traffic_volume": 1472, + "occupation_percentage": 51, + "load_percentage": 24, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 409, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-03T12:00:00", + "traffic_volume": 465, + "occupation_percentage": 58, + "load_percentage": 57, + "average_speed": 35, + "congestion_level": "medium", + "pedestrian_count": 139, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-03T18:00:00", + "traffic_volume": 1012, + "occupation_percentage": 37, + "load_percentage": 32, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 129, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-04T08:00:00", + "traffic_volume": 1334, + "occupation_percentage": 88, + "load_percentage": 35, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 158, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-04T12:00:00", + "traffic_volume": 542, + "occupation_percentage": 36, + "load_percentage": 93, + "average_speed": 18, + "congestion_level": "medium", + "pedestrian_count": 297, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-04T18:00:00", + "traffic_volume": 1024, + "occupation_percentage": 88, + "load_percentage": 27, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 383, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-05T08:00:00", + "traffic_volume": 877, + "occupation_percentage": 26, + "load_percentage": 73, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 495, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-05T12:00:00", + "traffic_volume": 663, + "occupation_percentage": 55, + "load_percentage": 80, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 288, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-05T18:00:00", + "traffic_volume": 1253, + "occupation_percentage": 42, + "load_percentage": 56, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 58, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-06T08:00:00", + "traffic_volume": 1452, + "occupation_percentage": 67, + "load_percentage": 34, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 92, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-06T12:00:00", + "traffic_volume": 661, + "occupation_percentage": 64, + "load_percentage": 37, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 314, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-06T18:00:00", + "traffic_volume": 982, + "occupation_percentage": 42, + "load_percentage": 94, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 191, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-07T08:00:00", + "traffic_volume": 1126, + "occupation_percentage": 64, + "load_percentage": 20, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 348, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-07T12:00:00", + "traffic_volume": 617, + "occupation_percentage": 62, + "load_percentage": 86, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 316, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-07T18:00:00", + "traffic_volume": 1251, + "occupation_percentage": 65, + "load_percentage": 30, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 180, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-08T08:00:00", + "traffic_volume": 1399, + "occupation_percentage": 73, + "load_percentage": 52, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 120, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-08T12:00:00", + "traffic_volume": 674, + "occupation_percentage": 53, + "load_percentage": 22, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 195, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-08T18:00:00", + "traffic_volume": 1342, + "occupation_percentage": 76, + "load_percentage": 72, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 352, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-09T08:00:00", + "traffic_volume": 1228, + "occupation_percentage": 19, + "load_percentage": 26, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 373, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-09T12:00:00", + "traffic_volume": 535, + "occupation_percentage": 84, + "load_percentage": 44, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 301, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-09T18:00:00", + "traffic_volume": 1356, + "occupation_percentage": 54, + "load_percentage": 34, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 64, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-10T08:00:00", + "traffic_volume": 1106, + "occupation_percentage": 13, + "load_percentage": 71, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 435, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-10T12:00:00", + "traffic_volume": 660, + "occupation_percentage": 44, + "load_percentage": 75, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 326, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-10T18:00:00", + "traffic_volume": 941, + "occupation_percentage": 88, + "load_percentage": 87, + "average_speed": 33, + "congestion_level": "high", + "pedestrian_count": 107, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-11T08:00:00", + "traffic_volume": 1281, + "occupation_percentage": 82, + "load_percentage": 41, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 191, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-11T12:00:00", + "traffic_volume": 566, + "occupation_percentage": 23, + "load_percentage": 22, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 405, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-11T18:00:00", + "traffic_volume": 1267, + "occupation_percentage": 37, + "load_percentage": 72, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 329, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-12T08:00:00", + "traffic_volume": 1109, + "occupation_percentage": 82, + "load_percentage": 88, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 159, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-12T12:00:00", + "traffic_volume": 605, + "occupation_percentage": 39, + "load_percentage": 90, + "average_speed": 22, + "congestion_level": "medium", + "pedestrian_count": 54, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-12T18:00:00", + "traffic_volume": 896, + "occupation_percentage": 74, + "load_percentage": 55, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 280, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-13T08:00:00", + "traffic_volume": 1447, + "occupation_percentage": 25, + "load_percentage": 78, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 474, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-13T12:00:00", + "traffic_volume": 528, + "occupation_percentage": 41, + "load_percentage": 75, + "average_speed": 45, + "congestion_level": "medium", + "pedestrian_count": 131, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-13T18:00:00", + "traffic_volume": 1188, + "occupation_percentage": 41, + "load_percentage": 76, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 158, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-14T08:00:00", + "traffic_volume": 1373, + "occupation_percentage": 55, + "load_percentage": 48, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 331, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-14T12:00:00", + "traffic_volume": 666, + "occupation_percentage": 87, + "load_percentage": 83, + "average_speed": 38, + "congestion_level": "medium", + "pedestrian_count": 147, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-14T18:00:00", + "traffic_volume": 1257, + "occupation_percentage": 39, + "load_percentage": 39, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 164, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-15T08:00:00", + "traffic_volume": 930, + "occupation_percentage": 25, + "load_percentage": 88, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 293, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-15T12:00:00", + "traffic_volume": 492, + "occupation_percentage": 19, + "load_percentage": 41, + "average_speed": 33, + "congestion_level": "medium", + "pedestrian_count": 378, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-15T18:00:00", + "traffic_volume": 1105, + "occupation_percentage": 12, + "load_percentage": 65, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 390, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-16T08:00:00", + "traffic_volume": 1162, + "occupation_percentage": 29, + "load_percentage": 55, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 487, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-16T12:00:00", + "traffic_volume": 537, + "occupation_percentage": 80, + "load_percentage": 24, + "average_speed": 20, + "congestion_level": "medium", + "pedestrian_count": 150, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-16T18:00:00", + "traffic_volume": 1123, + "occupation_percentage": 65, + "load_percentage": 41, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 381, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-17T08:00:00", + "traffic_volume": 1315, + "occupation_percentage": 47, + "load_percentage": 77, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 421, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-17T12:00:00", + "traffic_volume": 502, + "occupation_percentage": 61, + "load_percentage": 61, + "average_speed": 37, + "congestion_level": "medium", + "pedestrian_count": 394, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-17T18:00:00", + "traffic_volume": 1090, + "occupation_percentage": 61, + "load_percentage": 92, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 288, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-18T08:00:00", + "traffic_volume": 1371, + "occupation_percentage": 74, + "load_percentage": 76, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 147, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-18T12:00:00", + "traffic_volume": 490, + "occupation_percentage": 68, + "load_percentage": 40, + "average_speed": 40, + "congestion_level": "medium", + "pedestrian_count": 82, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-18T18:00:00", + "traffic_volume": 917, + "occupation_percentage": 41, + "load_percentage": 21, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 135, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-19T08:00:00", + "traffic_volume": 950, + "occupation_percentage": 73, + "load_percentage": 91, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 215, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-19T12:00:00", + "traffic_volume": 781, + "occupation_percentage": 15, + "load_percentage": 37, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 443, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-19T18:00:00", + "traffic_volume": 1045, + "occupation_percentage": 52, + "load_percentage": 94, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 319, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-20T08:00:00", + "traffic_volume": 1320, + "occupation_percentage": 70, + "load_percentage": 86, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 421, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-20T12:00:00", + "traffic_volume": 667, + "occupation_percentage": 54, + "load_percentage": 89, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 216, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-20T18:00:00", + "traffic_volume": 1453, + "occupation_percentage": 33, + "load_percentage": 82, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 483, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-21T08:00:00", + "traffic_volume": 1256, + "occupation_percentage": 52, + "load_percentage": 64, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 289, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-21T12:00:00", + "traffic_volume": 794, + "occupation_percentage": 60, + "load_percentage": 71, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 311, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-21T18:00:00", + "traffic_volume": 899, + "occupation_percentage": 67, + "load_percentage": 62, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 195, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-22T08:00:00", + "traffic_volume": 859, + "occupation_percentage": 44, + "load_percentage": 63, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 417, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-22T12:00:00", + "traffic_volume": 761, + "occupation_percentage": 87, + "load_percentage": 42, + "average_speed": 27, + "congestion_level": "medium", + "pedestrian_count": 151, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-22T18:00:00", + "traffic_volume": 933, + "occupation_percentage": 73, + "load_percentage": 92, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 147, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-23T08:00:00", + "traffic_volume": 1461, + "occupation_percentage": 77, + "load_percentage": 78, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 267, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-23T12:00:00", + "traffic_volume": 515, + "occupation_percentage": 25, + "load_percentage": 92, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 382, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-23T18:00:00", + "traffic_volume": 1418, + "occupation_percentage": 72, + "load_percentage": 45, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 424, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-24T08:00:00", + "traffic_volume": 1370, + "occupation_percentage": 74, + "load_percentage": 61, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 353, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-24T12:00:00", + "traffic_volume": 454, + "occupation_percentage": 85, + "load_percentage": 69, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 82, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-24T18:00:00", + "traffic_volume": 1291, + "occupation_percentage": 72, + "load_percentage": 82, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 176, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-25T08:00:00", + "traffic_volume": 1282, + "occupation_percentage": 64, + "load_percentage": 73, + "average_speed": 17, + "congestion_level": "high", + "pedestrian_count": 158, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-25T12:00:00", + "traffic_volume": 598, + "occupation_percentage": 18, + "load_percentage": 36, + "average_speed": 17, + "congestion_level": "medium", + "pedestrian_count": 60, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-25T18:00:00", + "traffic_volume": 1115, + "occupation_percentage": 22, + "load_percentage": 76, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 390, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-26T08:00:00", + "traffic_volume": 1124, + "occupation_percentage": 49, + "load_percentage": 46, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 303, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-26T12:00:00", + "traffic_volume": 454, + "occupation_percentage": 32, + "load_percentage": 60, + "average_speed": 22, + "congestion_level": "medium", + "pedestrian_count": 266, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-26T18:00:00", + "traffic_volume": 1393, + "occupation_percentage": 14, + "load_percentage": 85, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 397, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-27T08:00:00", + "traffic_volume": 931, + "occupation_percentage": 39, + "load_percentage": 56, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 479, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-27T12:00:00", + "traffic_volume": 781, + "occupation_percentage": 80, + "load_percentage": 22, + "average_speed": 40, + "congestion_level": "medium", + "pedestrian_count": 472, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-27T18:00:00", + "traffic_volume": 1072, + "occupation_percentage": 25, + "load_percentage": 32, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 416, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-28T08:00:00", + "traffic_volume": 1064, + "occupation_percentage": 85, + "load_percentage": 38, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 66, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-28T12:00:00", + "traffic_volume": 673, + "occupation_percentage": 34, + "load_percentage": 70, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 260, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-28T18:00:00", + "traffic_volume": 1165, + "occupation_percentage": 88, + "load_percentage": 36, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 250, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-29T08:00:00", + "traffic_volume": 1265, + "occupation_percentage": 10, + "load_percentage": 47, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 213, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-29T12:00:00", + "traffic_volume": 692, + "occupation_percentage": 14, + "load_percentage": 23, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 287, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-29T18:00:00", + "traffic_volume": 949, + "occupation_percentage": 81, + "load_percentage": 75, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 441, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-30T08:00:00", + "traffic_volume": 882, + "occupation_percentage": 48, + "load_percentage": 91, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 438, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-30T12:00:00", + "traffic_volume": 668, + "occupation_percentage": 56, + "load_percentage": 94, + "average_speed": 22, + "congestion_level": "medium", + "pedestrian_count": 444, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-30T18:00:00", + "traffic_volume": 979, + "occupation_percentage": 53, + "load_percentage": 28, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 420, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-31T08:00:00", + "traffic_volume": 1123, + "occupation_percentage": 34, + "load_percentage": 36, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 216, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-31T12:00:00", + "traffic_volume": 576, + "occupation_percentage": 34, + "load_percentage": 62, + "average_speed": 26, + "congestion_level": "medium", + "pedestrian_count": 140, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-10-31T18:00:00", + "traffic_volume": 1109, + "occupation_percentage": 52, + "load_percentage": 83, + "average_speed": 31, + "congestion_level": "high", + "pedestrian_count": 228, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-01T08:00:00", + "traffic_volume": 1117, + "occupation_percentage": 22, + "load_percentage": 54, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 313, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-01T12:00:00", + "traffic_volume": 799, + "occupation_percentage": 17, + "load_percentage": 90, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 152, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-01T18:00:00", + "traffic_volume": 1007, + "occupation_percentage": 79, + "load_percentage": 79, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 196, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-02T08:00:00", + "traffic_volume": 895, + "occupation_percentage": 57, + "load_percentage": 36, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 450, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-02T12:00:00", + "traffic_volume": 440, + "occupation_percentage": 26, + "load_percentage": 20, + "average_speed": 19, + "congestion_level": "medium", + "pedestrian_count": 476, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-02T18:00:00", + "traffic_volume": 1226, + "occupation_percentage": 84, + "load_percentage": 46, + "average_speed": 43, + "congestion_level": "high", + "pedestrian_count": 110, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-03T08:00:00", + "traffic_volume": 937, + "occupation_percentage": 64, + "load_percentage": 57, + "average_speed": 22, + "congestion_level": "high", + "pedestrian_count": 217, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-03T12:00:00", + "traffic_volume": 424, + "occupation_percentage": 79, + "load_percentage": 80, + "average_speed": 35, + "congestion_level": "medium", + "pedestrian_count": 200, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-03T18:00:00", + "traffic_volume": 1317, + "occupation_percentage": 40, + "load_percentage": 22, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 166, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-04T08:00:00", + "traffic_volume": 998, + "occupation_percentage": 22, + "load_percentage": 46, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 413, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-04T12:00:00", + "traffic_volume": 605, + "occupation_percentage": 45, + "load_percentage": 69, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 405, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-04T18:00:00", + "traffic_volume": 840, + "occupation_percentage": 70, + "load_percentage": 88, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 318, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-05T08:00:00", + "traffic_volume": 1397, + "occupation_percentage": 68, + "load_percentage": 37, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 147, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-05T12:00:00", + "traffic_volume": 722, + "occupation_percentage": 73, + "load_percentage": 68, + "average_speed": 38, + "congestion_level": "medium", + "pedestrian_count": 289, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-05T18:00:00", + "traffic_volume": 1440, + "occupation_percentage": 28, + "load_percentage": 84, + "average_speed": 42, + "congestion_level": "high", + "pedestrian_count": 293, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-06T08:00:00", + "traffic_volume": 1060, + "occupation_percentage": 73, + "load_percentage": 64, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 212, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-06T12:00:00", + "traffic_volume": 729, + "occupation_percentage": 84, + "load_percentage": 22, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 350, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-06T18:00:00", + "traffic_volume": 828, + "occupation_percentage": 78, + "load_percentage": 53, + "average_speed": 23, + "congestion_level": "high", + "pedestrian_count": 152, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-07T08:00:00", + "traffic_volume": 1085, + "occupation_percentage": 60, + "load_percentage": 85, + "average_speed": 32, + "congestion_level": "high", + "pedestrian_count": 453, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-07T12:00:00", + "traffic_volume": 555, + "occupation_percentage": 49, + "load_percentage": 64, + "average_speed": 47, + "congestion_level": "medium", + "pedestrian_count": 214, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-07T18:00:00", + "traffic_volume": 884, + "occupation_percentage": 17, + "load_percentage": 93, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 76, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-08T08:00:00", + "traffic_volume": 1089, + "occupation_percentage": 23, + "load_percentage": 31, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 292, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-08T12:00:00", + "traffic_volume": 705, + "occupation_percentage": 28, + "load_percentage": 57, + "average_speed": 49, + "congestion_level": "medium", + "pedestrian_count": 68, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-08T18:00:00", + "traffic_volume": 1044, + "occupation_percentage": 39, + "load_percentage": 86, + "average_speed": 36, + "congestion_level": "high", + "pedestrian_count": 453, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-09T08:00:00", + "traffic_volume": 1281, + "occupation_percentage": 48, + "load_percentage": 73, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 253, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-09T12:00:00", + "traffic_volume": 439, + "occupation_percentage": 39, + "load_percentage": 23, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 446, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-09T18:00:00", + "traffic_volume": 807, + "occupation_percentage": 11, + "load_percentage": 22, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 94, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-10T08:00:00", + "traffic_volume": 986, + "occupation_percentage": 69, + "load_percentage": 58, + "average_speed": 35, + "congestion_level": "high", + "pedestrian_count": 171, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-10T12:00:00", + "traffic_volume": 405, + "occupation_percentage": 76, + "load_percentage": 45, + "average_speed": 15, + "congestion_level": "medium", + "pedestrian_count": 70, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-10T18:00:00", + "traffic_volume": 1302, + "occupation_percentage": 60, + "load_percentage": 65, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 161, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-11T08:00:00", + "traffic_volume": 1324, + "occupation_percentage": 52, + "load_percentage": 34, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 107, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-11T12:00:00", + "traffic_volume": 730, + "occupation_percentage": 40, + "load_percentage": 53, + "average_speed": 24, + "congestion_level": "medium", + "pedestrian_count": 215, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-11T18:00:00", + "traffic_volume": 902, + "occupation_percentage": 68, + "load_percentage": 89, + "average_speed": 29, + "congestion_level": "high", + "pedestrian_count": 375, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-12T08:00:00", + "traffic_volume": 1072, + "occupation_percentage": 35, + "load_percentage": 85, + "average_speed": 49, + "congestion_level": "high", + "pedestrian_count": 128, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-12T12:00:00", + "traffic_volume": 403, + "occupation_percentage": 32, + "load_percentage": 61, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 278, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-12T18:00:00", + "traffic_volume": 1035, + "occupation_percentage": 27, + "load_percentage": 91, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 80, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-13T08:00:00", + "traffic_volume": 1385, + "occupation_percentage": 59, + "load_percentage": 70, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 388, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-13T12:00:00", + "traffic_volume": 505, + "occupation_percentage": 32, + "load_percentage": 55, + "average_speed": 41, + "congestion_level": "medium", + "pedestrian_count": 204, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-13T18:00:00", + "traffic_volume": 1339, + "occupation_percentage": 10, + "load_percentage": 40, + "average_speed": 21, + "congestion_level": "high", + "pedestrian_count": 373, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-14T08:00:00", + "traffic_volume": 816, + "occupation_percentage": 32, + "load_percentage": 40, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 303, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-14T12:00:00", + "traffic_volume": 471, + "occupation_percentage": 83, + "load_percentage": 87, + "average_speed": 37, + "congestion_level": "medium", + "pedestrian_count": 373, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-14T18:00:00", + "traffic_volume": 1141, + "occupation_percentage": 62, + "load_percentage": 67, + "average_speed": 34, + "congestion_level": "high", + "pedestrian_count": 253, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-15T08:00:00", + "traffic_volume": 1004, + "occupation_percentage": 69, + "load_percentage": 74, + "average_speed": 48, + "congestion_level": "high", + "pedestrian_count": 151, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-15T12:00:00", + "traffic_volume": 774, + "occupation_percentage": 87, + "load_percentage": 43, + "average_speed": 16, + "congestion_level": "medium", + "pedestrian_count": 377, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-15T18:00:00", + "traffic_volume": 1389, + "occupation_percentage": 45, + "load_percentage": 53, + "average_speed": 28, + "congestion_level": "high", + "pedestrian_count": 156, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-16T08:00:00", + "traffic_volume": 891, + "occupation_percentage": 69, + "load_percentage": 21, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 185, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-16T12:00:00", + "traffic_volume": 646, + "occupation_percentage": 76, + "load_percentage": 25, + "average_speed": 38, + "congestion_level": "medium", + "pedestrian_count": 316, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-16T18:00:00", + "traffic_volume": 1139, + "occupation_percentage": 46, + "load_percentage": 66, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 330, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-17T08:00:00", + "traffic_volume": 993, + "occupation_percentage": 30, + "load_percentage": 54, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 176, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-17T12:00:00", + "traffic_volume": 467, + "occupation_percentage": 66, + "load_percentage": 53, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 264, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-17T18:00:00", + "traffic_volume": 1275, + "occupation_percentage": 79, + "load_percentage": 44, + "average_speed": 20, + "congestion_level": "high", + "pedestrian_count": 131, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-18T08:00:00", + "traffic_volume": 952, + "occupation_percentage": 13, + "load_percentage": 23, + "average_speed": 26, + "congestion_level": "high", + "pedestrian_count": 96, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-18T12:00:00", + "traffic_volume": 727, + "occupation_percentage": 56, + "load_percentage": 67, + "average_speed": 23, + "congestion_level": "medium", + "pedestrian_count": 133, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-18T18:00:00", + "traffic_volume": 922, + "occupation_percentage": 23, + "load_percentage": 92, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 152, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-19T08:00:00", + "traffic_volume": 871, + "occupation_percentage": 32, + "load_percentage": 74, + "average_speed": 16, + "congestion_level": "high", + "pedestrian_count": 314, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-19T12:00:00", + "traffic_volume": 540, + "occupation_percentage": 81, + "load_percentage": 73, + "average_speed": 44, + "congestion_level": "medium", + "pedestrian_count": 156, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-19T18:00:00", + "traffic_volume": 1083, + "occupation_percentage": 37, + "load_percentage": 42, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 273, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-20T08:00:00", + "traffic_volume": 983, + "occupation_percentage": 83, + "load_percentage": 86, + "average_speed": 18, + "congestion_level": "high", + "pedestrian_count": 303, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-20T12:00:00", + "traffic_volume": 493, + "occupation_percentage": 36, + "load_percentage": 59, + "average_speed": 48, + "congestion_level": "medium", + "pedestrian_count": 471, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-20T18:00:00", + "traffic_volume": 1420, + "occupation_percentage": 41, + "load_percentage": 48, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 364, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-21T08:00:00", + "traffic_volume": 1456, + "occupation_percentage": 62, + "load_percentage": 82, + "average_speed": 47, + "congestion_level": "high", + "pedestrian_count": 103, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-21T12:00:00", + "traffic_volume": 470, + "occupation_percentage": 83, + "load_percentage": 23, + "average_speed": 37, + "congestion_level": "medium", + "pedestrian_count": 459, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-21T18:00:00", + "traffic_volume": 1300, + "occupation_percentage": 53, + "load_percentage": 87, + "average_speed": 41, + "congestion_level": "high", + "pedestrian_count": 360, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-22T08:00:00", + "traffic_volume": 803, + "occupation_percentage": 56, + "load_percentage": 39, + "average_speed": 37, + "congestion_level": "high", + "pedestrian_count": 484, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-22T12:00:00", + "traffic_volume": 790, + "occupation_percentage": 20, + "load_percentage": 91, + "average_speed": 49, + "congestion_level": "medium", + "pedestrian_count": 358, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-22T18:00:00", + "traffic_volume": 1493, + "occupation_percentage": 84, + "load_percentage": 71, + "average_speed": 24, + "congestion_level": "high", + "pedestrian_count": 262, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-23T08:00:00", + "traffic_volume": 1380, + "occupation_percentage": 15, + "load_percentage": 86, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 248, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-23T12:00:00", + "traffic_volume": 451, + "occupation_percentage": 89, + "load_percentage": 28, + "average_speed": 34, + "congestion_level": "medium", + "pedestrian_count": 299, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-23T18:00:00", + "traffic_volume": 1224, + "occupation_percentage": 37, + "load_percentage": 22, + "average_speed": 15, + "congestion_level": "high", + "pedestrian_count": 471, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-24T08:00:00", + "traffic_volume": 1063, + "occupation_percentage": 70, + "load_percentage": 83, + "average_speed": 44, + "congestion_level": "high", + "pedestrian_count": 169, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-24T12:00:00", + "traffic_volume": 421, + "occupation_percentage": 75, + "load_percentage": 53, + "average_speed": 42, + "congestion_level": "medium", + "pedestrian_count": 180, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-24T18:00:00", + "traffic_volume": 1398, + "occupation_percentage": 51, + "load_percentage": 89, + "average_speed": 27, + "congestion_level": "high", + "pedestrian_count": 131, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-25T08:00:00", + "traffic_volume": 986, + "occupation_percentage": 69, + "load_percentage": 88, + "average_speed": 30, + "congestion_level": "high", + "pedestrian_count": 222, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-25T12:00:00", + "traffic_volume": 749, + "occupation_percentage": 42, + "load_percentage": 92, + "average_speed": 49, + "congestion_level": "medium", + "pedestrian_count": 414, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-25T18:00:00", + "traffic_volume": 957, + "occupation_percentage": 25, + "load_percentage": 57, + "average_speed": 46, + "congestion_level": "high", + "pedestrian_count": 158, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-26T08:00:00", + "traffic_volume": 1484, + "occupation_percentage": 62, + "load_percentage": 22, + "average_speed": 25, + "congestion_level": "high", + "pedestrian_count": 84, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-26T12:00:00", + "traffic_volume": 520, + "occupation_percentage": 31, + "load_percentage": 46, + "average_speed": 21, + "congestion_level": "medium", + "pedestrian_count": 176, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-26T18:00:00", + "traffic_volume": 1113, + "occupation_percentage": 19, + "load_percentage": 35, + "average_speed": 45, + "congestion_level": "high", + "pedestrian_count": 381, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-27T08:00:00", + "traffic_volume": 1128, + "occupation_percentage": 69, + "load_percentage": 73, + "average_speed": 19, + "congestion_level": "high", + "pedestrian_count": 313, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-27T12:00:00", + "traffic_volume": 570, + "occupation_percentage": 50, + "load_percentage": 56, + "average_speed": 29, + "congestion_level": "medium", + "pedestrian_count": 481, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }, + { + "date": "2023-11-27T18:00:00", + "traffic_volume": 1478, + "occupation_percentage": 70, + "load_percentage": 43, + "average_speed": 38, + "congestion_level": "high", + "pedestrian_count": 247, + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + } +] \ No newline at end of file diff --git a/services/training/tests/fixtures/test_data/madrid_weather_sample.json b/services/training/tests/fixtures/test_data/madrid_weather_sample.json new file mode 100644 index 00000000..ad3f6e2d --- /dev/null +++ b/services/training/tests/fixtures/test_data/madrid_weather_sample.json @@ -0,0 +1,1802 @@ +[ + { + "date": "2023-06-01T00:00:00", + "temperature": 17.9, + "precipitation": 0.5778691204106683, + "humidity": 50.143273551956284, + "wind_speed": 15.739750544837268, + "pressure": 1002.2576456009473, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-02T00:00:00", + "temperature": 19.6, + "precipitation": 1.7506898329220113, + "humidity": 30.052726339453102, + "wind_speed": 12.497559953134555, + "pressure": 1017.6682395189941, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-03T00:00:00", + "temperature": 20.9, + "precipitation": 0.053945803621054854, + "humidity": 49.64237264635624, + "wind_speed": 24.189767927249587, + "pressure": 1023.569027125088, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-04T00:00:00", + "temperature": 17.3, + "precipitation": 2.752049665731604, + "humidity": 57.574354209019525, + "wind_speed": 11.504167595243512, + "pressure": 1023.6397740560934, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-05T00:00:00", + "temperature": 12.5, + "precipitation": 2.559937324731324, + "humidity": 44.664395482639925, + "wind_speed": 18.720116913187717, + "pressure": 1011.1032699752825, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-06T00:00:00", + "temperature": 23.6, + "precipitation": 2.6960728225443193, + "humidity": 36.53364276034031, + "wind_speed": 22.895412988925603, + "pressure": 1010.5767298032716, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-07T00:00:00", + "temperature": 16.4, + "precipitation": 1.2114306324190378, + "humidity": 58.95046812153905, + "wind_speed": 6.988075292700515, + "pressure": 1003.6012963754905, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-08T00:00:00", + "temperature": 21.9, + "precipitation": 1.0433812193027867, + "humidity": 77.48267608789112, + "wind_speed": 9.929753923729416, + "pressure": 1012.027500904873, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-09T00:00:00", + "temperature": 22.5, + "precipitation": 0.912454948616669, + "humidity": 56.249901767872664, + "wind_speed": 21.364961877581084, + "pressure": 1021.8532861144126, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-10T00:00:00", + "temperature": 23.9, + "precipitation": 0.7291091144196514, + "humidity": 60.76901549423427, + "wind_speed": 5.227917796741899, + "pressure": 1016.5862852257355, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-11T00:00:00", + "temperature": 11.0, + "precipitation": 0.5440343351836303, + "humidity": 61.6286447422181, + "wind_speed": 22.96189716005746, + "pressure": 1003.5277199899591, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-12T00:00:00", + "temperature": 17.2, + "precipitation": 0.6920992835749145, + "humidity": 51.40000508570175, + "wind_speed": 6.537474139960171, + "pressure": 1017.9079994550937, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-13T00:00:00", + "temperature": 19.3, + "precipitation": 0.4429718088200413, + "humidity": 73.87258211718985, + "wind_speed": 21.084628384386683, + "pressure": 1011.7736200834215, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-14T00:00:00", + "temperature": 17.0, + "precipitation": 0.18677093769457834, + "humidity": 53.79469882744176, + "wind_speed": 24.125085219954695, + "pressure": 1009.8476823123339, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-15T00:00:00", + "temperature": 20.6, + "precipitation": 0.43803387764416074, + "humidity": 47.19477868458975, + "wind_speed": 6.945359675658407, + "pressure": 1023.5545690313118, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-16T00:00:00", + "temperature": 15.8, + "precipitation": 2.3316150081543334, + "humidity": 51.452525340858664, + "wind_speed": 23.255725849223783, + "pressure": 1005.1530427037062, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-17T00:00:00", + "temperature": 21.1, + "precipitation": 0.9396125982553853, + "humidity": 39.44817306284658, + "wind_speed": 14.138659857982944, + "pressure": 1009.4287176751249, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-18T00:00:00", + "temperature": 17.1, + "precipitation": 0.2808148114019405, + "humidity": 36.25531547540186, + "wind_speed": 6.396104542772667, + "pressure": 1006.8104654216094, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-19T00:00:00", + "temperature": 19.7, + "precipitation": 0.409161947499557, + "humidity": 52.40110948928924, + "wind_speed": 6.036782250899771, + "pressure": 1003.4689954723348, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-20T00:00:00", + "temperature": 20.6, + "precipitation": 0.3177043590022445, + "humidity": 40.94301039216628, + "wind_speed": 11.376217709990033, + "pressure": 1006.7358310610163, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-21T00:00:00", + "temperature": 15.9, + "precipitation": 1.5412599591263447, + "humidity": 78.20284762376221, + "wind_speed": 22.798547783388877, + "pressure": 1023.4808316472705, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-22T00:00:00", + "temperature": 16.0, + "precipitation": 0.07275082146986632, + "humidity": 63.99422262210216, + "wind_speed": 21.416298967846558, + "pressure": 1000.2795548746211, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-23T00:00:00", + "temperature": 17.8, + "precipitation": 0.7274201299520647, + "humidity": 56.38126577973866, + "wind_speed": 21.619989135442317, + "pressure": 1007.8442638859453, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-24T00:00:00", + "temperature": 19.5, + "precipitation": 1.701167011353477, + "humidity": 30.140162723003673, + "wind_speed": 18.101074443548782, + "pressure": 1000.6141584699478, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-25T00:00:00", + "temperature": 7.9, + "precipitation": 0.7014343372317098, + "humidity": 65.0942631284944, + "wind_speed": 21.431737624004775, + "pressure": 1013.7481128526955, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-26T00:00:00", + "temperature": 21.7, + "precipitation": 1.9112313338239433, + "humidity": 66.58745738786952, + "wind_speed": 14.212520508786326, + "pressure": 1005.1619765382102, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-06-27T00:00:00", + "temperature": 17.7, + "precipitation": 1.3133037013886295, + "humidity": 39.140410979167285, + "wind_speed": 9.667632268371836, + "pressure": 1014.0782400359931, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-06-28T00:00:00", + "temperature": 12.7, + "precipitation": 0.05859881890442301, + "humidity": 73.00750013883237, + "wind_speed": 22.397437860837776, + "pressure": 1006.0342293804403, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-29T00:00:00", + "temperature": 15.0, + "precipitation": 1.9583160055703879, + "humidity": 36.0524181702405, + "wind_speed": 10.722891706219224, + "pressure": 1005.5838836076157, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-06-30T00:00:00", + "temperature": 14.1, + "precipitation": 0.594616240202771, + "humidity": 48.76692324676253, + "wind_speed": 6.99411854251834, + "pressure": 1005.5617770281068, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-01T00:00:00", + "temperature": 28.6, + "precipitation": 1.7778548206438678, + "humidity": 48.304415132078574, + "wind_speed": 16.441796053886595, + "pressure": 1001.452928393326, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-02T00:00:00", + "temperature": 13.7, + "precipitation": 0.22864699397238533, + "humidity": 74.50432326543952, + "wind_speed": 6.121726001899262, + "pressure": 1022.4100856621435, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-03T00:00:00", + "temperature": 15.7, + "precipitation": 0.9723466090534418, + "humidity": 61.71059367059595, + "wind_speed": 10.340453633775287, + "pressure": 1001.1351528350463, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-04T00:00:00", + "temperature": 7.8, + "precipitation": 0.3471108820345016, + "humidity": 38.15808815220118, + "wind_speed": 22.852614333799636, + "pressure": 1006.1114105519317, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-05T00:00:00", + "temperature": 5.6, + "precipitation": 0.3771034367898052, + "humidity": 47.81778551864653, + "wind_speed": 9.607148940686967, + "pressure": 1000.8364317654565, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-06T00:00:00", + "temperature": 22.3, + "precipitation": 0.46110025548025774, + "humidity": 37.894416305560995, + "wind_speed": 16.068801185589837, + "pressure": 1000.1787712128566, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-07T00:00:00", + "temperature": 10.7, + "precipitation": 4.0598663478091215, + "humidity": 47.65939515239915, + "wind_speed": 16.08961871460952, + "pressure": 1021.9864373034301, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-08T00:00:00", + "temperature": 18.2, + "precipitation": 0.47715504708770873, + "humidity": 78.34070285955734, + "wind_speed": 13.32265946740241, + "pressure": 1004.1171480609363, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-09T00:00:00", + "temperature": 13.4, + "precipitation": 0.05218749370261608, + "humidity": 37.895713684918164, + "wind_speed": 10.161379919992456, + "pressure": 1019.1112874149607, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-10T00:00:00", + "temperature": 19.2, + "precipitation": 0.37846859963787427, + "humidity": 77.05346079343909, + "wind_speed": 9.493265086473793, + "pressure": 1020.3090569991846, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-11T00:00:00", + "temperature": 4.4, + "precipitation": 0.3204122962591941, + "humidity": 38.44222030478732, + "wind_speed": 12.835775902426533, + "pressure": 1023.0793463648614, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-12T00:00:00", + "temperature": 11.3, + "precipitation": 1.535346194384968, + "humidity": 54.63438761559439, + "wind_speed": 24.90230787182857, + "pressure": 1015.6665572670543, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-13T00:00:00", + "temperature": 9.2, + "precipitation": 0.3022774726065095, + "humidity": 53.367049083935, + "wind_speed": 16.542671822849794, + "pressure": 1002.3702866009986, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-14T00:00:00", + "temperature": 6.2, + "precipitation": 0.8106811520422317, + "humidity": 53.41775278159933, + "wind_speed": 22.868081062778938, + "pressure": 1017.5270015934144, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-15T00:00:00", + "temperature": 8.4, + "precipitation": 0.5126403315314557, + "humidity": 75.71129998842304, + "wind_speed": 15.658721079382635, + "pressure": 1013.5889180464818, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-16T00:00:00", + "temperature": 9.7, + "precipitation": 0.2198646265151845, + "humidity": 77.23124436610057, + "wind_speed": 15.758789748019417, + "pressure": 1009.7415012154523, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-17T00:00:00", + "temperature": 13.0, + "precipitation": 0.19900450006160167, + "humidity": 59.29944562334474, + "wind_speed": 7.704926805000001, + "pressure": 1001.0469798809223, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-18T00:00:00", + "temperature": 12.6, + "precipitation": 1.6506043077637937, + "humidity": 62.506887198298514, + "wind_speed": 18.6706995720072, + "pressure": 1011.4952082481318, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-19T00:00:00", + "temperature": 10.5, + "precipitation": 0.0532030960416143, + "humidity": 51.154013573207514, + "wind_speed": 9.028965521759062, + "pressure": 1011.8616606786751, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-20T00:00:00", + "temperature": 9.0, + "precipitation": 1.2016396231336877, + "humidity": 41.77726610234843, + "wind_speed": 24.3844371769703, + "pressure": 1018.2500256538407, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-21T00:00:00", + "temperature": 20.5, + "precipitation": 1.0610728852939437, + "humidity": 53.278468777687564, + "wind_speed": 21.374399422629274, + "pressure": 1001.2497370214686, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-22T00:00:00", + "temperature": -1.2, + "precipitation": 0.7745466823109114, + "humidity": 51.89827649060023, + "wind_speed": 19.686018038804008, + "pressure": 1022.8688653109684, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-23T00:00:00", + "temperature": 5.9, + "precipitation": 0.9319258592195231, + "humidity": 44.538707099347974, + "wind_speed": 24.15599258650911, + "pressure": 1012.6549986842665, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-24T00:00:00", + "temperature": 19.9, + "precipitation": 0.5121597434824814, + "humidity": 52.76339094341788, + "wind_speed": 11.694425627707243, + "pressure": 1020.2882878373215, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-25T00:00:00", + "temperature": 5.0, + "precipitation": 0.22849784464382977, + "humidity": 53.94535347557009, + "wind_speed": 11.436747582486717, + "pressure": 1004.1732554246178, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-07-26T00:00:00", + "temperature": 13.1, + "precipitation": 4.631318181462757, + "humidity": 33.578857489102454, + "wind_speed": 14.920008717302798, + "pressure": 1006.8457702894372, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-27T00:00:00", + "temperature": 5.5, + "precipitation": 0.3244041441937948, + "humidity": 34.08451686986193, + "wind_speed": 20.99542431072465, + "pressure": 1005.8831559114847, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-28T00:00:00", + "temperature": 13.7, + "precipitation": 1.656471763160716, + "humidity": 52.452675271110884, + "wind_speed": 22.687264510387504, + "pressure": 1019.8178223870622, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-29T00:00:00", + "temperature": 9.1, + "precipitation": 0.21318931613337938, + "humidity": 47.080013976673044, + "wind_speed": 19.176426441399308, + "pressure": 1004.8154368700837, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-07-30T00:00:00", + "temperature": 5.7, + "precipitation": 0.38327657946434474, + "humidity": 45.484077603723605, + "wind_speed": 17.202930415914576, + "pressure": 1015.877103030233, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-07-31T00:00:00", + "temperature": 6.7, + "precipitation": 1.0104251135631852, + "humidity": 40.10543994879822, + "wind_speed": 11.07063084077991, + "pressure": 1004.0852168268844, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-01T00:00:00", + "temperature": 11.0, + "precipitation": 0.4930590959742491, + "humidity": 31.873493190235575, + "wind_speed": 22.509355853606856, + "pressure": 1015.4312757916155, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-02T00:00:00", + "temperature": 11.9, + "precipitation": 3.965946002793383, + "humidity": 45.57771141633626, + "wind_speed": 6.6791541484890455, + "pressure": 1024.9381285295428, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-03T00:00:00", + "temperature": 7.9, + "precipitation": 0.053501284019654975, + "humidity": 71.6174343625711, + "wind_speed": 18.583719995453443, + "pressure": 1009.2975162623062, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-04T00:00:00", + "temperature": 3.1, + "precipitation": 0.10488148719690486, + "humidity": 44.92620531919623, + "wind_speed": 5.779213195357529, + "pressure": 1000.8137940096404, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-05T00:00:00", + "temperature": 11.8, + "precipitation": 0.41192336902746296, + "humidity": 38.18934622779548, + "wind_speed": 10.780989423436738, + "pressure": 1013.0497632286626, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-06T00:00:00", + "temperature": 0.5, + "precipitation": 1.6495599783553125, + "humidity": 33.02206862353824, + "wind_speed": 14.702937332133372, + "pressure": 1020.3091588163421, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-07T00:00:00", + "temperature": 9.3, + "precipitation": 0.4794689704597723, + "humidity": 42.54319411336413, + "wind_speed": 5.309121110259472, + "pressure": 1005.5003842723604, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-08T00:00:00", + "temperature": 8.8, + "precipitation": 1.5652291135988738, + "humidity": 40.01670318463988, + "wind_speed": 8.316370995098312, + "pressure": 1004.6929552255808, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-09T00:00:00", + "temperature": 21.8, + "precipitation": 0.31474878826635333, + "humidity": 59.71477474551462, + "wind_speed": 20.531224046162535, + "pressure": 1005.7684826497815, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-10T00:00:00", + "temperature": 7.9, + "precipitation": 0.34247620835203907, + "humidity": 52.517059945428166, + "wind_speed": 21.80402490255432, + "pressure": 1011.4777642920614, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-11T00:00:00", + "temperature": 5.8, + "precipitation": 7.24593379458173, + "humidity": 73.38814579898458, + "wind_speed": 6.959184688735043, + "pressure": 1007.5329712212695, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-12T00:00:00", + "temperature": -2.8, + "precipitation": 0.011718266599694294, + "humidity": 77.89177850770406, + "wind_speed": 23.107338099962437, + "pressure": 1010.7355754877062, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-13T00:00:00", + "temperature": 6.6, + "precipitation": 1.2010597875520836, + "humidity": 53.67512645861916, + "wind_speed": 13.153601819691927, + "pressure": 1011.7155486887038, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-14T00:00:00", + "temperature": 7.1, + "precipitation": 0.8591586626516485, + "humidity": 75.98202112866534, + "wind_speed": 20.747998927046755, + "pressure": 1004.259567025125, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-15T00:00:00", + "temperature": 6.0, + "precipitation": 3.2252895636759833, + "humidity": 53.1390648754401, + "wind_speed": 21.79274101979263, + "pressure": 1009.1722729622485, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-16T00:00:00", + "temperature": 5.4, + "precipitation": 0.4238403776326282, + "humidity": 51.019254972902935, + "wind_speed": 7.278170157276438, + "pressure": 1020.6477074574124, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-17T00:00:00", + "temperature": -1.4, + "precipitation": 0.3700117346306844, + "humidity": 69.39440311455749, + "wind_speed": 21.336502161363747, + "pressure": 1005.6483000963008, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-18T00:00:00", + "temperature": 9.3, + "precipitation": 0.5958065216761146, + "humidity": 36.03923450496322, + "wind_speed": 13.987863623335821, + "pressure": 1016.0679980438879, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-19T00:00:00", + "temperature": 13.4, + "precipitation": 2.196248422464512, + "humidity": 48.30076280020886, + "wind_speed": 5.577078614621884, + "pressure": 1001.8467692354837, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-20T00:00:00", + "temperature": 3.8, + "precipitation": 0.9847097044332668, + "humidity": 39.32164799648709, + "wind_speed": 13.577270284966847, + "pressure": 1006.2197075497616, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-21T00:00:00", + "temperature": 8.5, + "precipitation": 0.022889740011121124, + "humidity": 33.899045275744776, + "wind_speed": 15.590488871324041, + "pressure": 1018.9550442015114, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-22T00:00:00", + "temperature": 12.3, + "precipitation": 3.9448112063453276, + "humidity": 45.723116599950124, + "wind_speed": 16.356228805729266, + "pressure": 1002.7835939902004, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-23T00:00:00", + "temperature": 0.1, + "precipitation": 0.5191545657408421, + "humidity": 30.031920085612292, + "wind_speed": 23.185115266488953, + "pressure": 1023.150746030106, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-24T00:00:00", + "temperature": 7.1, + "precipitation": 3.2781499803146534, + "humidity": 31.185070663922506, + "wind_speed": 10.201363046645383, + "pressure": 1021.3106747657034, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-25T00:00:00", + "temperature": 4.9, + "precipitation": 0.38093305376386094, + "humidity": 79.13017202084805, + "wind_speed": 12.98004425547962, + "pressure": 1001.9750006012097, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-26T00:00:00", + "temperature": 2.6, + "precipitation": 0.7805062317406598, + "humidity": 40.029466312236764, + "wind_speed": 21.212177147616593, + "pressure": 1012.9697740316185, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-27T00:00:00", + "temperature": -0.6, + "precipitation": 0.9559149025826086, + "humidity": 64.61422233128512, + "wind_speed": 10.439461688657783, + "pressure": 1009.910888367552, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-28T00:00:00", + "temperature": 4.5, + "precipitation": 1.8564291410339577, + "humidity": 60.505539801209835, + "wind_speed": 14.320912203554919, + "pressure": 1021.541875054398, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-08-29T00:00:00", + "temperature": 5.2, + "precipitation": 0.18483160177262412, + "humidity": 59.68004068245084, + "wind_speed": 6.564593925561095, + "pressure": 1004.819663158875, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-08-30T00:00:00", + "temperature": -0.4, + "precipitation": 0.9114465558724606, + "humidity": 68.35115411140154, + "wind_speed": 21.18778457167036, + "pressure": 1009.9727029545081, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-08-31T00:00:00", + "temperature": -0.8, + "precipitation": 0.5919185963335887, + "humidity": 71.17101966895576, + "wind_speed": 14.95298882267845, + "pressure": 1020.6120973198514, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-01T00:00:00", + "temperature": 7.0, + "precipitation": 0.14929398318701076, + "humidity": 55.39650118956425, + "wind_speed": 6.0129924447, + "pressure": 1018.3051122676682, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-02T00:00:00", + "temperature": 8.0, + "precipitation": 3.609492966701578, + "humidity": 79.61932163553277, + "wind_speed": 7.501418970656366, + "pressure": 1003.6207105663793, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-03T00:00:00", + "temperature": 4.2, + "precipitation": 3.9857773516347206, + "humidity": 33.49571831311766, + "wind_speed": 22.653701713941736, + "pressure": 1022.8375145726711, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-04T00:00:00", + "temperature": -1.8, + "precipitation": 0.32838174344026827, + "humidity": 44.35805749269905, + "wind_speed": 14.951027541375373, + "pressure": 1004.6035517537889, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-05T00:00:00", + "temperature": 8.4, + "precipitation": 0.008462466141770935, + "humidity": 60.96403301134015, + "wind_speed": 21.716492251658917, + "pressure": 1009.5297283246567, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-06T00:00:00", + "temperature": -4.2, + "precipitation": 0.9717556790284276, + "humidity": 78.47923560915564, + "wind_speed": 24.886652478787784, + "pressure": 1003.4551295800075, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-07T00:00:00", + "temperature": -8.4, + "precipitation": 0.05051429264204863, + "humidity": 75.85561386767097, + "wind_speed": 9.63964418813141, + "pressure": 1022.0151299862731, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-08T00:00:00", + "temperature": 4.2, + "precipitation": 0.11027072254055817, + "humidity": 62.40575211997888, + "wind_speed": 23.38617302021173, + "pressure": 1005.234093603423, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-09T00:00:00", + "temperature": 2.9, + "precipitation": 0.6974733285652236, + "humidity": 79.91983525964847, + "wind_speed": 17.528607347810492, + "pressure": 1023.8780121819165, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-10T00:00:00", + "temperature": 3.3, + "precipitation": 2.4885270048787937, + "humidity": 36.249989996103004, + "wind_speed": 24.825848416886352, + "pressure": 1000.2234501424653, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-11T00:00:00", + "temperature": -0.8, + "precipitation": 0.4850300373667475, + "humidity": 37.6949378344296, + "wind_speed": 23.672300739743182, + "pressure": 1021.1844691038173, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-12T00:00:00", + "temperature": 0.3, + "precipitation": 0.4478369604948892, + "humidity": 65.8746244855768, + "wind_speed": 17.605382172452213, + "pressure": 1014.1015584092268, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-13T00:00:00", + "temperature": 2.8, + "precipitation": 0.41984802082749745, + "humidity": 54.96755937736208, + "wind_speed": 16.33438163956814, + "pressure": 1009.2132581681811, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-14T00:00:00", + "temperature": -0.5, + "precipitation": 3.0970529073778397, + "humidity": 62.3613944729325, + "wind_speed": 11.189687459712353, + "pressure": 1012.2617484326772, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-15T00:00:00", + "temperature": 8.1, + "precipitation": 0.14032678987162084, + "humidity": 55.69745040588842, + "wind_speed": 9.010729021062197, + "pressure": 1017.6030649016609, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-16T00:00:00", + "temperature": -2.8, + "precipitation": 2.3800415682258196, + "humidity": 37.85214623443463, + "wind_speed": 17.910855298715923, + "pressure": 1008.0401508010923, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-17T00:00:00", + "temperature": 0.4, + "precipitation": 4.358682749630257, + "humidity": 54.82859847076153, + "wind_speed": 11.350844193265782, + "pressure": 1013.9435517612322, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-18T00:00:00", + "temperature": -1.4, + "precipitation": 0.40632529163191594, + "humidity": 50.71254221622633, + "wind_speed": 8.9767620246366, + "pressure": 1005.6586207963226, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-19T00:00:00", + "temperature": 4.1, + "precipitation": 3.6034390082992216, + "humidity": 53.94241388253562, + "wind_speed": 24.434089492499858, + "pressure": 1013.0447766577474, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-20T00:00:00", + "temperature": -0.8, + "precipitation": 0.5632247333808397, + "humidity": 59.07480205616567, + "wind_speed": 10.349482130928031, + "pressure": 1018.9058426732605, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-21T00:00:00", + "temperature": 4.0, + "precipitation": 2.1314396848734476, + "humidity": 56.93309588414236, + "wind_speed": 13.14377685425553, + "pressure": 1018.1755905609972, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-22T00:00:00", + "temperature": 9.5, + "precipitation": 0.1464965558631004, + "humidity": 32.851997073836834, + "wind_speed": 5.695748441758717, + "pressure": 1004.7023462316492, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-23T00:00:00", + "temperature": 10.2, + "precipitation": 0.36195653349193524, + "humidity": 49.065083976485326, + "wind_speed": 8.001451286046963, + "pressure": 1018.6511752952855, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-24T00:00:00", + "temperature": -3.8, + "precipitation": 1.9762281950597504, + "humidity": 39.611724053890086, + "wind_speed": 15.57675766820627, + "pressure": 1007.0801296708245, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-25T00:00:00", + "temperature": 5.1, + "precipitation": 0.05738951673934508, + "humidity": 77.35782660076157, + "wind_speed": 6.692254903674644, + "pressure": 1012.6502478985539, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-26T00:00:00", + "temperature": -0.8, + "precipitation": 0.6380403955502962, + "humidity": 61.70698652414996, + "wind_speed": 11.03541021196321, + "pressure": 1001.8134500339243, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-09-27T00:00:00", + "temperature": 3.4, + "precipitation": 0.12098734449566631, + "humidity": 41.70317821678751, + "wind_speed": 16.780176617500302, + "pressure": 1002.2503563301866, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-28T00:00:00", + "temperature": 10.9, + "precipitation": 0.9822629573988392, + "humidity": 44.29595552871935, + "wind_speed": 13.581829042903768, + "pressure": 1012.4108641744277, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-09-29T00:00:00", + "temperature": 7.0, + "precipitation": 0.2919235216282653, + "humidity": 60.57700794632834, + "wind_speed": 11.678274223839928, + "pressure": 1014.9662224218683, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-09-30T00:00:00", + "temperature": -0.8, + "precipitation": 1.3948245825221242, + "humidity": 49.66399932258817, + "wind_speed": 13.101726801941414, + "pressure": 1009.4041828497454, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-01T00:00:00", + "temperature": 3.9, + "precipitation": 1.5457502142082717, + "humidity": 46.19477363762162, + "wind_speed": 20.875209561307194, + "pressure": 1009.3156010452024, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-02T00:00:00", + "temperature": 2.7, + "precipitation": 1.4180942403786958, + "humidity": 77.22178423160695, + "wind_speed": 6.156511902721213, + "pressure": 1020.1278163837845, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-03T00:00:00", + "temperature": 8.8, + "precipitation": 0.28073970855701014, + "humidity": 55.611358821449805, + "wind_speed": 8.923249661903528, + "pressure": 1020.7339134409787, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-04T00:00:00", + "temperature": 2.8, + "precipitation": 0.6551645265248072, + "humidity": 37.799678635445964, + "wind_speed": 19.04178076824263, + "pressure": 1015.2461065334724, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-05T00:00:00", + "temperature": -3.8, + "precipitation": 0.5518319243691621, + "humidity": 36.04225127215574, + "wind_speed": 19.01747647807756, + "pressure": 1008.3634485198168, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-06T00:00:00", + "temperature": -2.6, + "precipitation": 0.8661673081762268, + "humidity": 43.60507410132694, + "wind_speed": 8.481581278931047, + "pressure": 1007.5075983758541, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-07T00:00:00", + "temperature": -0.7, + "precipitation": 1.312913254433109, + "humidity": 66.43647130007673, + "wind_speed": 17.64190980168266, + "pressure": 1020.3497571882535, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-08T00:00:00", + "temperature": 4.8, + "precipitation": 0.5481871775089653, + "humidity": 43.12589971611252, + "wind_speed": 22.106482063687977, + "pressure": 1020.803676824475, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-09T00:00:00", + "temperature": 7.2, + "precipitation": 0.6505365569909423, + "humidity": 38.78039663032371, + "wind_speed": 22.87402007540078, + "pressure": 1016.8333303086347, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-10T00:00:00", + "temperature": 4.0, + "precipitation": 2.688649108651277, + "humidity": 66.36312134333059, + "wind_speed": 19.603472407623414, + "pressure": 1012.1753479201086, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-11T00:00:00", + "temperature": 2.0, + "precipitation": 0.3492578612221071, + "humidity": 53.78543638715997, + "wind_speed": 21.6081940940798, + "pressure": 1000.6073610368906, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-12T00:00:00", + "temperature": -1.6, + "precipitation": 0.5954812115177235, + "humidity": 56.768861157106, + "wind_speed": 19.816161421215163, + "pressure": 1007.8470944725352, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-13T00:00:00", + "temperature": 0.7, + "precipitation": 0.9032808884753917, + "humidity": 69.37194106155508, + "wind_speed": 12.284742659764907, + "pressure": 1005.775431758908, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-14T00:00:00", + "temperature": -6.6, + "precipitation": 1.1810987582137764, + "humidity": 39.989367043278676, + "wind_speed": 14.791482200791243, + "pressure": 1016.7007994057931, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-15T00:00:00", + "temperature": -2.4, + "precipitation": 0.24387086455937626, + "humidity": 48.68402757438993, + "wind_speed": 12.232090895891169, + "pressure": 1009.6955574480312, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-16T00:00:00", + "temperature": -5.3, + "precipitation": 1.1563227558745508, + "humidity": 35.85770728977627, + "wind_speed": 14.600619993902932, + "pressure": 1009.5155873912353, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-17T00:00:00", + "temperature": 8.6, + "precipitation": 0.011829021392617158, + "humidity": 42.420850903695175, + "wind_speed": 19.8095066363588, + "pressure": 1001.9203461945458, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-18T00:00:00", + "temperature": -7.8, + "precipitation": 0.6242930417326978, + "humidity": 31.483813361569965, + "wind_speed": 19.623631815854353, + "pressure": 1021.8163033053052, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-19T00:00:00", + "temperature": 4.5, + "precipitation": 0.7158260044939169, + "humidity": 34.130744692839855, + "wind_speed": 10.262763403499928, + "pressure": 1002.0961662372572, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-20T00:00:00", + "temperature": 1.1, + "precipitation": 0.16717501539957766, + "humidity": 60.88086840621368, + "wind_speed": 14.625645103203382, + "pressure": 1024.356947617343, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-21T00:00:00", + "temperature": 1.7, + "precipitation": 2.572578310170702, + "humidity": 33.26948158590279, + "wind_speed": 16.089657422600833, + "pressure": 1007.0893442957065, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-22T00:00:00", + "temperature": 5.2, + "precipitation": 0.4597212532351751, + "humidity": 52.67711685172472, + "wind_speed": 14.028023942273375, + "pressure": 1011.1795107112728, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-23T00:00:00", + "temperature": 2.1, + "precipitation": 0.5405174968711876, + "humidity": 47.37514267696042, + "wind_speed": 22.082680457409385, + "pressure": 1006.2557121414955, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-24T00:00:00", + "temperature": 7.0, + "precipitation": 0.24898414974507296, + "humidity": 59.33491166935061, + "wind_speed": 23.63388713558311, + "pressure": 1000.0270893024929, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-25T00:00:00", + "temperature": -0.4, + "precipitation": 0.6488213544832594, + "humidity": 55.38544862166285, + "wind_speed": 7.164077087133549, + "pressure": 1007.6476726131909, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-26T00:00:00", + "temperature": 9.4, + "precipitation": 0.18711400021882904, + "humidity": 36.400137636022286, + "wind_speed": 6.329564055247403, + "pressure": 1004.883032885318, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-27T00:00:00", + "temperature": 4.1, + "precipitation": 0.2268001976602032, + "humidity": 78.12152003129573, + "wind_speed": 15.599374978303846, + "pressure": 1022.7356612159022, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-28T00:00:00", + "temperature": -1.9, + "precipitation": 0.07309662311347191, + "humidity": 31.739001867101138, + "wind_speed": 14.771628540986912, + "pressure": 1015.1651658638206, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-10-29T00:00:00", + "temperature": -2.8, + "precipitation": 0.43867658290187994, + "humidity": 78.77199158769707, + "wind_speed": 24.909746751064734, + "pressure": 1013.195782199328, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-10-30T00:00:00", + "temperature": -3.9, + "precipitation": 2.860433366435249, + "humidity": 31.60425041157149, + "wind_speed": 20.274120485506298, + "pressure": 1014.4640933423002, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-10-31T00:00:00", + "temperature": 3.9, + "precipitation": 2.4422499843124044, + "humidity": 56.779693231749384, + "wind_speed": 17.6484302316495, + "pressure": 1001.038175141741, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-01T00:00:00", + "temperature": -6.5, + "precipitation": 1.2311220502835505, + "humidity": 75.79773113240856, + "wind_speed": 7.492464630654672, + "pressure": 1024.6546403711022, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-02T00:00:00", + "temperature": 2.3, + "precipitation": 0.49129898544985495, + "humidity": 54.85996111033259, + "wind_speed": 23.09513730452512, + "pressure": 1023.2615875358028, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-03T00:00:00", + "temperature": 3.1, + "precipitation": 0.5450649312301181, + "humidity": 63.59671057066549, + "wind_speed": 5.699584185378164, + "pressure": 1024.554180053189, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-04T00:00:00", + "temperature": 3.5, + "precipitation": 0.7126896579239126, + "humidity": 73.1991118748287, + "wind_speed": 9.388490295850751, + "pressure": 1012.0262102204933, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-05T00:00:00", + "temperature": 5.5, + "precipitation": 1.5899784873300027, + "humidity": 35.516131334076995, + "wind_speed": 15.947384493286476, + "pressure": 1022.8229167186242, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-06T00:00:00", + "temperature": 1.3, + "precipitation": 0.7747915012945169, + "humidity": 60.96040839155209, + "wind_speed": 23.656469278918838, + "pressure": 1010.8930654669568, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-07T00:00:00", + "temperature": 4.3, + "precipitation": 1.6610331984304285, + "humidity": 30.48375832021592, + "wind_speed": 24.867828573722235, + "pressure": 1005.2504558024249, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-08T00:00:00", + "temperature": -0.9, + "precipitation": 0.2602090184020369, + "humidity": 54.52041938560757, + "wind_speed": 13.228361553863575, + "pressure": 1021.0947286913952, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-09T00:00:00", + "temperature": 8.7, + "precipitation": 0.1574190301521281, + "humidity": 47.948276649934165, + "wind_speed": 6.972864642064842, + "pressure": 1005.0655467363713, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-10T00:00:00", + "temperature": 12.9, + "precipitation": 1.9973114633600182, + "humidity": 50.46087341207901, + "wind_speed": 8.704606602191749, + "pressure": 1012.9334212311516, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-11T00:00:00", + "temperature": -0.8, + "precipitation": 0.7542755913882029, + "humidity": 55.313850298420135, + "wind_speed": 18.333998808285564, + "pressure": 1011.3581699047035, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-12T00:00:00", + "temperature": -0.2, + "precipitation": 0.2232120915720585, + "humidity": 56.00976798557663, + "wind_speed": 22.195252572108085, + "pressure": 1013.861888910381, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-13T00:00:00", + "temperature": -1.4, + "precipitation": 0.4064483975575881, + "humidity": 53.69453139941834, + "wind_speed": 22.88278559853349, + "pressure": 1022.6096667451567, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-14T00:00:00", + "temperature": 7.7, + "precipitation": 1.0222760822677606, + "humidity": 36.96911238292416, + "wind_speed": 11.710817923585841, + "pressure": 1002.7222899362764, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-15T00:00:00", + "temperature": 14.4, + "precipitation": 1.2439284254799898, + "humidity": 65.331526550364, + "wind_speed": 12.920557456223747, + "pressure": 1011.8163460142495, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-16T00:00:00", + "temperature": 11.1, + "precipitation": 0.5412835889306766, + "humidity": 74.07246618181787, + "wind_speed": 15.481146234610268, + "pressure": 1003.5226818052428, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-17T00:00:00", + "temperature": 6.5, + "precipitation": 0.18655017887324532, + "humidity": 79.70409143690613, + "wind_speed": 5.091898513372812, + "pressure": 1010.3591475422256, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-18T00:00:00", + "temperature": 5.5, + "precipitation": 0.4171784229281938, + "humidity": 32.6642715068504, + "wind_speed": 21.945156423386592, + "pressure": 1018.8777645967158, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-19T00:00:00", + "temperature": 2.6, + "precipitation": 1.094542531594838, + "humidity": 39.48499768970514, + "wind_speed": 5.194532978028763, + "pressure": 1020.8446495170518, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-20T00:00:00", + "temperature": 12.3, + "precipitation": 1.0552500857984284, + "humidity": 56.899008202086094, + "wind_speed": 13.297146753887008, + "pressure": 1006.9524747637357, + "description": "Soleado", + "source": "aemet_test" + }, + { + "date": "2023-11-21T00:00:00", + "temperature": 10.6, + "precipitation": 1.340412723401404, + "humidity": 59.020789350393585, + "wind_speed": 11.74530287146049, + "pressure": 1014.2605535341573, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-22T00:00:00", + "temperature": -2.5, + "precipitation": 3.510480950133899, + "humidity": 32.74316521887547, + "wind_speed": 10.126960429520437, + "pressure": 1015.6942917602148, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-23T00:00:00", + "temperature": 13.3, + "precipitation": 1.3835788261677058, + "humidity": 66.92810959255652, + "wind_speed": 13.673155999991746, + "pressure": 1004.0316014717635, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-24T00:00:00", + "temperature": 7.3, + "precipitation": 2.0225988195209026, + "humidity": 54.124235269196035, + "wind_speed": 10.595744644898968, + "pressure": 1007.6048582368207, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-25T00:00:00", + "temperature": 13.2, + "precipitation": 0.1333143710136748, + "humidity": 63.82236791450961, + "wind_speed": 12.913114825394628, + "pressure": 1004.340550931344, + "description": "Nuboso", + "source": "aemet_test" + }, + { + "date": "2023-11-26T00:00:00", + "temperature": 7.4, + "precipitation": 0.6190723192164542, + "humidity": 70.9824321191063, + "wind_speed": 10.253458888634611, + "pressure": 1001.7193784244129, + "description": "Lluvioso", + "source": "aemet_test" + }, + { + "date": "2023-11-27T00:00:00", + "temperature": 13.6, + "precipitation": 0.8781650508292259, + "humidity": 30.82816090512288, + "wind_speed": 17.929581467272914, + "pressure": 1004.4854032082834, + "description": "Nuboso", + "source": "aemet_test" + } +] \ No newline at end of file diff --git a/services/training/tests/results/coverage_end_to_end.xml b/services/training/tests/results/coverage_end_to_end.xml new file mode 100644 index 00000000..efc0fce2 --- /dev/null +++ b/services/training/tests/results/coverage_end_to_end.xml @@ -0,0 +1,1670 @@ + + + + + + /app/app + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/training/tests/results/coverage_integration.xml b/services/training/tests/results/coverage_integration.xml new file mode 100644 index 00000000..50459467 --- /dev/null +++ b/services/training/tests/results/coverage_integration.xml @@ -0,0 +1,1670 @@ + + + + + + /app/app + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/training/tests/results/coverage_performance.xml b/services/training/tests/results/coverage_performance.xml new file mode 100644 index 00000000..23f7bfff --- /dev/null +++ b/services/training/tests/results/coverage_performance.xml @@ -0,0 +1,1670 @@ + + + + + + /app/app + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/training/tests/results/coverage_unit.xml b/services/training/tests/results/coverage_unit.xml new file mode 100644 index 00000000..75018877 --- /dev/null +++ b/services/training/tests/results/coverage_unit.xml @@ -0,0 +1,1670 @@ + + + + + + /app/app + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/training/tests/results/junit_end_to_end.xml b/services/training/tests/results/junit_end_to_end.xml new file mode 100644 index 00000000..b9859adf --- /dev/null +++ b/services/training/tests/results/junit_end_to_end.xml @@ -0,0 +1,5 @@ +tests/test_end_to_end.py:75: in real_bakery_data + temp = 15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) +E UnboundLocalError: cannot access local variable 'np' where it is not associated with a valuetests/conftest.py:464: in setup_test_environment + os.environ.pop(var, None)(scope="session") +E TypeError: 'str' object is not callable \ No newline at end of file diff --git a/services/training/tests/results/junit_integration.xml b/services/training/tests/results/junit_integration.xml new file mode 100644 index 00000000..f3741a14 --- /dev/null +++ b/services/training/tests/results/junit_integration.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/services/training/tests/results/junit_performance.xml b/services/training/tests/results/junit_performance.xml new file mode 100644 index 00000000..39f7291f --- /dev/null +++ b/services/training/tests/results/junit_performance.xml @@ -0,0 +1,8 @@ +ImportError while importing test module '/app/tests/test_performance.py'. +Hint: make sure your test modules/packages have valid Python names. +Traceback: +/usr/local/lib/python3.11/importlib/__init__.py:126: in import_module + return _bootstrap._gcd_import(name[level:], package, level) +tests/test_performance.py:16: in <module> + import psutil +E ModuleNotFoundError: No module named 'psutil' \ No newline at end of file diff --git a/services/training/tests/results/junit_unit.xml b/services/training/tests/results/junit_unit.xml new file mode 100644 index 00000000..7e64b266 --- /dev/null +++ b/services/training/tests/results/junit_unit.xml @@ -0,0 +1,649 @@ +tests/test_api.py:20: in test_health_check + response = await test_client.get("/health") +E AttributeError: 'async_generator' object has no attribute 'get'tests/test_api.py:32: in test_readiness_check_ready + with patch('app.main.app.state.ready', True): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <starlette.datastructures.State object at 0xffff5ae06a10> does not have the attribute 'ready'tests/test_api.py:42: in test_readiness_check_not_ready + with patch('app.main.app.state.ready', False): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <starlette.datastructures.State object at 0xffff5ae06a10> does not have the attribute 'ready'tests/test_api.py:53: in test_liveness_check_healthy + response = await test_client.get("/health/live") +E AttributeError: 'async_generator' object has no attribute 'get'tests/test_api.py:63: in test_liveness_check_unhealthy + response = await test_client.get("/health/live") +E AttributeError: 'async_generator' object has no attribute 'get'tests/test_api.py:73: in test_metrics_endpoint + response = await test_client.get("/metrics") +E AttributeError: 'async_generator' object has no attribute 'get'tests/test_api.py:92: in test_root_endpoint + response = await test_client.get("/") +E AttributeError: 'async_generator' object has no attribute 'get'file /app/tests/test_api.py, line 104 + @pytest.mark.asyncio + async def test_start_training_job_success( + self, + test_client: AsyncClient, + mock_messaging, + mock_ml_trainer, + mock_data_service + ): + """Test starting a training job successfully""" + request_data = { + "include_weather": True, + "include_traffic": True, + "min_data_points": 30, + "seasonality_mode": "additive" + } + + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.post("/training/jobs", json=request_data) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert "job_id" in data + assert data["status"] == "started" + assert data["tenant_id"] == "test-tenant" + assert "estimated_duration_minutes" in data +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:104tests/test_api.py:139: in test_start_training_job_validation_error + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/test_api.py:167: in test_get_training_status_nonexistent_job + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/test_api.py:233: in test_cancel_nonexistent_job + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogfile /app/tests/test_api.py, line 257 + @pytest.mark.asyncio + async def test_validate_training_data_valid( + self, + test_client: AsyncClient, + mock_data_service + ): + """Test validating valid training data""" + request_data = { + "include_weather": True, + "include_traffic": True, + "min_data_points": 30 + } + + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.post("/training/validate", json=request_data) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert "is_valid" in data + assert "issues" in data + assert "recommendations" in data + assert "estimated_training_time" in data +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:257file /app/tests/test_api.py, line 285 + @pytest.mark.asyncio + async def test_train_single_product_success( + self, + test_client: AsyncClient, + mock_messaging, + mock_ml_trainer, + mock_data_service + ): + """Test training a single product successfully""" + product_name = "Pan Integral" + request_data = { + "include_weather": True, + "include_traffic": True, + "seasonality_mode": "additive" + } + + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.post( + f"/training/products/{product_name}", + json=request_data + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert "job_id" in data + assert data["status"] == "started" + assert data["tenant_id"] == "test-tenant" + assert f"training started for {product_name}" in data["message"].lower() +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:285tests/test_api.py:323: in test_train_single_product_validation_error + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'file /app/tests/test_api.py, line 331 + @pytest.mark.asyncio + async def test_train_single_product_special_characters( + self, + test_client: AsyncClient, + mock_messaging, + mock_ml_trainer, + mock_data_service + ): + """Test training product with special characters in name""" + product_name = "Pan FrancΓ©s" # With accent + request_data = { + "include_weather": True, + "seasonality_mode": "additive" + } + + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.post( + f"/training/products/{product_name}", + json=request_data + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + assert "job_id" in data +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:331file /app/tests/test_api.py, line 360 + @pytest.mark.asyncio + async def test_list_models( + self, + test_client: AsyncClient, + trained_model_in_db + ): + """Test listing trained models""" + with patch('app.api.models.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.get("/models") + + # This endpoint might not exist yet, so we expect either 200 or 404 + assert response.status_code in [status.HTTP_200_OK, status.HTTP_404_NOT_FOUND] + + if response.status_code == status.HTTP_200_OK: + data = response.json() + assert isinstance(data, list) +E fixture 'trained_model_in_db' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:360file /app/tests/test_api.py, line 377 + @pytest.mark.asyncio + async def test_get_model_details( + self, + test_client: AsyncClient, + trained_model_in_db + ): + """Test getting model details""" + model_id = trained_model_in_db.model_id + + with patch('app.api.models.get_current_tenant_id', return_value="test-tenant"): + response = await test_client.get(f"/models/{model_id}") + + # This endpoint might not exist yet + assert response.status_code in [ + status.HTTP_200_OK, + status.HTTP_404_NOT_FOUND, + status.HTTP_501_NOT_IMPLEMENTED + ] +E fixture 'trained_model_in_db' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:377tests/test_api.py:412: in test_database_error_handling + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:427: in test_missing_tenant_id + response = await test_client.post("/training/jobs", json=request_data) +E AttributeError: 'async_generator' object has no attribute 'post'tests/test_api.py:437: in test_invalid_job_id_format + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'file /app/tests/test_api.py, line 443 + @pytest.mark.asyncio + async def test_messaging_failure_handling( + self, + test_client: AsyncClient, + mock_data_service + ): + """Test handling when messaging fails""" + request_data = { + "include_weather": True, + "include_traffic": True, + "min_data_points": 30 + } + + with patch('app.services.messaging.publish_job_started', side_effect=Exception("Messaging failed")), \ + patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): + + response = await test_client.post("/training/jobs", json=request_data) + + # Should still succeed even if messaging fails + assert response.status_code == status.HTTP_200_OK + data = response.json() + assert "job_id" in data +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_api.py:443tests/test_api.py:469: in test_invalid_json_payload + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:481: in test_unsupported_content_type + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:512: in test_endpoints_require_auth + response = await test_client.post(endpoint, json={}) +E AttributeError: 'async_generator' object has no attribute 'post'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/test_api.py:555: in test_training_request_validation + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:591: in test_single_product_request_validation + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:612: in test_query_parameter_validation + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:643: in test_concurrent_requests + with patch('app.api.training.get_current_tenant_id', return_value=f"tenant-{i}"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:665: in test_large_payload_handling + with patch('app.api.training.get_current_tenant_id', return_value="test-tenant"): +/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__ + original, local = self.get_original() +/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original + raise AttributeError( +E AttributeError: <module 'app.api.training' from '/app/app/api/training.py'> does not have the attribute 'get_current_tenant_id'tests/test_api.py:681: in test_rapid_successive_requests + response = await test_client.get("/health") +E AttributeError: 'async_generator' object has no attribute 'get'tests/test_ml.py:201: in test_prepare_training_data_insufficient_data + with pytest.raises(Exception): +E Failed: DID NOT RAISE <class 'Exception'>tests/test_ml.py:239: in test_train_bakery_model_success + result = await prophet_manager.train_bakery_model( +app/ml/prophet_manager.py:70: in train_bakery_model + model = self._create_prophet_model(regressor_columns) +app/ml/prophet_manager.py:238: in _create_prophet_model + daily_seasonality=settings.PROPHET_DAILY_SEASONALITY, +/usr/local/lib/python3.11/site-packages/pydantic/main.py:761: in __getattr__ + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') +E AttributeError: 'TrainingSettings' object has no attribute 'PROPHET_DAILY_SEASONALITY'tests/test_ml.py:414: in test_train_single_product_success + result = await ml_trainer.train_single_product( +app/ml/trainer.py:149: in train_single_product + model_info = await self.prophet_manager.train_bakery_model( +app/ml/prophet_manager.py:61: in train_bakery_model + await self._validate_training_data(df, product_name) +app/ml/prophet_manager.py:158: in _validate_training_data + raise ValueError( +E ValueError: Insufficient training data for Pan Integral: 3 days, minimum required: 30tests/test_ml.py:438: in test_train_single_product_no_data + await ml_trainer.train_single_product( +app/ml/trainer.py:134: in train_single_product + product_sales = sales_df[sales_df['product_name'] == product_name].copy() +/usr/local/lib/python3.11/site-packages/pandas/core/frame.py:3893: in __getitem__ + indexer = self.columns.get_loc(key) +/usr/local/lib/python3.11/site-packages/pandas/core/indexes/range.py:418: in get_loc + raise KeyError(key) +E KeyError: 'product_name'/app/tests/test_ml.py:508: Requires actual Prophet dependencies for integration test/app/tests/test_ml.py:513: Requires actual dependencies for integration testapp/services/training_service.py:52: in create_training_job + db.add(training_log) +E AttributeError: 'coroutine' object has no attribute 'add' + +During handling of the above exception, another exception occurred: +tests/test_service.py:34: in test_create_training_job_success + result = await training_service.create_training_job( +app/services/training_service.py:61: in create_training_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'app/services/training_service.py:84: in create_single_product_job + db.add(training_log) +E AttributeError: 'coroutine' object has no attribute 'add' + +During handling of the above exception, another exception occurred: +tests/test_service.py:60: in test_create_single_product_job_success + result = await training_service.create_single_product_job( +app/services/training_service.py:93: in create_single_product_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogtests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogapp/services/training_service.py:270: in cancel_training_job + result = await db.execute( +E AttributeError: 'coroutine' object has no attribute 'execute' + +During handling of the above exception, another exception occurred: +tests/test_service.py:175: in test_cancel_nonexistent_job + result = await training_service.cancel_training_job( +app/services/training_service.py:297: in cancel_training_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'file /app/tests/test_service.py, line 183 + @pytest.mark.asyncio + async def test_validate_training_data_valid( + self, + training_service, + test_db_session, + mock_data_service + ): + """Test validation with valid data""" + config = {"min_data_points": 30} + + result = await training_service.validate_training_data( + db=test_db_session, + tenant_id="test-tenant", + config=config + ) + + assert isinstance(result, dict) + assert "is_valid" in result + assert "issues" in result + assert "recommendations" in result + assert "estimated_time_minutes" in result +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_service.py:183tests/test_service.py:221: in test_validate_training_data_no_data + assert result["is_valid"] is False +E assert True is Falsetests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogapp/services/training_service.py:572: in _store_trained_models + await db.execute( +E AttributeError: 'coroutine' object has no attribute 'execute' + +During handling of the above exception, another exception occurred: +tests/test_service.py:280: in test_store_trained_models + await training_service._store_trained_models( +app/services/training_service.py:592: in _store_trained_models + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'tests/conftest.py:539: in training_job_in_db + job = ModelTrainingLog( +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance + with util.safe_reraise(): +/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__ + raise exc_value.with_traceback(exc_tb) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance + manager.original_init(*mixed[1:], **kwargs) +/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor + raise TypeError( +E TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLogfile /app/tests/test_service.py, line 468 + @pytest.mark.asyncio + async def test_execute_training_job_success( + self, + training_service, + test_db_session, + mock_messaging, + mock_data_service + ): + """Test successful training job execution""" + # Create job first + job_id = "test-execution-job" + training_log = await training_service.create_training_job( + db=test_db_session, + tenant_id="test-tenant", + job_id=job_id, + config={"include_weather": True} + ) + + request = TrainingJobRequest( + include_weather=True, + include_traffic=True, + min_data_points=30 + ) + + with patch('app.services.training_service.TrainingService._fetch_sales_data') as mock_fetch_sales, \ + patch('app.services.training_service.TrainingService._fetch_weather_data') as mock_fetch_weather, \ + patch('app.services.training_service.TrainingService._fetch_traffic_data') as mock_fetch_traffic, \ + patch('app.services.training_service.TrainingService._store_trained_models') as mock_store: + + mock_fetch_sales.return_value = [{"date": "2024-01-01", "product_name": "Pan Integral", "quantity": 45}] + mock_fetch_weather.return_value = [] + mock_fetch_traffic.return_value = [] + mock_store.return_value = None + + await training_service.execute_training_job( + db=test_db_session, + job_id=job_id, + tenant_id="test-tenant", + request=request + ) + + # Verify job was completed + updated_job = await training_service.get_job_status( + db=test_db_session, + job_id=job_id, + tenant_id="test-tenant" + ) + + assert updated_job.status == "completed" + assert updated_job.progress == 100 +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_service.py:468app/services/training_service.py:52: in create_training_job + db.add(training_log) +E AttributeError: 'coroutine' object has no attribute 'add' + +During handling of the above exception, another exception occurred: +tests/test_service.py:529: in test_execute_training_job_failure + await training_service.create_training_job( +app/services/training_service.py:61: in create_training_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'file /app/tests/test_service.py, line 559 + @pytest.mark.asyncio + async def test_execute_single_product_training_success( + self, + training_service, + test_db_session, + mock_messaging, + mock_data_service + ): + """Test successful single product training execution""" + job_id = "test-single-product-job" + product_name = "Pan Integral" + + await training_service.create_single_product_job( + db=test_db_session, + tenant_id="test-tenant", + product_name=product_name, + job_id=job_id, + config={} + ) + + request = SingleProductTrainingRequest( + include_weather=True, + include_traffic=False + ) + + with patch('app.services.training_service.TrainingService._fetch_product_sales_data') as mock_fetch_sales, \ + patch('app.services.training_service.TrainingService._fetch_weather_data') as mock_fetch_weather, \ + patch('app.services.training_service.TrainingService._store_single_trained_model') as mock_store: + + mock_fetch_sales.return_value = [{"date": "2024-01-01", "product_name": product_name, "quantity": 45}] + mock_fetch_weather.return_value = [] + mock_store.return_value = None + + await training_service.execute_single_product_training( + db=test_db_session, + job_id=job_id, + tenant_id="test-tenant", + product_name=product_name, + request=request + ) + + # Verify job was completed + updated_job = await training_service.get_job_status( + db=test_db_session, + job_id=job_id, + tenant_id="test-tenant" + ) + + assert updated_job.status == "completed" + assert updated_job.progress == 100 +E fixture 'mock_data_service' not found +> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory +> use 'pytest --fixtures [testpath]' for help on them. + +/app/tests/test_service.py:559app/services/training_service.py:52: in create_training_job + db.add(training_log) +E AttributeError: 'coroutine' object has no attribute 'add' + +During handling of the above exception, another exception occurred: +tests/test_service.py:660: in test_concurrent_job_creation + job = await training_service.create_training_job( +app/services/training_service.py:61: in create_training_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'app/services/training_service.py:52: in create_training_job + db.add(training_log) +E AttributeError: 'coroutine' object has no attribute 'add' + +During handling of the above exception, another exception occurred: +tests/test_service.py:681: in test_malformed_config_handling + job = await training_service.create_training_job( +app/services/training_service.py:61: in create_training_job + await db.rollback() +E AttributeError: 'coroutine' object has no attribute 'rollback'tests/conftest.py:464: in setup_test_environment + os.environ.pop(var, None)(scope="session") +E TypeError: 'str' object is not callable \ No newline at end of file diff --git a/services/training/tests/results/test_report.json b/services/training/tests/results/test_report.json new file mode 100644 index 00000000..9e1e8427 --- /dev/null +++ b/services/training/tests/results/test_report.json @@ -0,0 +1,53 @@ +{ + "test_run_summary": { + "timestamp": "2025-07-25T11:22:46.885733", + "total_suites": 4, + "passed_suites": 0, + "failed_suites": 4, + "error_suites": 0, + "timeout_suites": 0, + "success_rate": 0.0, + "total_duration_seconds": 9.403800010681152 + }, + "suite_results": { + "unit": { + "suite": "unit", + "status": "failed", + "return_code": 1, + "duration": 6.222464323043823, + "stdout": "\n================================================================================\nTRAINING SERVICE TEST SESSION STARTING\n================================================================================\n============================= test session starts ==============================\nplatform linux -- Python 3.11.13, pytest-7.4.3, pluggy-1.6.0 -- /usr/local/bin/python\ncachedir: .pytest_cache\nrootdir: /app\nplugins: anyio-3.7.1, mock-3.12.0, asyncio-0.21.1, cov-4.1.0\nasyncio: mode=Mode.STRICT\ncollecting ... collected 83 items\n\ntests/test_api.py::TestTrainingAPI::test_health_check FAILED\ntests/test_api.py::TestTrainingAPI::test_readiness_check_ready 2025-07-25 11:22:39 [INFO] shared.monitoring.logging: Logging configured for training-service at level INFO\nFAILED\ntests/test_api.py::TestTrainingAPI::test_readiness_check_not_ready FAILED\ntests/test_api.py::TestTrainingAPI::test_liveness_check_healthy FAILED\ntests/test_api.py::TestTrainingAPI::test_liveness_check_unhealthy FAILED\ntests/test_api.py::TestTrainingAPI::test_metrics_endpoint FAILED\ntests/test_api.py::TestTrainingAPI::test_root_endpoint FAILED\ntests/test_api.py::TestTrainingJobsAPI::test_start_training_job_success ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_start_training_job_validation_error FAILED\ntests/test_api.py::TestTrainingJobsAPI::test_get_training_status_existing_job ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_get_training_status_nonexistent_job FAILED\ntests/test_api.py::TestTrainingJobsAPI::test_list_training_jobs ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_list_training_jobs_with_status_filter ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_cancel_training_job_success ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_cancel_nonexistent_job FAILED\ntests/test_api.py::TestTrainingJobsAPI::test_get_training_logs ERROR\ntests/test_api.py::TestTrainingJobsAPI::test_validate_training_data_valid ERROR\ntests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_success ERROR\ntests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_validation_error FAILED\ntests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_special_characters ERROR\ntests/test_api.py::TestModelsAPI::test_list_models ERROR\ntests/test_api.py::TestModelsAPI::test_get_model_details ERROR\ntests/test_api.py::TestErrorHandling::test_database_error_handling FAILED\ntests/test_api.py::TestErrorHandling::test_missing_tenant_id FAILED\ntests/test_api.py::TestErrorHandling::test_invalid_job_id_format FAILED\ntests/test_api.py::TestErrorHandling::test_messaging_failure_handling ERROR\ntests/test_api.py::TestErrorHandling::test_invalid_json_payload FAILED\ntests/test_api.py::TestErrorHandling::test_unsupported_content_type FAILED\ntests/test_api.py::TestAuthenticationIntegration::test_endpoints_require_auth FAILED\ntests/test_api.py::TestAuthenticationIntegration::test_tenant_isolation_in_api ERROR\ntests/test_api.py::TestAPIValidation::test_training_request_validation FAILED\ntests/test_api.py::TestAPIValidation::test_single_product_request_validation FAILED\ntests/test_api.py::TestAPIValidation::test_query_parameter_validation FAILED\ntests/test_api.py::TestAPIPerformance::test_concurrent_requests FAILED\ntests/test_api.py::TestAPIPerformance::test_large_payload_handling FAILED\ntests/test_api.py::TestAPIPerformance::test_rapid_successive_requests FAILED\ntests/test_ml.py::TestBakeryDataProcessor::test_prepare_training_data_basic PASSED\ntests/test_ml.py::TestBakeryDataProcessor::test_prepare_training_data_empty_weather PASSED\ntests/test_ml.py::TestBakeryDataProcessor::test_prepare_prediction_features PASSED\ntests/test_ml.py::TestBakeryDataProcessor::test_add_temporal_features PASSED\ntests/test_ml.py::TestBakeryDataProcessor::test_spanish_holiday_detection PASSED\ntests/test_ml.py::TestBakeryDataProcessor::test_prepare_training_data_insufficient_data FAILED\ntests/test_ml.py::TestBakeryProphetManager::test_train_bakery_model_success FAILED\ntests/test_ml.py::TestBakeryProphetManager::test_validate_training_data_valid PASSED\ntests/test_ml.py::TestBakeryProphetManager::test_validate_training_data_insufficient PASSED\ntests/test_ml.py::TestBakeryProphetManager::test_validate_training_data_missing_columns PASSED\ntests/test_ml.py::TestBakeryProphetManager::test_get_spanish_holidays PASSED\ntests/test_ml.py::TestBakeryProphetManager::test_extract_regressor_columns PASSED\ntests/test_ml.py::TestBakeryProphetManager::test_generate_forecast PASSED\ntests/test_ml.py::TestBakeryMLTrainer::test_train_tenant_models_success PASSED\ntests/test_ml.py::TestBakeryMLTrainer::test_train_single_product_success FAILED\ntests/test_ml.py::TestBakeryMLTrainer::test_train_single_product_no_data FAILED\ntests/test_ml.py::TestBakeryMLTrainer::test_validate_input_data_valid PASSED\ntests/test_ml.py::TestBakeryMLTrainer::test_validate_input_data_empty PASSED\ntests/test_ml.py::TestBakeryMLTrainer::test_validate_input_data_missing_columns PASSED\ntests/test_ml.py::TestBakeryMLTrainer::test_calculate_training_summary PASSED\ntests/test_ml.py::TestIntegrationML::test_end_to_end_training_flow SKIPPED\ntests/test_ml.py::TestIntegrationML::test_data_pipeline_integration SKIPPED\ntests/test_service.py::TestTrainingService::test_create_training_job_success FAILED\ntests/test_service.py::TestTrainingService::test_create_single_product_job_success FAILED\ntests/test_service.py::TestTrainingService::test_get_job_status_existing ERROR\ntests/test_service.py::TestTrainingService::test_get_job_status_nonexistent PASSED\ntests/test_service.py::TestTrainingService::test_list_training_jobs ERROR\ntests/test_service.py::TestTrainingService::test_list_training_jobs_with_filter ERROR\ntests/test_service.py::TestTrainingService::test_cancel_training_job_success ERROR\ntests/test_service.py::TestTrainingService::test_cancel_nonexistent_job FAILED\ntests/test_service.py::TestTrainingService::test_validate_training_data_valid ERROR\ntests/test_service.py::TestTrainingService::test_validate_training_data_no_data FAILED\ntests/test_service.py::TestTrainingService::test_update_job_status ERROR\ntests/test_service.py::TestTrainingService::test_store_trained_models FAILED\ntests/test_service.py::TestTrainingService::test_get_training_logs ERROR\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_sales_data_success PASSED\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_sales_data_error PASSED\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_weather_data_success PASSED\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_traffic_data_success PASSED\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_data_with_date_filters PASSED\ntests/test_service.py::TestTrainingServiceExecution::test_execute_training_job_success ERROR\ntests/test_service.py::TestTrainingServiceExecution::test_execute_training_job_failure FAILED\ntests/test_service.py::TestTrainingServiceExecution::test_execute_single_product_training_success ERROR\ntests/test_service.py::TestTrainingServiceEdgeCases::test_database_connection_failure PASSED\ntests/test_service.py::TestTrainingServiceEdgeCases::test_external_service_timeout PASSED\ntests/test_service.py::TestTrainingServiceEdgeCases::test_concurrent_job_creation FAILED\ntests/test_service.py::TestTrainingServiceEdgeCases::test_malformed_config_handling FAILED\ntests/test_service.py::TestTrainingServiceEdgeCases::test_malformed_config_handling ERROR\n================================================================================\nTRAINING SERVICE TEST SESSION FINISHED\nExit Status: 1\n================================================================================\n\n\n==================================== ERRORS ====================================\n____ ERROR at setup of TestTrainingJobsAPI.test_start_training_job_success _____\nfile /app/tests/test_api.py, line 104\n @pytest.mark.asyncio\n async def test_start_training_job_success(\n self,\n test_client: AsyncClient,\n mock_messaging,\n mock_ml_trainer,\n mock_data_service\n ):\n \"\"\"Test starting a training job successfully\"\"\"\n request_data = {\n \"include_weather\": True,\n \"include_traffic\": True,\n \"min_data_points\": 30,\n \"seasonality_mode\": \"additive\"\n }\n\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.post(\"/training/jobs\", json=request_data)\n\n assert response.status_code == status.HTTP_200_OK\n data = response.json()\n\n assert \"job_id\" in data\n assert data[\"status\"] == \"started\"\n assert data[\"tenant_id\"] == \"test-tenant\"\n assert \"estimated_duration_minutes\" in data\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:104\n_ ERROR at setup of TestTrainingJobsAPI.test_get_training_status_existing_job __\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n________ ERROR at setup of TestTrainingJobsAPI.test_list_training_jobs _________\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n_ ERROR at setup of TestTrainingJobsAPI.test_list_training_jobs_with_status_filter _\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n____ ERROR at setup of TestTrainingJobsAPI.test_cancel_training_job_success ____\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n_________ ERROR at setup of TestTrainingJobsAPI.test_get_training_logs _________\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n___ ERROR at setup of TestTrainingJobsAPI.test_validate_training_data_valid ____\nfile /app/tests/test_api.py, line 257\n @pytest.mark.asyncio\n async def test_validate_training_data_valid(\n self,\n test_client: AsyncClient,\n mock_data_service\n ):\n \"\"\"Test validating valid training data\"\"\"\n request_data = {\n \"include_weather\": True,\n \"include_traffic\": True,\n \"min_data_points\": 30\n }\n\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.post(\"/training/validate\", json=request_data)\n\n assert response.status_code == status.HTTP_200_OK\n data = response.json()\n\n assert \"is_valid\" in data\n assert \"issues\" in data\n assert \"recommendations\" in data\n assert \"estimated_training_time\" in data\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:257\n_ ERROR at setup of TestSingleProductTrainingAPI.test_train_single_product_success _\nfile /app/tests/test_api.py, line 285\n @pytest.mark.asyncio\n async def test_train_single_product_success(\n self,\n test_client: AsyncClient,\n mock_messaging,\n mock_ml_trainer,\n mock_data_service\n ):\n \"\"\"Test training a single product successfully\"\"\"\n product_name = \"Pan Integral\"\n request_data = {\n \"include_weather\": True,\n \"include_traffic\": True,\n \"seasonality_mode\": \"additive\"\n }\n\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.post(\n f\"/training/products/{product_name}\",\n json=request_data\n )\n\n assert response.status_code == status.HTTP_200_OK\n data = response.json()\n\n assert \"job_id\" in data\n assert data[\"status\"] == \"started\"\n assert data[\"tenant_id\"] == \"test-tenant\"\n assert f\"training started for {product_name}\" in data[\"message\"].lower()\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:285\n_ ERROR at setup of TestSingleProductTrainingAPI.test_train_single_product_special_characters _\nfile /app/tests/test_api.py, line 331\n @pytest.mark.asyncio\n async def test_train_single_product_special_characters(\n self,\n test_client: AsyncClient,\n mock_messaging,\n mock_ml_trainer,\n mock_data_service\n ):\n \"\"\"Test training product with special characters in name\"\"\"\n product_name = \"Pan Franc\u00e9s\" # With accent\n request_data = {\n \"include_weather\": True,\n \"seasonality_mode\": \"additive\"\n }\n\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.post(\n f\"/training/products/{product_name}\",\n json=request_data\n )\n\n assert response.status_code == status.HTTP_200_OK\n data = response.json()\n assert \"job_id\" in data\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:331\n_______________ ERROR at setup of TestModelsAPI.test_list_models _______________\nfile /app/tests/test_api.py, line 360\n @pytest.mark.asyncio\n async def test_list_models(\n self,\n test_client: AsyncClient,\n trained_model_in_db\n ):\n \"\"\"Test listing trained models\"\"\"\n with patch('app.api.models.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.get(\"/models\")\n\n # This endpoint might not exist yet, so we expect either 200 or 404\n assert response.status_code in [status.HTTP_200_OK, status.HTTP_404_NOT_FOUND]\n\n if response.status_code == status.HTTP_200_OK:\n data = response.json()\n assert isinstance(data, list)\nE fixture 'trained_model_in_db' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:360\n____________ ERROR at setup of TestModelsAPI.test_get_model_details ____________\nfile /app/tests/test_api.py, line 377\n @pytest.mark.asyncio\n async def test_get_model_details(\n self,\n test_client: AsyncClient,\n trained_model_in_db\n ):\n \"\"\"Test getting model details\"\"\"\n model_id = trained_model_in_db.model_id\n\n with patch('app.api.models.get_current_tenant_id', return_value=\"test-tenant\"):\n response = await test_client.get(f\"/models/{model_id}\")\n\n # This endpoint might not exist yet\n assert response.status_code in [\n status.HTTP_200_OK,\n status.HTTP_404_NOT_FOUND,\n status.HTTP_501_NOT_IMPLEMENTED\n ]\nE fixture 'trained_model_in_db' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:377\n_____ ERROR at setup of TestErrorHandling.test_messaging_failure_handling ______\nfile /app/tests/test_api.py, line 443\n @pytest.mark.asyncio\n async def test_messaging_failure_handling(\n self,\n test_client: AsyncClient,\n mock_data_service\n ):\n \"\"\"Test handling when messaging fails\"\"\"\n request_data = {\n \"include_weather\": True,\n \"include_traffic\": True,\n \"min_data_points\": 30\n }\n\n with patch('app.services.messaging.publish_job_started', side_effect=Exception(\"Messaging failed\")), \\\n patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n\n response = await test_client.post(\"/training/jobs\", json=request_data)\n\n # Should still succeed even if messaging fails\n assert response.status_code == status.HTTP_200_OK\n data = response.json()\n assert \"job_id\" in data\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_api.py:443\n_ ERROR at setup of TestAuthenticationIntegration.test_tenant_isolation_in_api _\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n______ ERROR at setup of TestTrainingService.test_get_job_status_existing ______\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n________ ERROR at setup of TestTrainingService.test_list_training_jobs _________\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n__ ERROR at setup of TestTrainingService.test_list_training_jobs_with_filter ___\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n____ ERROR at setup of TestTrainingService.test_cancel_training_job_success ____\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n___ ERROR at setup of TestTrainingService.test_validate_training_data_valid ____\nfile /app/tests/test_service.py, line 183\n @pytest.mark.asyncio\n async def test_validate_training_data_valid(\n self,\n training_service,\n test_db_session,\n mock_data_service\n ):\n \"\"\"Test validation with valid data\"\"\"\n config = {\"min_data_points\": 30}\n\n result = await training_service.validate_training_data(\n db=test_db_session,\n tenant_id=\"test-tenant\",\n config=config\n )\n\n assert isinstance(result, dict)\n assert \"is_valid\" in result\n assert \"issues\" in result\n assert \"recommendations\" in result\n assert \"estimated_time_minutes\" in result\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_service.py:183\n_________ ERROR at setup of TestTrainingService.test_update_job_status _________\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n_________ ERROR at setup of TestTrainingService.test_get_training_logs _________\ntests/conftest.py:539: in training_job_in_db\n job = ModelTrainingLog(\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:566: in _initialize_instance\n with util.safe_reraise():\n/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py:146: in __exit__\n raise exc_value.with_traceback(exc_tb)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state.py:564: in _initialize_instance\n manager.original_init(*mixed[1:], **kwargs)\n/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py:2142: in _declarative_constructor\n raise TypeError(\nE TypeError: 'started_at' is an invalid keyword argument for ModelTrainingLog\n_ ERROR at setup of TestTrainingServiceExecution.test_execute_training_job_success _\nfile /app/tests/test_service.py, line 468\n @pytest.mark.asyncio\n async def test_execute_training_job_success(\n self,\n training_service,\n test_db_session,\n mock_messaging,\n mock_data_service\n ):\n \"\"\"Test successful training job execution\"\"\"\n # Create job first\n job_id = \"test-execution-job\"\n training_log = await training_service.create_training_job(\n db=test_db_session,\n tenant_id=\"test-tenant\",\n job_id=job_id,\n config={\"include_weather\": True}\n )\n\n request = TrainingJobRequest(\n include_weather=True,\n include_traffic=True,\n min_data_points=30\n )\n\n with patch('app.services.training_service.TrainingService._fetch_sales_data') as mock_fetch_sales, \\\n patch('app.services.training_service.TrainingService._fetch_weather_data') as mock_fetch_weather, \\\n patch('app.services.training_service.TrainingService._fetch_traffic_data') as mock_fetch_traffic, \\\n patch('app.services.training_service.TrainingService._store_trained_models') as mock_store:\n\n mock_fetch_sales.return_value = [{\"date\": \"2024-01-01\", \"product_name\": \"Pan Integral\", \"quantity\": 45}]\n mock_fetch_weather.return_value = []\n mock_fetch_traffic.return_value = []\n mock_store.return_value = None\n\n await training_service.execute_training_job(\n db=test_db_session,\n job_id=job_id,\n tenant_id=\"test-tenant\",\n request=request\n )\n\n # Verify job was completed\n updated_job = await training_service.get_job_status(\n db=test_db_session,\n job_id=job_id,\n tenant_id=\"test-tenant\"\n )\n\n assert updated_job.status == \"completed\"\n assert updated_job.progress == 100\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_service.py:468\n_ ERROR at setup of TestTrainingServiceExecution.test_execute_single_product_training_success _\nfile /app/tests/test_service.py, line 559\n @pytest.mark.asyncio\n async def test_execute_single_product_training_success(\n self,\n training_service,\n test_db_session,\n mock_messaging,\n mock_data_service\n ):\n \"\"\"Test successful single product training execution\"\"\"\n job_id = \"test-single-product-job\"\n product_name = \"Pan Integral\"\n\n await training_service.create_single_product_job(\n db=test_db_session,\n tenant_id=\"test-tenant\",\n product_name=product_name,\n job_id=job_id,\n config={}\n )\n\n request = SingleProductTrainingRequest(\n include_weather=True,\n include_traffic=False\n )\n\n with patch('app.services.training_service.TrainingService._fetch_product_sales_data') as mock_fetch_sales, \\\n patch('app.services.training_service.TrainingService._fetch_weather_data') as mock_fetch_weather, \\\n patch('app.services.training_service.TrainingService._store_single_trained_model') as mock_store:\n\n mock_fetch_sales.return_value = [{\"date\": \"2024-01-01\", \"product_name\": product_name, \"quantity\": 45}]\n mock_fetch_weather.return_value = []\n mock_store.return_value = None\n\n await training_service.execute_single_product_training(\n db=test_db_session,\n job_id=job_id,\n tenant_id=\"test-tenant\",\n product_name=product_name,\n request=request\n )\n\n # Verify job was completed\n updated_job = await training_service.get_job_status(\n db=test_db_session,\n job_id=job_id,\n tenant_id=\"test-tenant\"\n )\n\n assert updated_job.status == \"completed\"\n assert updated_job.progress == 100\nE fixture 'mock_data_service' not found\n> available fixtures: anyio_backend, anyio_backend_name, anyio_backend_options, api_test_scenarios, auth_headers, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, class_mocker, cleanup_after_test, configure_test_logging, corrupted_sales_data, cov, data_quality_test_cases, doctest_namespace, error_scenarios, event_loop, failing_external_services, insufficient_sales_data, integration_test_dependencies, integration_test_setup, large_dataset_for_performance, load_test_configuration, memory_monitor, mock_aemet_client, mock_data_processor, mock_external_services, mock_job_scheduler, mock_madrid_client, mock_messaging, mock_ml_trainer, mock_model_storage, mock_notification_system, mock_prophet_manager, mocker, module_mocker, monkeypatch, no_cover, package_mocker, performance_benchmarks, pytestconfig, real_world_scenarios, record_property, record_testsuite_property, record_xml_attribute, recwarn, sample_bakery_sales_data, sample_model_metadata, sample_single_product_request, sample_traffic_data, sample_training_request, sample_weather_data, seasonal_product_data, session_mocker, setup_test_environment, spanish_holidays_2023, temp_model_storage, test_app, test_client, test_config, test_data_validator, test_db_session, test_metrics_collector, timing_monitor, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, training_job_in_db, training_progress_states, training_service, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory\n> use 'pytest --fixtures [testpath]' for help on them.\n\n/app/tests/test_service.py:559\n_ ERROR at teardown of TestTrainingServiceEdgeCases.test_malformed_config_handling _\ntests/conftest.py:464: in setup_test_environment\n os.environ.pop(var, None)(scope=\"session\")\nE TypeError: 'str' object is not callable\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:60 Failed to create training job: 'coroutine' object has no attribute 'add'\n=================================== FAILURES ===================================\n______________________ TestTrainingAPI.test_health_check _______________________\ntests/test_api.py:20: in test_health_check\n response = await test_client.get(\"/health\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n__________________ TestTrainingAPI.test_readiness_check_ready __________________\ntests/test_api.py:32: in test_readiness_check_ready\n with patch('app.main.app.state.ready', True):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'ready'\n________________ TestTrainingAPI.test_readiness_check_not_ready ________________\ntests/test_api.py:42: in test_readiness_check_not_ready\n with patch('app.main.app.state.ready', False):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'ready'\n_________________ TestTrainingAPI.test_liveness_check_healthy __________________\ntests/test_api.py:53: in test_liveness_check_healthy\n response = await test_client.get(\"/health/live\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n________________ TestTrainingAPI.test_liveness_check_unhealthy _________________\ntests/test_api.py:63: in test_liveness_check_unhealthy\n response = await test_client.get(\"/health/live\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n____________________ TestTrainingAPI.test_metrics_endpoint _____________________\ntests/test_api.py:73: in test_metrics_endpoint\n response = await test_client.get(\"/metrics\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n______________________ TestTrainingAPI.test_root_endpoint ______________________\ntests/test_api.py:92: in test_root_endpoint\n response = await test_client.get(\"/\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n_________ TestTrainingJobsAPI.test_start_training_job_validation_error _________\ntests/test_api.py:139: in test_start_training_job_validation_error\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n_________ TestTrainingJobsAPI.test_get_training_status_nonexistent_job _________\ntests/test_api.py:167: in test_get_training_status_nonexistent_job\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n_______________ TestTrainingJobsAPI.test_cancel_nonexistent_job ________________\ntests/test_api.py:233: in test_cancel_nonexistent_job\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n___ TestSingleProductTrainingAPI.test_train_single_product_validation_error ____\ntests/test_api.py:323: in test_train_single_product_validation_error\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n________________ TestErrorHandling.test_database_error_handling ________________\ntests/test_api.py:412: in test_database_error_handling\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n___________________ TestErrorHandling.test_missing_tenant_id ___________________\ntests/test_api.py:427: in test_missing_tenant_id\n response = await test_client.post(\"/training/jobs\", json=request_data)\nE AttributeError: 'async_generator' object has no attribute 'post'\n_________________ TestErrorHandling.test_invalid_job_id_format _________________\ntests/test_api.py:437: in test_invalid_job_id_format\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n_________________ TestErrorHandling.test_invalid_json_payload __________________\ntests/test_api.py:469: in test_invalid_json_payload\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n_______________ TestErrorHandling.test_unsupported_content_type ________________\ntests/test_api.py:481: in test_unsupported_content_type\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n__________ TestAuthenticationIntegration.test_endpoints_require_auth ___________\ntests/test_api.py:512: in test_endpoints_require_auth\n response = await test_client.post(endpoint, json={})\nE AttributeError: 'async_generator' object has no attribute 'post'\n______________ TestAPIValidation.test_training_request_validation ______________\ntests/test_api.py:555: in test_training_request_validation\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n___________ TestAPIValidation.test_single_product_request_validation ___________\ntests/test_api.py:591: in test_single_product_request_validation\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n______________ TestAPIValidation.test_query_parameter_validation _______________\ntests/test_api.py:612: in test_query_parameter_validation\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n_________________ TestAPIPerformance.test_concurrent_requests __________________\ntests/test_api.py:643: in test_concurrent_requests\n with patch('app.api.training.get_current_tenant_id', return_value=f\"tenant-{i}\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n________________ TestAPIPerformance.test_large_payload_handling ________________\ntests/test_api.py:665: in test_large_payload_handling\n with patch('app.api.training.get_current_tenant_id', return_value=\"test-tenant\"):\n/usr/local/lib/python3.11/unittest/mock.py:1446: in __enter__\n original, local = self.get_original()\n/usr/local/lib/python3.11/unittest/mock.py:1419: in get_original\n raise AttributeError(\nE AttributeError: does not have the attribute 'get_current_tenant_id'\n______________ TestAPIPerformance.test_rapid_successive_requests _______________\ntests/test_api.py:681: in test_rapid_successive_requests\n response = await test_client.get(\"/health\")\nE AttributeError: 'async_generator' object has no attribute 'get'\n_____ TestBakeryDataProcessor.test_prepare_training_data_insufficient_data _____\ntests/test_ml.py:201: in test_prepare_training_data_insufficient_data\n with pytest.raises(Exception):\nE Failed: DID NOT RAISE \n------------------------------ Captured log call -------------------------------\nINFO app.ml.data_processor:data_processor.py:45 Preparing training data for product: Pan Integral\nINFO app.ml.data_processor:data_processor.py:69 Prepared 5 data points for Pan Integral\n___________ TestBakeryProphetManager.test_train_bakery_model_success ___________\ntests/test_ml.py:239: in test_train_bakery_model_success\n result = await prophet_manager.train_bakery_model(\napp/ml/prophet_manager.py:70: in train_bakery_model\n model = self._create_prophet_model(regressor_columns)\napp/ml/prophet_manager.py:238: in _create_prophet_model\n daily_seasonality=settings.PROPHET_DAILY_SEASONALITY,\n/usr/local/lib/python3.11/site-packages/pydantic/main.py:761: in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nE AttributeError: 'TrainingSettings' object has no attribute 'PROPHET_DAILY_SEASONALITY'\n------------------------------ Captured log call -------------------------------\nINFO app.ml.prophet_manager:prophet_manager.py:58 Training bakery model for tenant test-tenant, product Pan Integral\nINFO app.ml.prophet_manager:prophet_manager.py:226 Identified regressor columns: ['temperature', 'humidity']\nERROR app.ml.prophet_manager:prophet_manager.py:115 Failed to train bakery model for Pan Integral: 'TrainingSettings' object has no attribute 'PROPHET_DAILY_SEASONALITY'\n____________ TestBakeryMLTrainer.test_train_single_product_success _____________\ntests/test_ml.py:414: in test_train_single_product_success\n result = await ml_trainer.train_single_product(\napp/ml/trainer.py:149: in train_single_product\n model_info = await self.prophet_manager.train_bakery_model(\napp/ml/prophet_manager.py:61: in train_bakery_model\n await self._validate_training_data(df, product_name)\napp/ml/prophet_manager.py:158: in _validate_training_data\n raise ValueError(\nE ValueError: Insufficient training data for Pan Integral: 3 days, minimum required: 30\n------------------------------ Captured log call -------------------------------\nINFO app.ml.trainer:trainer.py:125 Starting single product training test-job-123 for Pan Integral\nINFO app.ml.data_processor:data_processor.py:45 Preparing training data for product: Pan Integral\nINFO app.ml.data_processor:data_processor.py:69 Prepared 3 data points for Pan Integral\nINFO app.ml.prophet_manager:prophet_manager.py:58 Training bakery model for tenant test-tenant, product Pan Integral\nERROR app.ml.prophet_manager:prophet_manager.py:115 Failed to train bakery model for Pan Integral: Insufficient training data for Pan Integral: 3 days, minimum required: 30\nERROR app.ml.trainer:trainer.py:170 Single product training test-job-123 failed: Insufficient training data for Pan Integral: 3 days, minimum required: 30\n____________ TestBakeryMLTrainer.test_train_single_product_no_data _____________\ntests/test_ml.py:438: in test_train_single_product_no_data\n await ml_trainer.train_single_product(\napp/ml/trainer.py:134: in train_single_product\n product_sales = sales_df[sales_df['product_name'] == product_name].copy()\n/usr/local/lib/python3.11/site-packages/pandas/core/frame.py:3893: in __getitem__\n indexer = self.columns.get_loc(key)\n/usr/local/lib/python3.11/site-packages/pandas/core/indexes/range.py:418: in get_loc\n raise KeyError(key)\nE KeyError: 'product_name'\n------------------------------ Captured log call -------------------------------\nINFO app.ml.trainer:trainer.py:125 Starting single product training test-job-123 for Nonexistent Product\nERROR app.ml.trainer:trainer.py:170 Single product training test-job-123 failed: 'product_name'\n_____________ TestTrainingService.test_create_training_job_success _____________\napp/services/training_service.py:52: in create_training_job\n db.add(training_log)\nE AttributeError: 'coroutine' object has no attribute 'add'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:34: in test_create_training_job_success\n result = await training_service.create_training_job(\napp/services/training_service.py:61: in create_training_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:60 Failed to create training job: 'coroutine' object has no attribute 'add'\n__________ TestTrainingService.test_create_single_product_job_success __________\napp/services/training_service.py:84: in create_single_product_job\n db.add(training_log)\nE AttributeError: 'coroutine' object has no attribute 'add'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:60: in test_create_single_product_job_success\n result = await training_service.create_single_product_job(\napp/services/training_service.py:93: in create_single_product_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:92 Failed to create single product training job: 'coroutine' object has no attribute 'add'\n_______________ TestTrainingService.test_cancel_nonexistent_job ________________\napp/services/training_service.py:270: in cancel_training_job\n result = await db.execute(\nE AttributeError: 'coroutine' object has no attribute 'execute'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:175: in test_cancel_nonexistent_job\n result = await training_service.cancel_training_job(\napp/services/training_service.py:297: in cancel_training_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:296 Failed to cancel training job: 'coroutine' object has no attribute 'execute'\n___________ TestTrainingService.test_validate_training_data_no_data ____________\ntests/test_service.py:221: in test_validate_training_data_no_data\n assert result[\"is_valid\"] is False\nE assert True is False\n------------------------------ Captured log call -------------------------------\nINFO app.services.training_service:training_service.py:306 Validating training data for tenant test-tenant\n________________ TestTrainingService.test_store_trained_models _________________\napp/services/training_service.py:572: in _store_trained_models\n await db.execute(\nE AttributeError: 'coroutine' object has no attribute 'execute'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:280: in test_store_trained_models\n await training_service._store_trained_models(\napp/services/training_service.py:592: in _store_trained_models\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:591 Failed to store trained models: 'coroutine' object has no attribute 'execute'\n________ TestTrainingServiceExecution.test_execute_training_job_failure ________\napp/services/training_service.py:52: in create_training_job\n db.add(training_log)\nE AttributeError: 'coroutine' object has no attribute 'add'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:529: in test_execute_training_job_failure\n await training_service.create_training_job(\napp/services/training_service.py:61: in create_training_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:60 Failed to create training job: 'coroutine' object has no attribute 'add'\n__________ TestTrainingServiceEdgeCases.test_concurrent_job_creation ___________\napp/services/training_service.py:52: in create_training_job\n db.add(training_log)\nE AttributeError: 'coroutine' object has no attribute 'add'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:660: in test_concurrent_job_creation\n job = await training_service.create_training_job(\napp/services/training_service.py:61: in create_training_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:60 Failed to create training job: 'coroutine' object has no attribute 'add'\n_________ TestTrainingServiceEdgeCases.test_malformed_config_handling __________\napp/services/training_service.py:52: in create_training_job\n db.add(training_log)\nE AttributeError: 'coroutine' object has no attribute 'add'\n\nDuring handling of the above exception, another exception occurred:\ntests/test_service.py:681: in test_malformed_config_handling\n job = await training_service.create_training_job(\napp/services/training_service.py:61: in create_training_job\n await db.rollback()\nE AttributeError: 'coroutine' object has no attribute 'rollback'\n------------------------------ Captured log call -------------------------------\nERROR app.services.training_service:training_service.py:60 Failed to create training job: 'coroutine' object has no attribute 'add'\n=============================== warnings summary ===============================\napp/schemas/training.py:41\n /app/app/schemas/training.py:41: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('seasonality_mode')\n\napp/schemas/training.py:106\n /app/app/schemas/training.py:106: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('min_data_points')\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_id\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_path\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_type\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_info\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\nshared/config/base.py:280\n /app/shared/config/base.py:280: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('JWT_SECRET_KEY')\n\nshared/config/base.py:288\n /app/shared/config/base.py:288: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('LOG_LEVEL')\n\nshared/config/base.py:295\n /app/shared/config/base.py:295: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('ENVIRONMENT')\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_config.py:268\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_config.py:268: PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n warnings.warn(DEPRECATION_MESSAGE, DeprecationWarning)\n\napp/models/training.py:12\n /app/app/models/training.py:12: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)\n Base = declarative_base()\n\ntests/test_api.py: 22 warnings\ntests/test_ml.py: 1 warning\n /app/tests/conftest.py:1560: RuntimeWarning: coroutine 'test_app' was never awaited\n gc.collect() # Force garbage collection after each test\n Enable tracemalloc to get traceback where the object was allocated.\n See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.\n\ntests/test_api.py: 13 warnings\n /usr/local/lib/python3.11/site-packages/_pytest/runner.py:139: RuntimeWarning: coroutine 'test_app' was never awaited\n item.funcargs = None # type: ignore[attr-defined]\n Enable tracemalloc to get traceback where the object was allocated.\n See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.\n\ntests/test_service.py::TestTrainingService::test_create_single_product_job_success\ntests/test_service.py::TestTrainingService::test_get_job_status_nonexistent\ntests/test_service.py::TestTrainingService::test_validate_training_data_no_data\ntests/test_service.py::TestTrainingService::test_store_trained_models\ntests/test_service.py::TestTrainingServiceDataFetching::test_fetch_sales_data_success\ntests/test_service.py::TestTrainingServiceEdgeCases::test_database_connection_failure\ntests/test_service.py::TestTrainingServiceEdgeCases::test_malformed_config_handling\n /app/tests/conftest.py:1560: RuntimeWarning: coroutine 'test_db_session' was never awaited\n gc.collect() # Force garbage collection after each test\n Enable tracemalloc to get traceback where the object was allocated.\n See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.\n\ntests/test_service.py: 10 warnings\n /usr/local/lib/python3.11/site-packages/_pytest/runner.py:139: RuntimeWarning: coroutine 'test_db_session' was never awaited\n item.funcargs = None # type: ignore[attr-defined]\n Enable tracemalloc to get traceback where the object was allocated.\n See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n------------ generated xml file: /app/tests/results/junit_unit.xml -------------\n\n---------- coverage: platform linux, python 3.11.13-final-0 ----------\nName Stmts Miss Cover Missing\n----------------------------------------------------------------\napp/__init__.py 0 0 100%\napp/api/__init__.py 0 0 100%\napp/api/models.py 18 5 72% 29-33\napp/api/training.py 166 132 20% 49-88, 100-124, 134-157, 167-187, 197-232, 244-286, 296-317, 327-347, 358-384, 395-415, 426-472\napp/core/__init__.py 0 0 100%\napp/core/config.py 32 0 100%\napp/core/database.py 115 91 21% 28-36, 45-72, 77-104, 109-165, 175-192, 199-209, 214-233, 238-248\napp/main.py 89 54 39% 39-90, 116-152, 158-168, 193, 203-211, 219-221, 224\napp/ml/__init__.py 0 0 100%\napp/ml/data_processor.py 238 63 74% 72-74, 104, 110-114, 125-134, 138-141, 149, 155, 194, 238, 274, 278-285, 302, 333, 335, 339-343, 382-383, 404, 418-423, 453, 457, 463, 473-493\napp/ml/prophet_manager.py 162 69 57% 73-112, 140-141, 148-150, 155, 170, 174, 185-186, 204, 213, 250, 282-284, 296-328, 334-365, 377-378, 382-388, 392-408\napp/ml/trainer.py 127 38 70% 52, 97-99, 123, 138, 156-167, 190-240, 255-256, 260, 288-291, 317-335\napp/models/__init__.py 0 0 100%\napp/models/training.py 87 0 100%\napp/schemas/__init__.py 0 0 100%\napp/schemas/training.py 184 6 97% 43-45, 108-110\napp/services/__init__.py 0 0 100%\napp/services/messaging.py 41 20 51% 24-28, 32-33, 38-46, 54, 71-81, 89-97, 105, 121, 137, 155, 172, 189, 206\napp/services/training_service.py 285 164 42% 54-57, 62, 85-89, 94, 102-161, 170-221, 237, 249-262, 286-293, 298, 315-316, 330-332, 340-341, 358-360, 376-403, 425, 448-475, 484, 487, 498-503, 512, 515, 526-531, 585-588, 593, 601-648, 655-694\n----------------------------------------------------------------\nTOTAL 1544 642 58%\nCoverage HTML written to dir /app/tests/results/coverage_unit_html\nCoverage XML written to file /app/tests/results/coverage_unit.xml\n\n=========================== short test summary info ============================\nFAILED tests/test_api.py::TestTrainingAPI::test_health_check - AttributeError...\nFAILED tests/test_api.py::TestTrainingAPI::test_readiness_check_ready - Attri...\nFAILED tests/test_api.py::TestTrainingAPI::test_readiness_check_not_ready - A...\nFAILED tests/test_api.py::TestTrainingAPI::test_liveness_check_healthy - Attr...\nFAILED tests/test_api.py::TestTrainingAPI::test_liveness_check_unhealthy - At...\nFAILED tests/test_api.py::TestTrainingAPI::test_metrics_endpoint - AttributeE...\nFAILED tests/test_api.py::TestTrainingAPI::test_root_endpoint - AttributeErro...\nFAILED tests/test_api.py::TestTrainingJobsAPI::test_start_training_job_validation_error\nFAILED tests/test_api.py::TestTrainingJobsAPI::test_get_training_status_nonexistent_job\nFAILED tests/test_api.py::TestTrainingJobsAPI::test_cancel_nonexistent_job - ...\nFAILED tests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_validation_error\nFAILED tests/test_api.py::TestErrorHandling::test_database_error_handling - A...\nFAILED tests/test_api.py::TestErrorHandling::test_missing_tenant_id - Attribu...\nFAILED tests/test_api.py::TestErrorHandling::test_invalid_job_id_format - Att...\nFAILED tests/test_api.py::TestErrorHandling::test_invalid_json_payload - Attr...\nFAILED tests/test_api.py::TestErrorHandling::test_unsupported_content_type - ...\nFAILED tests/test_api.py::TestAuthenticationIntegration::test_endpoints_require_auth\nFAILED tests/test_api.py::TestAPIValidation::test_training_request_validation\nFAILED tests/test_api.py::TestAPIValidation::test_single_product_request_validation\nFAILED tests/test_api.py::TestAPIValidation::test_query_parameter_validation\nFAILED tests/test_api.py::TestAPIPerformance::test_concurrent_requests - Attr...\nFAILED tests/test_api.py::TestAPIPerformance::test_large_payload_handling - A...\nFAILED tests/test_api.py::TestAPIPerformance::test_rapid_successive_requests\nFAILED tests/test_ml.py::TestBakeryDataProcessor::test_prepare_training_data_insufficient_data\nFAILED tests/test_ml.py::TestBakeryProphetManager::test_train_bakery_model_success\nFAILED tests/test_ml.py::TestBakeryMLTrainer::test_train_single_product_success\nFAILED tests/test_ml.py::TestBakeryMLTrainer::test_train_single_product_no_data\nFAILED tests/test_service.py::TestTrainingService::test_create_training_job_success\nFAILED tests/test_service.py::TestTrainingService::test_create_single_product_job_success\nFAILED tests/test_service.py::TestTrainingService::test_cancel_nonexistent_job\nFAILED tests/test_service.py::TestTrainingService::test_validate_training_data_no_data\nFAILED tests/test_service.py::TestTrainingService::test_store_trained_models\nFAILED tests/test_service.py::TestTrainingServiceExecution::test_execute_training_job_failure\nFAILED tests/test_service.py::TestTrainingServiceEdgeCases::test_concurrent_job_creation\nFAILED tests/test_service.py::TestTrainingServiceEdgeCases::test_malformed_config_handling\nERROR tests/test_api.py::TestTrainingJobsAPI::test_start_training_job_success\nERROR tests/test_api.py::TestTrainingJobsAPI::test_get_training_status_existing_job\nERROR tests/test_api.py::TestTrainingJobsAPI::test_list_training_jobs - TypeE...\nERROR tests/test_api.py::TestTrainingJobsAPI::test_list_training_jobs_with_status_filter\nERROR tests/test_api.py::TestTrainingJobsAPI::test_cancel_training_job_success\nERROR tests/test_api.py::TestTrainingJobsAPI::test_get_training_logs - TypeEr...\nERROR tests/test_api.py::TestTrainingJobsAPI::test_validate_training_data_valid\nERROR tests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_success\nERROR tests/test_api.py::TestSingleProductTrainingAPI::test_train_single_product_special_characters\nERROR tests/test_api.py::TestModelsAPI::test_list_models\nERROR tests/test_api.py::TestModelsAPI::test_get_model_details\nERROR tests/test_api.py::TestErrorHandling::test_messaging_failure_handling\nERROR tests/test_api.py::TestAuthenticationIntegration::test_tenant_isolation_in_api\nERROR tests/test_service.py::TestTrainingService::test_get_job_status_existing\nERROR tests/test_service.py::TestTrainingService::test_list_training_jobs - T...\nERROR tests/test_service.py::TestTrainingService::test_list_training_jobs_with_filter\nERROR tests/test_service.py::TestTrainingService::test_cancel_training_job_success\nERROR tests/test_service.py::TestTrainingService::test_validate_training_data_valid\nERROR tests/test_service.py::TestTrainingService::test_update_job_status - Ty...\nERROR tests/test_service.py::TestTrainingService::test_get_training_logs - Ty...\nERROR tests/test_service.py::TestTrainingServiceExecution::test_execute_training_job_success\nERROR tests/test_service.py::TestTrainingServiceExecution::test_execute_single_product_training_success\nERROR tests/test_service.py::TestTrainingServiceEdgeCases::test_malformed_config_handling\n======= 35 failed, 24 passed, 2 skipped, 64 warnings, 23 errors in 5.71s =======\n", + "stderr": "sys:1: RuntimeWarning: coroutine 'test_db_session' was never awaited\n", + "timestamp": "2025-07-25T11:22:43.703966" + }, + "integration": { + "suite": "integration", + "status": "failed", + "return_code": 5, + "duration": 0.5286672115325928, + "stdout": "\n================================================================================\nTRAINING SERVICE TEST SESSION STARTING\n================================================================================\n============================= test session starts ==============================\nplatform linux -- Python 3.11.13, pytest-7.4.3, pluggy-1.6.0 -- /usr/local/bin/python\ncachedir: .pytest_cache\nrootdir: /app\nplugins: anyio-3.7.1, mock-3.12.0, asyncio-0.21.1, cov-4.1.0\nasyncio: mode=Mode.STRICT\ncollecting ... collected 0 items\n\n================================================================================\nTRAINING SERVICE TEST SESSION FINISHED\nExit Status: 5\n================================================================================\n\n--------- generated xml file: /app/tests/results/junit_integration.xml ---------\n\n---------- coverage: platform linux, python 3.11.13-final-0 ----------\nName Stmts Miss Cover Missing\n----------------------------------------------------------------\napp/__init__.py 0 0 100%\napp/api/__init__.py 0 0 100%\napp/api/models.py 18 18 0% 5-33\napp/api/training.py 166 166 0% 4-472\napp/core/__init__.py 0 0 100%\napp/core/config.py 32 32 0% 6-65\napp/core/database.py 115 115 0% 2-251\napp/main.py 89 89 0% 4-224\napp/ml/__init__.py 0 0 100%\napp/ml/data_processor.py 238 238 0% 2-493\napp/ml/prophet_manager.py 162 162 0% 2-408\napp/ml/trainer.py 127 127 0% 2-365\napp/models/__init__.py 0 0 100%\napp/models/training.py 87 87 0% 2-154\napp/schemas/__init__.py 0 0 100%\napp/schemas/training.py 184 184 0% 2-287\napp/services/__init__.py 0 0 100%\napp/services/messaging.py 41 41 0% 2-206\napp/services/training_service.py 285 285 0% 2-694\n----------------------------------------------------------------\nTOTAL 1544 1544 0%\nCoverage HTML written to dir /app/tests/results/coverage_integration_html\nCoverage XML written to file /app/tests/results/coverage_integration.xml\n\n============================ no tests ran in 0.20s =============================\n", + "stderr": "/usr/local/lib/python3.11/site-packages/coverage/control.py:883: CoverageWarning: No data was collected. (no-data-collected)\n self._warn(\"No data was collected.\", slug=\"no-data-collected\")\n", + "timestamp": "2025-07-25T11:22:44.232765" + }, + "performance": { + "suite": "performance", + "status": "failed", + "return_code": 2, + "duration": 0.6394450664520264, + "stdout": "\n================================================================================\nTRAINING SERVICE TEST SESSION STARTING\n================================================================================\n============================= test session starts ==============================\nplatform linux -- Python 3.11.13, pytest-7.4.3, pluggy-1.6.0 -- /usr/local/bin/python\ncachedir: .pytest_cache\nrootdir: /app\nplugins: anyio-3.7.1, mock-3.12.0, asyncio-0.21.1, cov-4.1.0\nasyncio: mode=Mode.STRICT\ncollecting ... collected 0 items / 1 error\n\n================================================================================\nTRAINING SERVICE TEST SESSION FINISHED\nExit Status: 2\n================================================================================\n\n==================================== ERRORS ====================================\n__________________ ERROR collecting tests/test_performance.py __________________\nImportError while importing test module '/app/tests/test_performance.py'.\nHint: make sure your test modules/packages have valid Python names.\nTraceback:\n/usr/local/lib/python3.11/importlib/__init__.py:126: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\ntests/test_performance.py:16: in \n import psutil\nE ModuleNotFoundError: No module named 'psutil'\n--------- generated xml file: /app/tests/results/junit_performance.xml ---------\n\n---------- coverage: platform linux, python 3.11.13-final-0 ----------\nName Stmts Miss Cover Missing\n----------------------------------------------------------------\napp/__init__.py 0 0 100%\napp/api/__init__.py 0 0 100%\napp/api/models.py 18 18 0% 5-33\napp/api/training.py 166 166 0% 4-472\napp/core/__init__.py 0 0 100%\napp/core/config.py 32 32 0% 6-65\napp/core/database.py 115 115 0% 2-251\napp/main.py 89 89 0% 4-224\napp/ml/__init__.py 0 0 100%\napp/ml/data_processor.py 238 238 0% 2-493\napp/ml/prophet_manager.py 162 162 0% 2-408\napp/ml/trainer.py 127 127 0% 2-365\napp/models/__init__.py 0 0 100%\napp/models/training.py 87 87 0% 2-154\napp/schemas/__init__.py 0 0 100%\napp/schemas/training.py 184 184 0% 2-287\napp/services/__init__.py 0 0 100%\napp/services/messaging.py 41 41 0% 2-206\napp/services/training_service.py 285 285 0% 2-694\n----------------------------------------------------------------\nTOTAL 1544 1544 0%\nCoverage HTML written to dir /app/tests/results/coverage_performance_html\nCoverage XML written to file /app/tests/results/coverage_performance.xml\n\n=========================== short test summary info ============================\nERROR tests/test_performance.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n=============================== 1 error in 0.23s ===============================\n", + "stderr": "/usr/local/lib/python3.11/site-packages/coverage/control.py:883: CoverageWarning: No data was collected. (no-data-collected)\n self._warn(\"No data was collected.\", slug=\"no-data-collected\")\n", + "timestamp": "2025-07-25T11:22:44.872314" + }, + "end_to_end": { + "suite": "end_to_end", + "status": "failed", + "return_code": 1, + "duration": 2.01322340965271, + "stdout": "\n================================================================================\nTRAINING SERVICE TEST SESSION STARTING\n================================================================================\n============================= test session starts ==============================\nplatform linux -- Python 3.11.13, pytest-7.4.3, pluggy-1.6.0 -- /usr/local/bin/python\ncachedir: .pytest_cache\nrootdir: /app\nplugins: anyio-3.7.1, mock-3.12.0, asyncio-0.21.1, cov-4.1.0\nasyncio: mode=Mode.STRICT\ncollecting ... 2025-07-25 11:22:46 [INFO] shared.monitoring.logging: Logging configured for training-service at level INFO\ncollected 1 item\n\ntests/test_end_to_end.py::TestTrainingServiceEndToEnd::test_complete_training_workflow_api ERROR\ntests/test_end_to_end.py::TestTrainingServiceEndToEnd::test_complete_training_workflow_api ERROR\n================================================================================\nTRAINING SERVICE TEST SESSION FINISHED\nExit Status: 1\n================================================================================\n\n\n==================================== ERRORS ====================================\n_ ERROR at setup of TestTrainingServiceEndToEnd.test_complete_training_workflow_api _\ntests/test_end_to_end.py:75: in real_bakery_data\n temp = 15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi)\nE UnboundLocalError: cannot access local variable 'np' where it is not associated with a value\n_ ERROR at teardown of TestTrainingServiceEndToEnd.test_complete_training_workflow_api _\ntests/conftest.py:464: in setup_test_environment\n os.environ.pop(var, None)(scope=\"session\")\nE TypeError: 'str' object is not callable\n=============================== warnings summary ===============================\nshared/config/base.py:280\n /app/shared/config/base.py:280: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('JWT_SECRET_KEY')\n\nshared/config/base.py:288\n /app/shared/config/base.py:288: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('LOG_LEVEL')\n\nshared/config/base.py:295\n /app/shared/config/base.py:295: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('ENVIRONMENT')\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_config.py:268\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_config.py:268: PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n warnings.warn(DEPRECATION_MESSAGE, DeprecationWarning)\n\napp/schemas/training.py:41\n /app/app/schemas/training.py:41: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('seasonality_mode')\n\napp/schemas/training.py:106\n /app/app/schemas/training.py:106: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/\n @validator('min_data_points')\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_id\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_path\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_type\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\n../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149\n /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:149: UserWarning: Field \"model_info\" has conflict with protected namespace \"model_\".\n \n You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.\n warnings.warn(\n\napp/models/training.py:12\n /app/app/models/training.py:12: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)\n Base = declarative_base()\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n--------- generated xml file: /app/tests/results/junit_end_to_end.xml ----------\n\n---------- coverage: platform linux, python 3.11.13-final-0 ----------\nName Stmts Miss Cover Missing\n----------------------------------------------------------------\napp/__init__.py 0 0 100%\napp/api/__init__.py 0 0 100%\napp/api/models.py 18 5 72% 29-33\napp/api/training.py 166 132 20% 49-88, 100-124, 134-157, 167-187, 197-232, 244-286, 296-317, 327-347, 358-384, 395-415, 426-472\napp/core/__init__.py 0 0 100%\napp/core/config.py 32 0 100%\napp/core/database.py 115 91 21% 28-36, 45-72, 77-104, 109-165, 175-192, 199-209, 214-233, 238-248\napp/main.py 89 54 39% 39-90, 116-152, 158-168, 193, 203-211, 219-221, 224\napp/ml/__init__.py 0 0 100%\napp/ml/data_processor.py 238 211 11% 44-74, 91-141, 145-167, 171-186, 190-218, 225-285, 292-343, 347-371, 375-385, 389-412, 416-423, 427-444, 448-465, 473-493\napp/ml/prophet_manager.py 162 127 22% 57-116, 133-150, 154-174, 178-215, 219-227, 233-250, 254-284, 296-328, 334-365, 377-378, 382-388, 392-408\napp/ml/trainer.py 127 103 19% 51-99, 122-171, 190-240, 244-260, 268-293, 300-341, 345-365\napp/models/__init__.py 0 0 100%\napp/models/training.py 87 0 100%\napp/schemas/__init__.py 0 0 100%\napp/schemas/training.py 184 6 97% 43-45, 108-110\napp/services/__init__.py 0 0 100%\napp/services/messaging.py 41 20 51% 24-28, 32-33, 38-46, 54, 71-81, 89-97, 105, 121, 137, 155, 172, 189, 206\napp/services/training_service.py 285 249 13% 41-62, 71-94, 102-161, 170-221, 228-241, 249-262, 269-298, 305-360, 376-403, 410-441, 448-475, 479-503, 507-531, 538-593, 601-648, 655-694\n----------------------------------------------------------------\nTOTAL 1544 998 35%\nCoverage HTML written to dir /app/tests/results/coverage_end_to_end_html\nCoverage XML written to file /app/tests/results/coverage_end_to_end.xml\n\n=========================== short test summary info ============================\nERROR tests/test_end_to_end.py::TestTrainingServiceEndToEnd::test_complete_training_workflow_api\nERROR tests/test_end_to_end.py::TestTrainingServiceEndToEnd::test_complete_training_workflow_api\n======================== 11 warnings, 2 errors in 1.45s ========================\n", + "stderr": "", + "timestamp": "2025-07-25T11:22:46.885639" + } + }, + "recommendations": [ + "Failed test suites: unit, integration, performance, end_to_end. Check logs for detailed error messages." + ] +} \ No newline at end of file diff --git a/services/training/tests/run_tests.py b/services/training/tests/run_tests.py new file mode 100644 index 00000000..f99ac527 --- /dev/null +++ b/services/training/tests/run_tests.py @@ -0,0 +1,673 @@ +# ================================================================ +# services/training/tests/run_tests.py +# ================================================================ +""" +Main test runner script for Training Service +Executes comprehensive test suite and generates reports +""" + +import os +import sys +import asyncio +import subprocess +import json +import time +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Any +import logging + +# Setup logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + + +class TrainingTestRunner: + """Main test runner for training service""" + + def __init__(self): + self.test_dir = Path(__file__).parent + self.results_dir = self.test_dir / "results" + self.results_dir.mkdir(exist_ok=True) + + # Test configuration + self.test_suites = { + "unit": { + "files": ["test_api.py", "test_ml.py", "test_service.py"], + "description": "Unit tests for individual components", + "timeout": 300 # 5 minutes + }, + "integration": { + "files": ["test_ml_pipeline_integration.py"], + "description": "Integration tests for ML pipeline with external data", + "timeout": 600 # 10 minutes + }, + "performance": { + "files": ["test_performance.py"], + "description": "Performance and load testing", + "timeout": 900 # 15 minutes + }, + "end_to_end": { + "files": ["test_end_to_end.py"], + "description": "End-to-end workflow testing", + "timeout": 800 # 13 minutes + } + } + + self.test_results = {} + + async def setup_test_environment(self): + """Setup test environment and dependencies""" + logger.info("Setting up test environment...") + + # Check if we're running in Docker + if os.path.exists("/.dockerenv"): + logger.info("Running in Docker environment") + else: + logger.info("Running in local environment") + + # Verify required files exist + required_files = [ + "conftest.py", + "test_ml_pipeline_integration.py", + "test_performance.py" + ] + + for file in required_files: + file_path = self.test_dir / file + if not file_path.exists(): + logger.warning(f"Required test file missing: {file}") + + # Create test data if needed + await self.create_test_data() + + # Verify external services (mock or real) + await self.verify_external_services() + + async def create_test_data(self): + """Create or verify test data exists""" + logger.info("Creating/verifying test data...") + + test_data_dir = self.test_dir / "fixtures" / "test_data" + test_data_dir.mkdir(parents=True, exist_ok=True) + + # Create bakery sales sample if it doesn't exist + sales_file = test_data_dir / "bakery_sales_sample.csv" + if not sales_file.exists(): + logger.info("Creating sample sales data...") + await self.generate_sample_sales_data(sales_file) + + # Create weather data sample + weather_file = test_data_dir / "madrid_weather_sample.json" + if not weather_file.exists(): + logger.info("Creating sample weather data...") + await self.generate_sample_weather_data(weather_file) + + # Create traffic data sample + traffic_file = test_data_dir / "madrid_traffic_sample.json" + if not traffic_file.exists(): + logger.info("Creating sample traffic data...") + await self.generate_sample_traffic_data(traffic_file) + + async def generate_sample_sales_data(self, file_path: Path): + """Generate sample sales data for testing""" + import pandas as pd + import numpy as np + from datetime import datetime, timedelta + + # Generate 6 months of sample data + start_date = datetime(2023, 6, 1) + dates = [start_date + timedelta(days=i) for i in range(180)] + + products = ["Pan Integral", "Croissant", "Magdalenas", "Empanadas", "Tarta Chocolate"] + + data = [] + for date in dates: + for product in products: + base_quantity = np.random.randint(10, 100) + + # Weekend boost + if date.weekday() >= 5: + base_quantity *= 1.2 + + # Seasonal variation + temp = 15 + 10 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": int(base_quantity), + "revenue": round(base_quantity * np.random.uniform(2.5, 8.0), 2), + "temperature": round(temp + np.random.normal(0, 3), 1), + "precipitation": max(0, np.random.exponential(0.5)), + "is_weekend": date.weekday() >= 5, + "is_holiday": False + }) + + df = pd.DataFrame(data) + df.to_csv(file_path, index=False) + logger.info(f"Created sample sales data: {len(df)} records") + + async def generate_sample_weather_data(self, file_path: Path): + """Generate sample weather data""" + import json + from datetime import datetime, timedelta + import numpy as np + + start_date = datetime(2023, 6, 1) + weather_data = [] + + for i in range(180): + date = start_date + timedelta(days=i) + day_of_year = date.timetuple().tm_yday + base_temp = 14 + 12 * np.sin((day_of_year / 365) * 2 * np.pi) + + weather_data.append({ + "date": date.isoformat(), + "temperature": round(base_temp + np.random.normal(0, 5), 1), + "precipitation": max(0, np.random.exponential(1.0)), + "humidity": np.random.uniform(30, 80), + "wind_speed": np.random.uniform(5, 25), + "pressure": np.random.uniform(1000, 1025), + "description": np.random.choice(["Soleado", "Nuboso", "Lluvioso"]), + "source": "aemet_test" + }) + + with open(file_path, 'w') as f: + json.dump(weather_data, f, indent=2) + logger.info(f"Created sample weather data: {len(weather_data)} records") + + async def generate_sample_traffic_data(self, file_path: Path): + """Generate sample traffic data""" + import json + from datetime import datetime, timedelta + import numpy as np + + start_date = datetime(2023, 6, 1) + traffic_data = [] + + for i in range(180): + date = start_date + timedelta(days=i) + + for hour in [8, 12, 18]: # Three measurements per day + measurement_time = date.replace(hour=hour) + + if hour in [8, 18]: # Rush hours + volume = np.random.randint(800, 1500) + congestion = "high" + else: # Lunch time + volume = np.random.randint(400, 800) + congestion = "medium" + + traffic_data.append({ + "date": measurement_time.isoformat(), + "traffic_volume": volume, + "occupation_percentage": np.random.randint(10, 90), + "load_percentage": np.random.randint(20, 95), + "average_speed": np.random.randint(15, 50), + "congestion_level": congestion, + "pedestrian_count": np.random.randint(50, 500), + "measurement_point_id": "TEST_POINT_001", + "measurement_point_name": "Plaza Mayor", + "road_type": "URB", + "source": "madrid_opendata_test" + }) + + with open(file_path, 'w') as f: + json.dump(traffic_data, f, indent=2) + logger.info(f"Created sample traffic data: {len(traffic_data)} records") + + async def verify_external_services(self): + """Verify external services are available (mock or real)""" + logger.info("Verifying external services...") + + # Check if mock services are available + mock_services = [ + ("Mock AEMET", "http://localhost:8080/health"), + ("Mock Madrid OpenData", "http://localhost:8081/health"), + ("Mock Auth Service", "http://localhost:8082/health"), + ("Mock Data Service", "http://localhost:8083/health") + ] + + try: + import httpx + async with httpx.AsyncClient(timeout=5.0) as client: + for service_name, url in mock_services: + try: + response = await client.get(url) + if response.status_code == 200: + logger.info(f"{service_name} is available") + else: + logger.warning(f"{service_name} returned status {response.status_code}") + except Exception as e: + logger.warning(f"{service_name} is not available: {e}") + except ImportError: + logger.warning("httpx not available, skipping service checks") + + def run_test_suite(self, suite_name: str) -> Dict[str, Any]: + """Run a specific test suite""" + suite_config = self.test_suites[suite_name] + logger.info(f"Running {suite_name} test suite: {suite_config['description']}") + + start_time = time.time() + + # Prepare pytest command + pytest_args = [ + "python", "-m", "pytest", + "-v", + "--tb=short", + "--capture=no", + f"--junitxml={self.results_dir}/junit_{suite_name}.xml", + f"--cov=app", + f"--cov-report=html:{self.results_dir}/coverage_{suite_name}_html", + f"--cov-report=xml:{self.results_dir}/coverage_{suite_name}.xml", + "--cov-report=term-missing" + ] + + # Add test files + for test_file in suite_config["files"]: + test_path = self.test_dir / test_file + if test_path.exists(): + pytest_args.append(str(test_path)) + else: + logger.warning(f"Test file not found: {test_file}") + + # Run the tests + try: + result = subprocess.run( + pytest_args, + cwd=self.test_dir.parent, # Run from training service root + capture_output=True, + text=True, + timeout=suite_config["timeout"] + ) + + duration = time.time() - start_time + + return { + "suite": suite_name, + "status": "passed" if result.returncode == 0 else "failed", + "return_code": result.returncode, + "duration": duration, + "stdout": result.stdout, + "stderr": result.stderr, + "timestamp": datetime.now().isoformat() + } + + except subprocess.TimeoutExpired: + duration = time.time() - start_time + logger.error(f"Test suite {suite_name} timed out after {duration:.2f}s") + + return { + "suite": suite_name, + "status": "timeout", + "return_code": -1, + "duration": duration, + "stdout": "", + "stderr": f"Test suite timed out after {suite_config['timeout']}s", + "timestamp": datetime.now().isoformat() + } + + except Exception as e: + duration = time.time() - start_time + logger.error(f"Error running test suite {suite_name}: {e}") + + return { + "suite": suite_name, + "status": "error", + "return_code": -1, + "duration": duration, + "stdout": "", + "stderr": str(e), + "timestamp": datetime.now().isoformat() + } + + def generate_test_report(self): + """Generate comprehensive test report""" + logger.info("Generating test report...") + + # Calculate summary statistics + total_suites = len(self.test_results) + passed_suites = sum(1 for r in self.test_results.values() if r["status"] == "passed") + failed_suites = sum(1 for r in self.test_results.values() if r["status"] == "failed") + error_suites = sum(1 for r in self.test_results.values() if r["status"] == "error") + timeout_suites = sum(1 for r in self.test_results.values() if r["status"] == "timeout") + + total_duration = sum(r["duration"] for r in self.test_results.values()) + + # Create detailed report + report = { + "test_run_summary": { + "timestamp": datetime.now().isoformat(), + "total_suites": total_suites, + "passed_suites": passed_suites, + "failed_suites": failed_suites, + "error_suites": error_suites, + "timeout_suites": timeout_suites, + "success_rate": (passed_suites / total_suites * 100) if total_suites > 0 else 0, + "total_duration_seconds": total_duration + }, + "suite_results": self.test_results, + "recommendations": self.generate_recommendations() + } + + # Save JSON report + report_file = self.results_dir / "test_report.json" + with open(report_file, 'w') as f: + json.dump(report, f, indent=2) + + # Generate HTML report + self.generate_html_report(report) + + # Print summary to console + self.print_test_summary(report) + + return report + + def generate_recommendations(self) -> List[str]: + """Generate recommendations based on test results""" + recommendations = [] + + failed_suites = [name for name, result in self.test_results.items() if result["status"] == "failed"] + timeout_suites = [name for name, result in self.test_results.items() if result["status"] == "timeout"] + + if failed_suites: + recommendations.append(f"Failed test suites: {', '.join(failed_suites)}. Check logs for detailed error messages.") + + if timeout_suites: + recommendations.append(f"Timeout in suites: {', '.join(timeout_suites)}. Consider increasing timeout or optimizing performance.") + + # Performance recommendations + slow_suites = [ + name for name, result in self.test_results.items() + if result["duration"] > 300 # 5 minutes + ] + if slow_suites: + recommendations.append(f"Slow test suites: {', '.join(slow_suites)}. Consider performance optimization.") + + if not recommendations: + recommendations.append("All tests passed successfully! Consider adding more edge case tests.") + + return recommendations + + def generate_html_report(self, report: Dict[str, Any]): + """Generate HTML test report""" + html_template = """ + + + + Training Service Test Report + + + +
+

Training Service Test Report

+

Generated: {timestamp}

+
+ +
+
+
{total_suites}
+
Total Suites
+
+
+
{passed_suites}
+
Passed
+
+
+
{failed_suites}
+
Failed
+
+
+
{timeout_suites}
+
Timeout
+
+
+
{success_rate:.1f}%
+
Success Rate
+
+
+
{duration:.1f}s
+
Total Duration
+
+
+ +
+

Recommendations

+
    + {recommendations_html} +
+
+ +

Suite Results

+ {suite_results_html} + + + + """ + + # Format recommendations + recommendations_html = '\n'.join( + f"
  • {rec}
  • " for rec in report["recommendations"] + ) + + # Format suite results + suite_results_html = "" + for suite_name, result in report["suite_results"].items(): + status_class = result["status"] + suite_results_html += f""" +
    +

    {suite_name.title()} Tests ({result["status"].upper()})

    +

    Duration: {result["duration"]:.2f}s

    +

    Return Code: {result["return_code"]}

    + + {f'

    Output:

    {result["stdout"][:1000]}{"..." if len(result["stdout"]) > 1000 else ""}
    ' if result["stdout"] else ""} + {f'

    Errors:

    {result["stderr"][:1000]}{"..." if len(result["stderr"]) > 1000 else ""}
    ' if result["stderr"] else ""} +
    + """ + + # Fill template + html_content = html_template.format( + timestamp=report["test_run_summary"]["timestamp"], + total_suites=report["test_run_summary"]["total_suites"], + passed_suites=report["test_run_summary"]["passed_suites"], + failed_suites=report["test_run_summary"]["failed_suites"], + timeout_suites=report["test_run_summary"]["timeout_suites"], + success_rate=report["test_run_summary"]["success_rate"], + duration=report["test_run_summary"]["total_duration_seconds"], + recommendations_html=recommendations_html, + suite_results_html=suite_results_html + ) + + # Save HTML report + html_file = self.results_dir / "test_report.html" + with open(html_file, 'w') as f: + f.write(html_content) + + logger.info(f"HTML report saved to: {html_file}") + + def print_test_summary(self, report: Dict[str, Any]): + """Print test summary to console""" + summary = report["test_run_summary"] + + print("\n" + "=" * 80) + print("TRAINING SERVICE TEST RESULTS SUMMARY") + print("=" * 80) + print(f"Timestamp: {summary['timestamp']}") + print(f"Total Suites: {summary['total_suites']}") + print(f"Passed: {summary['passed_suites']}") + print(f"Failed: {summary['failed_suites']}") + print(f"Errors: {summary['error_suites']}") + print(f"Timeouts: {summary['timeout_suites']}") + print(f"Success Rate: {summary['success_rate']:.1f}%") + print(f"Total Duration: {summary['total_duration_seconds']:.2f}s") + + print("\nSUITE DETAILS:") + print("-" * 50) + for suite_name, result in report["suite_results"].items(): + status_icon = "βœ…" if result["status"] == "passed" else "❌" + print(f"{status_icon} {suite_name.ljust(15)}: {result['status'].upper().ljust(10)} ({result['duration']:.2f}s)") + + print("\nRECOMMENDATIONS:") + print("-" * 50) + for i, rec in enumerate(report["recommendations"], 1): + print(f"{i}. {rec}") + + print("\nFILES GENERATED:") + print("-" * 50) + print(f"πŸ“„ JSON Report: {self.results_dir}/test_report.json") + print(f"🌐 HTML Report: {self.results_dir}/test_report.html") + print(f"πŸ“Š Coverage Reports: {self.results_dir}/coverage_*_html/") + print(f"πŸ“‹ JUnit XML: {self.results_dir}/junit_*.xml") + print("=" * 80) + + async def run_all_tests(self): + """Run all test suites""" + logger.info("Starting comprehensive test run...") + + # Setup environment + await self.setup_test_environment() + + # Run each test suite + for suite_name in self.test_suites.keys(): + logger.info(f"Starting {suite_name} test suite...") + result = self.run_test_suite(suite_name) + self.test_results[suite_name] = result + + if result["status"] == "passed": + logger.info(f"βœ… {suite_name} tests PASSED ({result['duration']:.2f}s)") + elif result["status"] == "failed": + logger.error(f"❌ {suite_name} tests FAILED ({result['duration']:.2f}s)") + elif result["status"] == "timeout": + logger.error(f"⏰ {suite_name} tests TIMED OUT ({result['duration']:.2f}s)") + else: + logger.error(f"πŸ’₯ {suite_name} tests ERROR ({result['duration']:.2f}s)") + + # Generate final report + report = self.generate_test_report() + + return report + + def run_specific_suite(self, suite_name: str): + """Run a specific test suite""" + if suite_name not in self.test_suites: + logger.error(f"Unknown test suite: {suite_name}") + logger.info(f"Available suites: {', '.join(self.test_suites.keys())}") + return None + + logger.info(f"Running {suite_name} test suite only...") + result = self.run_test_suite(suite_name) + self.test_results[suite_name] = result + + # Generate report for single suite + report = self.generate_test_report() + return report + + +# ================================================================ +# MAIN EXECUTION +# ================================================================ + +async def main(): + """Main execution function""" + import argparse + + parser = argparse.ArgumentParser(description="Training Service Test Runner") + parser.add_argument( + "--suite", + choices=list(TrainingTestRunner().test_suites.keys()) + ["all"], + default="all", + help="Test suite to run (default: all)" + ) + parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Verbose output" + ) + parser.add_argument( + "--quick", + action="store_true", + help="Run quick tests only (skip performance tests)" + ) + + args = parser.parse_args() + + # Setup logging level + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + # Create test runner + runner = TrainingTestRunner() + + # Modify test suites for quick run + if args.quick: + # Skip performance tests in quick mode + if "performance" in runner.test_suites: + del runner.test_suites["performance"] + logger.info("Quick mode: Skipping performance tests") + + try: + if args.suite == "all": + report = await runner.run_all_tests() + else: + report = runner.run_specific_suite(args.suite) + + # Exit with appropriate code + if report and report["test_run_summary"]["failed_suites"] == 0 and report["test_run_summary"]["error_suites"] == 0: + logger.info("All tests completed successfully!") + sys.exit(0) + else: + logger.error("Some tests failed!") + sys.exit(1) + + except KeyboardInterrupt: + logger.info("Test run interrupted by user") + sys.exit(130) + except Exception as e: + logger.error(f"Test run failed with error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + # Handle both direct execution and pytest discovery + if len(sys.argv) > 1 and sys.argv[1] in ["--suite", "-h", "--help"]: + # Running as main script with arguments + asyncio.run(main()) + else: + # Running as pytest discovery or direct execution without args + print("Training Service Test Runner") + print("=" * 50) + print("Usage:") + print(" python run_tests.py --suite all # Run all test suites") + print(" python run_tests.py --suite unit # Run unit tests only") + print(" python run_tests.py --suite integration # Run integration tests only") + print(" python run_tests.py --suite performance # Run performance tests only") + print(" python run_tests.py --quick # Run quick tests (skip performance)") + print(" python run_tests.py -v # Verbose output") + print() + print("Available test suites:") + runner = TrainingTestRunner() + for suite_name, config in runner.test_suites.items(): + print(f" {suite_name.ljust(15)}: {config['description']}") + print() + + # If no arguments provided, run all tests + if len(sys.argv) == 1: + print("No arguments provided. Running all tests...") + asyncio.run(TrainingTestRunner().run_all_tests()) \ No newline at end of file diff --git a/services/training/tests/test_end_to_end.py b/services/training/tests/test_end_to_end.py new file mode 100644 index 00000000..65c75712 --- /dev/null +++ b/services/training/tests/test_end_to_end.py @@ -0,0 +1,311 @@ +# ================================================================ +# services/training/tests/test_end_to_end.py +# ================================================================ +""" +End-to-End Testing for Training Service +Tests complete workflows from API to ML pipeline to results +""" + +import pytest +import asyncio +import httpx +import pandas as pd +import json +import tempfile +import time +from datetime import datetime, timedelta +from typing import Dict, List, Any +from unittest.mock import patch, AsyncMock +import uuid + +from app.main import app +from app.schemas.training import TrainingJobRequest, SingleProductTrainingRequest + + +class TestTrainingServiceEndToEnd: + """End-to-end tests for complete training workflows""" + + @pytest.fixture + async def test_client(self): + """Create test client for the training service""" + from httpx import AsyncClient + async with AsyncClient(app=app, base_url="http://test") as client: + yield client + + @pytest.fixture + def real_bakery_data(self): + """Use the actual bakery sales data from the uploaded CSV""" + # This fixture would load the real bakery_sales_2023_2024.csv data + # For testing, we'll simulate the structure based on the document description + + # Generate realistic data matching the CSV structure + start_date = datetime(2023, 1, 1) + dates = [start_date + timedelta(days=i) for i in range(365)] + + products = [ + "Pan Integral", "Pan Blanco", "Croissant", "Magdalenas", + "Empanadas", "Tarta Chocolate", "Roscon Reyes", "Palmeras" + ] + + data = [] + for date in dates: + for product in products: + # Realistic sales patterns for Madrid bakery + base_quantity = { + "Pan Integral": 80, "Pan Blanco": 120, "Croissant": 45, + "Magdalenas": 30, "Empanadas": 25, "Tarta Chocolate": 15, + "Roscon Reyes": 8, "Palmeras": 12 + }.get(product, 20) + + # Seasonal variations + if date.month == 12 and product == "Roscon Reyes": + base_quantity *= 5 # Christmas specialty + elif date.month in [6, 7, 8]: # Summer + base_quantity *= 0.85 + elif date.month in [11, 12, 1]: # Winter + base_quantity *= 1.15 + + # Weekly patterns + if date.weekday() >= 5: # Weekends + base_quantity *= 1.3 + elif date.weekday() == 0: # Monday slower + base_quantity *= 0.8 + + # Weather influence + temp = 15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) + if temp > 30: # Very hot days + if product in ["Pan Integral", "Pan Blanco"]: + base_quantity *= 0.7 + elif temp < 5: # Cold days + base_quantity *= 1.1 + + # Add realistic noise + import numpy as np + quantity = max(1, int(base_quantity + np.random.normal(0, base_quantity * 0.15))) + + # Calculate revenue (realistic Spanish bakery prices) + price_per_unit = { + "Pan Integral": 2.80, "Pan Blanco": 2.50, "Croissant": 1.50, + "Magdalenas": 1.20, "Empanadas": 3.50, "Tarta Chocolate": 18.00, + "Roscon Reyes": 25.00, "Palmeras": 1.80 + }.get(product, 2.00) + + revenue = round(quantity * price_per_unit, 2) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": quantity, + "revenue": revenue, + "temperature": round(temp + np.random.normal(0, 3), 1), + "precipitation": max(0, np.random.exponential(0.8)), + "is_weekend": date.weekday() >= 5, + "is_holiday": self._is_spanish_holiday(date) + }) + + return pd.DataFrame(data) + + def _is_spanish_holiday(self, date: datetime) -> bool: + """Check if date is a Spanish holiday""" + spanish_holidays = [ + (1, 1), # AΓ±o Nuevo + (1, 6), # Reyes Magos + (5, 1), # DΓ­a del Trabajo + (8, 15), # AsunciΓ³n de la Virgen + (10, 12), # Fiesta Nacional de EspaΓ±a + (11, 1), # Todos los Santos + (12, 6), # DΓ­a de la ConstituciΓ³n + (12, 8), # Inmaculada ConcepciΓ³n + (12, 25), # Navidad + ] + return (date.month, date.day) in spanish_holidays + + @pytest.fixture + async def mock_external_apis(self): + """Mock external APIs (AEMET and Madrid OpenData)""" + with patch('app.external.aemet.AEMETClient') as mock_aemet, \ + patch('app.external.madrid_opendata.MadridOpenDataClient') as mock_madrid: + + # Mock AEMET weather data + mock_aemet_instance = AsyncMock() + mock_aemet.return_value = mock_aemet_instance + + # Generate realistic Madrid weather data + weather_data = [] + for i in range(365): + date = datetime(2023, 1, 1) + timedelta(days=i) + day_of_year = date.timetuple().tm_yday + # Madrid climate: hot summers, mild winters + base_temp = 14 + 12 * np.sin((day_of_year / 365) * 2 * np.pi) + + weather_data.append({ + "date": date, + "temperature": round(base_temp + np.random.normal(0, 4), 1), + "precipitation": max(0, np.random.exponential(1.2)), + "humidity": np.random.uniform(25, 75), + "wind_speed": np.random.uniform(3, 20), + "pressure": np.random.uniform(995, 1025), + "description": np.random.choice([ + "Soleado", "Parcialmente nublado", "Nublado", + "Lluvia ligera", "Despejado" + ]), + "source": "aemet" + }) + + mock_aemet_instance.get_historical_weather.return_value = weather_data + mock_aemet_instance.get_current_weather.return_value = weather_data[-1] + + # Mock Madrid traffic data + mock_madrid_instance = AsyncMock() + mock_madrid.return_value = mock_madrid_instance + + traffic_data = [] + for i in range(365): + date = datetime(2023, 1, 1) + timedelta(days=i) + + # Multiple measurements per day + for hour in range(6, 22, 2): # Every 2 hours from 6 AM to 10 PM + measurement_time = date.replace(hour=hour) + + # Realistic Madrid traffic patterns + if hour in [7, 8, 9, 18, 19, 20]: # Rush hours + volume = np.random.randint(1200, 2000) + congestion = "high" + speed = np.random.randint(10, 25) + elif hour in [12, 13, 14]: # Lunch time + volume = np.random.randint(800, 1200) + congestion = "medium" + speed = np.random.randint(20, 35) + else: # Off-peak + volume = np.random.randint(300, 800) + congestion = "low" + speed = np.random.randint(30, 50) + + traffic_data.append({ + "date": measurement_time, + "traffic_volume": volume, + "occupation_percentage": np.random.randint(15, 85), + "load_percentage": np.random.randint(25, 90), + "average_speed": speed, + "congestion_level": congestion, + "pedestrian_count": np.random.randint(100, 800), + "measurement_point_id": "MADRID_CENTER_001", + "measurement_point_name": "Puerta del Sol", + "road_type": "URB", + "source": "madrid_opendata" + }) + + mock_madrid_instance.get_historical_traffic.return_value = traffic_data + mock_madrid_instance.get_current_traffic.return_value = traffic_data[-1] + + yield { + 'aemet': mock_aemet_instance, + 'madrid': mock_madrid_instance + } + + @pytest.mark.asyncio + async def test_complete_training_workflow_api( + self, + test_client, + real_bakery_data, + mock_external_apis + ): + """Test complete training workflow through API endpoints""" + + # Step 1: Check service health + health_response = await test_client.get("/health") + assert health_response.status_code == 200 + health_data = health_response.json() + assert health_data["status"] == "healthy" + + # Step 2: Validate training data quality + with patch('app.services.training_service.TrainingService._fetch_sales_data', + return_value=real_bakery_data): + + validation_response = await test_client.post( + "/training/validate", + json={ + "tenant_id": "test_bakery_001", + "include_weather": True, + "include_traffic": True + } + ) + + assert validation_response.status_code == 200 + validation_data = validation_response.json() + assert validation_data["is_valid"] is True + assert validation_data["data_points"] > 1000 # Sufficient data + assert validation_data["missing_percentage"] < 10 + + # Step 3: Start training job for multiple products + training_request = { + "products": ["Pan Integral", "Croissant", "Magdalenas"], + "include_weather": True, + "include_traffic": True, + "config": { + "seasonality_mode": "additive", + "changepoint_prior_scale": 0.05, + "seasonality_prior_scale": 10.0, + "validation_enabled": True + } + } + + with patch('app.services.training_service.TrainingService._fetch_sales_data', + return_value=real_bakery_data): + + start_response = await test_client.post( + "/training/jobs", + json=training_request, + headers={"X-Tenant-ID": "test_bakery_001"} + ) + + assert start_response.status_code == 201 + job_data = start_response.json() + job_id = job_data["job_id"] + assert job_data["status"] == "pending" + + # Step 4: Monitor job progress + max_wait_time = 300 # 5 minutes + start_time = time.time() + + while time.time() - start_time < max_wait_time: + status_response = await test_client.get(f"/training/jobs/{job_id}/status") + assert status_response.status_code == 200 + + status_data = status_response.json() + + if status_data["status"] == "completed": + # Training completed successfully + assert "models_trained" in status_data + assert len(status_data["models_trained"]) == 3 # Three products + + # Check model quality + for model_info in status_data["models_trained"]: + assert "product_name" in model_info + assert "model_id" in model_info + assert "metrics" in model_info + + metrics = model_info["metrics"] + assert "mape" in metrics + assert "rmse" in metrics + assert "mae" in metrics + + # Quality thresholds for bakery data + assert metrics["mape"] < 50, f"MAPE too high for {model_info['product_name']}: {metrics['mape']}" + assert metrics["rmse"] > 0 + + break + elif status_data["status"] == "failed": + pytest.fail(f"Training job failed: {status_data.get('error_message', 'Unknown error')}") + + # Wait before checking again + await asyncio.sleep(10) + else: + pytest.fail(f"Training job did not complete within {max_wait_time} seconds") + + # Step 5: Get detailed job logs + logs_response = await test_client.get(f"/training/jobs/{job_id}/logs") + assert logs_response.status_code == 200 + logs_data = logs_response.json() + assert "logs" in logs_data + assert len(logs_data["logs"]) > 0 \ No newline at end of file diff --git a/services/training/tests/test_ml_pipeline_integration.py b/services/training/tests/test_ml_pipeline_integration.py new file mode 100644 index 00000000..e69de29b diff --git a/services/training/tests/test_performance.py b/services/training/tests/test_performance.py new file mode 100644 index 00000000..a7ba60bf --- /dev/null +++ b/services/training/tests/test_performance.py @@ -0,0 +1,630 @@ +# ================================================================ +# services/training/tests/test_performance.py +# ================================================================ +""" +Performance and Load Testing for Training Service +Tests training performance with real-world data volumes +""" + +import pytest +import asyncio +import pandas as pd +import numpy as np +import time +from datetime import datetime, timedelta +from concurrent.futures import ThreadPoolExecutor +import psutil +import gc +from typing import List, Dict, Any +import logging + +from app.ml.trainer import BakeryMLTrainer +from app.ml.data_processor import BakeryDataProcessor +from app.services.training_service import TrainingService + + +class TestTrainingPerformance: + """Performance tests for training service components""" + + @pytest.fixture + def large_sales_dataset(self): + """Generate large dataset for performance testing (2 years of data)""" + start_date = datetime(2022, 1, 1) + end_date = datetime(2024, 1, 1) + + date_range = pd.date_range(start=start_date, end=end_date, freq='D') + products = [ + "Pan Integral", "Pan Blanco", "Croissant", "Magdalenas", + "Empanadas", "Tarta Chocolate", "Roscon Reyes", "Palmeras", + "Donuts", "Berlinas", "Napolitanas", "Ensaimadas" + ] + + data = [] + for date in date_range: + for product in products: + # Realistic sales simulation + base_quantity = np.random.randint(5, 150) + + # Seasonal patterns + if date.month in [12, 1]: # Winter/Holiday season + base_quantity *= 1.4 + elif date.month in [6, 7, 8]: # Summer + base_quantity *= 0.8 + + # Weekly patterns + if date.weekday() >= 5: # Weekends + base_quantity *= 1.2 + elif date.weekday() == 0: # Monday + base_quantity *= 0.7 + + # Add noise + quantity = max(1, int(base_quantity + np.random.normal(0, base_quantity * 0.1))) + + data.append({ + "date": date.strftime("%Y-%m-%d"), + "product": product, + "quantity": quantity, + "revenue": round(quantity * np.random.uniform(1.5, 8.0), 2), + "temperature": round(15 + 12 * np.sin((date.timetuple().tm_yday / 365) * 2 * np.pi) + np.random.normal(0, 3), 1), + "precipitation": max(0, np.random.exponential(0.8)), + "is_weekend": date.weekday() >= 5, + "is_holiday": self._is_spanish_holiday(date) + }) + + return pd.DataFrame(data) + + def _is_spanish_holiday(self, date: datetime) -> bool: + """Check if date is a Spanish holiday""" + holidays = [ + (1, 1), # New Year + (1, 6), # Epiphany + (5, 1), # Labor Day + (8, 15), # Assumption + (10, 12), # National Day + (11, 1), # All Saints + (12, 6), # Constitution Day + (12, 8), # Immaculate Conception + (12, 25), # Christmas + ] + return (date.month, date.day) in holidays + + @pytest.mark.asyncio + async def test_single_product_training_performance(self, large_sales_dataset): + """Test performance of single product training with large dataset""" + + trainer = BakeryMLTrainer() + product_data = large_sales_dataset[large_sales_dataset['product'] == 'Pan Integral'].copy() + + # Measure memory before training + process = psutil.Process() + memory_before = process.memory_info().rss / 1024 / 1024 # MB + + start_time = time.time() + + result = await trainer.train_single_product( + tenant_id="perf_test_tenant", + product_name="Pan Integral", + sales_data=product_data, + config={ + "include_weather": True, + "include_traffic": False, # Skip traffic for performance + "seasonality_mode": "additive" + } + ) + + end_time = time.time() + training_duration = end_time - start_time + + # Measure memory after training + memory_after = process.memory_info().rss / 1024 / 1024 # MB + memory_used = memory_after - memory_before + + # Performance assertions + assert training_duration < 120, f"Training took too long: {training_duration:.2f}s" + assert memory_used < 500, f"Memory usage too high: {memory_used:.2f}MB" + assert result['status'] == 'completed' + + # Quality assertions + metrics = result['metrics'] + assert metrics['mape'] < 50, f"MAPE too high: {metrics['mape']:.2f}%" + + print(f"Performance Results:") + print(f" Training Duration: {training_duration:.2f}s") + print(f" Memory Used: {memory_used:.2f}MB") + print(f" Data Points: {len(product_data)}") + print(f" MAPE: {metrics['mape']:.2f}%") + print(f" RMSE: {metrics['rmse']:.2f}") + + @pytest.mark.asyncio + async def test_concurrent_training_performance(self, large_sales_dataset): + """Test performance of concurrent training jobs""" + + trainer = BakeryMLTrainer() + products = ["Pan Integral", "Croissant", "Magdalenas"] + + async def train_product(product_name: str): + """Train a single product""" + product_data = large_sales_dataset[large_sales_dataset['product'] == product_name].copy() + + start_time = time.time() + result = await trainer.train_single_product( + tenant_id=f"concurrent_test_{product_name.replace(' ', '_').lower()}", + product_name=product_name, + sales_data=product_data, + config={"include_weather": True, "include_traffic": False} + ) + end_time = time.time() + + return { + 'product': product_name, + 'duration': end_time - start_time, + 'status': result['status'], + 'metrics': result.get('metrics', {}) + } + + # Run concurrent training + start_time = time.time() + tasks = [train_product(product) for product in products] + results = await asyncio.gather(*tasks) + total_time = time.time() - start_time + + # Verify all trainings completed + for result in results: + assert result['status'] == 'completed' + assert result['duration'] < 120 # Individual training time + + # Concurrent execution should be faster than sequential + sequential_time_estimate = sum(r['duration'] for r in results) + efficiency = sequential_time_estimate / total_time + + assert efficiency > 1.5, f"Concurrency efficiency too low: {efficiency:.2f}x" + + print(f"Concurrent Training Results:") + print(f" Total Time: {total_time:.2f}s") + print(f" Sequential Estimate: {sequential_time_estimate:.2f}s") + print(f" Efficiency: {efficiency:.2f}x") + + for result in results: + print(f" {result['product']}: {result['duration']:.2f}s, MAPE: {result['metrics'].get('mape', 'N/A'):.2f}%") + + @pytest.mark.asyncio + async def test_data_processing_scalability(self, large_sales_dataset): + """Test data processing performance with increasing data sizes""" + + data_processor = BakeryDataProcessor() + + # Test with different data sizes + data_sizes = [1000, 5000, 10000, 20000, len(large_sales_dataset)] + performance_results = [] + + for size in data_sizes: + # Take a sample of the specified size + sample_data = large_sales_dataset.head(size).copy() + + start_time = time.time() + + # Process the data + processed_data = await data_processor.prepare_training_data( + sales_data=sample_data, + include_weather=True, + include_traffic=True, + tenant_id="scalability_test", + product_name="Pan Integral" + ) + + processing_time = time.time() - start_time + + performance_results.append({ + 'data_size': size, + 'processing_time': processing_time, + 'processed_rows': len(processed_data), + 'throughput': size / processing_time if processing_time > 0 else 0 + }) + + # Verify linear or sub-linear scaling + for i in range(1, len(performance_results)): + prev_result = performance_results[i-1] + curr_result = performance_results[i] + + size_ratio = curr_result['data_size'] / prev_result['data_size'] + time_ratio = curr_result['processing_time'] / prev_result['processing_time'] + + # Processing time should scale better than linearly + assert time_ratio < size_ratio * 1.5, f"Poor scaling at size {curr_result['data_size']}" + + print("Data Processing Scalability Results:") + for result in performance_results: + print(f" Size: {result['data_size']:,} rows, Time: {result['processing_time']:.2f}s, " + f"Throughput: {result['throughput']:.0f} rows/s") + + @pytest.mark.asyncio + async def test_memory_usage_optimization(self, large_sales_dataset): + """Test memory usage optimization during training""" + + trainer = BakeryMLTrainer() + process = psutil.Process() + + # Baseline memory + gc.collect() # Force garbage collection + baseline_memory = process.memory_info().rss / 1024 / 1024 # MB + + memory_snapshots = [{'stage': 'baseline', 'memory_mb': baseline_memory}] + + # Load data + product_data = large_sales_dataset[large_sales_dataset['product'] == 'Pan Integral'].copy() + current_memory = process.memory_info().rss / 1024 / 1024 + memory_snapshots.append({'stage': 'data_loaded', 'memory_mb': current_memory}) + + # Train model + result = await trainer.train_single_product( + tenant_id="memory_test_tenant", + product_name="Pan Integral", + sales_data=product_data, + config={"include_weather": True, "include_traffic": True} + ) + + current_memory = process.memory_info().rss / 1024 / 1024 + memory_snapshots.append({'stage': 'model_trained', 'memory_mb': current_memory}) + + # Cleanup + del product_data + del result + gc.collect() + + final_memory = process.memory_info().rss / 1024 / 1024 + memory_snapshots.append({'stage': 'cleanup', 'memory_mb': final_memory}) + + # Memory assertions + peak_memory = max(snapshot['memory_mb'] for snapshot in memory_snapshots) + memory_increase = peak_memory - baseline_memory + memory_after_cleanup = final_memory - baseline_memory + + assert memory_increase < 800, f"Peak memory increase too high: {memory_increase:.2f}MB" + assert memory_after_cleanup < 100, f"Memory not properly cleaned up: {memory_after_cleanup:.2f}MB" + + print("Memory Usage Analysis:") + for snapshot in memory_snapshots: + print(f" {snapshot['stage']}: {snapshot['memory_mb']:.2f}MB") + print(f" Peak increase: {memory_increase:.2f}MB") + print(f" After cleanup: {memory_after_cleanup:.2f}MB") + + @pytest.mark.asyncio + async def test_training_service_throughput(self, large_sales_dataset): + """Test training service throughput with multiple requests""" + + training_service = TrainingService() + + # Simulate multiple training requests + num_requests = 5 + products = ["Pan Integral", "Croissant", "Magdalenas", "Empanadas", "Tarta Chocolate"] + + async def execute_training_request(request_id: int, product: str): + """Execute a single training request""" + product_data = large_sales_dataset[large_sales_dataset['product'] == product].copy() + + with patch.object(training_service, '_fetch_sales_data', return_value=product_data): + start_time = time.time() + + result = await training_service.execute_training_job( + db=None, # Mock DB session + tenant_id=f"throughput_test_tenant_{request_id}", + job_id=f"job_{request_id}_{product.replace(' ', '_').lower()}", + request={ + 'products': [product], + 'include_weather': True, + 'include_traffic': False, + 'config': {'seasonality_mode': 'additive'} + } + ) + + duration = time.time() - start_time + return { + 'request_id': request_id, + 'product': product, + 'duration': duration, + 'status': result.get('status', 'unknown'), + 'models_trained': len(result.get('models_trained', [])) + } + + # Execute requests concurrently + start_time = time.time() + tasks = [ + execute_training_request(i, products[i % len(products)]) + for i in range(num_requests) + ] + results = await asyncio.gather(*tasks) + total_time = time.time() - start_time + + # Calculate throughput metrics + successful_requests = sum(1 for r in results if r['status'] == 'completed') + throughput = successful_requests / total_time # requests per second + + # Performance assertions + assert successful_requests >= num_requests * 0.8, "Too many failed requests" + assert throughput >= 0.1, f"Throughput too low: {throughput:.3f} req/s" + assert total_time < 300, f"Total time too long: {total_time:.2f}s" + + print(f"Training Service Throughput Results:") + print(f" Total Requests: {num_requests}") + print(f" Successful: {successful_requests}") + print(f" Total Time: {total_time:.2f}s") + print(f" Throughput: {throughput:.3f} req/s") + print(f" Average Request Time: {total_time/num_requests:.2f}s") + + @pytest.mark.asyncio + async def test_large_dataset_edge_cases(self, large_sales_dataset): + """Test handling of edge cases with large datasets""" + + data_processor = BakeryDataProcessor() + + # Test 1: Dataset with many missing values + corrupted_data = large_sales_dataset.copy() + # Introduce 30% missing values randomly + mask = np.random.random(len(corrupted_data)) < 0.3 + corrupted_data.loc[mask, 'quantity'] = np.nan + + start_time = time.time() + result = await data_processor.validate_data_quality(corrupted_data) + validation_time = time.time() - start_time + + assert validation_time < 10, f"Validation too slow: {validation_time:.2f}s" + assert result['is_valid'] is False + assert 'high_missing_data' in result['issues'] + + # Test 2: Dataset with extreme outliers + outlier_data = large_sales_dataset.copy() + # Add extreme outliers (100x normal values) + outlier_indices = np.random.choice(len(outlier_data), size=int(len(outlier_data) * 0.01), replace=False) + outlier_data.loc[outlier_indices, 'quantity'] *= 100 + + start_time = time.time() + cleaned_data = await data_processor.clean_outliers(outlier_data) + cleaning_time = time.time() - start_time + + assert cleaning_time < 15, f"Outlier cleaning too slow: {cleaning_time:.2f}s" + assert len(cleaned_data) > len(outlier_data) * 0.95 # Should retain most data + + # Test 3: Very sparse data (many products with few sales) + sparse_data = large_sales_dataset.copy() + # Keep only 10% of data for each product randomly + sparse_data = sparse_data.groupby('product').apply( + lambda x: x.sample(n=max(1, int(len(x) * 0.1))) + ).reset_index(drop=True) + + start_time = time.time() + validation_result = await data_processor.validate_data_quality(sparse_data) + sparse_validation_time = time.time() - start_time + + assert sparse_validation_time < 5, f"Sparse data validation too slow: {sparse_validation_time:.2f}s" + + print("Edge Case Performance Results:") + print(f" Corrupted data validation: {validation_time:.2f}s") + print(f" Outlier cleaning: {cleaning_time:.2f}s") + print(f" Sparse data validation: {sparse_validation_time:.2f}s") + + +class TestTrainingServiceLoad: + """Load testing for training service under stress""" + + @pytest.mark.asyncio + async def test_sustained_load_training(self, large_sales_dataset): + """Test training service under sustained load""" + + trainer = BakeryMLTrainer() + + # Define load test parameters + duration_minutes = 2 # Run for 2 minutes + requests_per_minute = 3 + + products = ["Pan Integral", "Croissant", "Magdalenas"] + + async def sustained_training_worker(worker_id: int, duration: float): + """Worker that continuously submits training requests""" + start_time = time.time() + completed_requests = 0 + failed_requests = 0 + + while time.time() - start_time < duration: + try: + product = products[completed_requests % len(products)] + product_data = large_sales_dataset[ + large_sales_dataset['product'] == product + ].copy() + + result = await trainer.train_single_product( + tenant_id=f"load_test_worker_{worker_id}", + product_name=product, + sales_data=product_data, + config={"include_weather": False, "include_traffic": False} # Minimal config for speed + ) + + if result['status'] == 'completed': + completed_requests += 1 + else: + failed_requests += 1 + + except Exception as e: + failed_requests += 1 + logging.error(f"Training request failed: {e}") + + # Wait before next request + await asyncio.sleep(60 / requests_per_minute) + + return { + 'worker_id': worker_id, + 'completed': completed_requests, + 'failed': failed_requests, + 'duration': time.time() - start_time + } + + # Start multiple workers + num_workers = 2 + duration_seconds = duration_minutes * 60 + + start_time = time.time() + tasks = [ + sustained_training_worker(i, duration_seconds) + for i in range(num_workers) + ] + results = await asyncio.gather(*tasks) + total_time = time.time() - start_time + + # Analyze results + total_completed = sum(r['completed'] for r in results) + total_failed = sum(r['failed'] for r in results) + success_rate = total_completed / (total_completed + total_failed) if (total_completed + total_failed) > 0 else 0 + + # Performance assertions + assert success_rate >= 0.8, f"Success rate too low: {success_rate:.2%}" + assert total_completed >= duration_minutes * requests_per_minute * num_workers * 0.7, "Throughput too low" + + print(f"Sustained Load Test Results:") + print(f" Duration: {total_time:.2f}s") + print(f" Workers: {num_workers}") + print(f" Completed Requests: {total_completed}") + print(f" Failed Requests: {total_failed}") + print(f" Success Rate: {success_rate:.2%}") + print(f" Average Throughput: {total_completed/total_time:.2f} req/s") + + @pytest.mark.asyncio + async def test_resource_exhaustion_recovery(self, large_sales_dataset): + """Test service recovery from resource exhaustion""" + + trainer = BakeryMLTrainer() + + # Simulate resource exhaustion by running many concurrent requests + num_concurrent = 10 # High concurrency to stress the system + + async def resource_intensive_task(task_id: int): + """Task designed to consume resources""" + try: + # Use all products to increase memory usage + all_products_data = large_sales_dataset.copy() + + result = await trainer.train_tenant_models( + tenant_id=f"resource_test_{task_id}", + sales_data=all_products_data, + config={ + "train_all_products": True, + "include_weather": True, + "include_traffic": True + } + ) + + return {'task_id': task_id, 'status': 'completed', 'error': None} + + except Exception as e: + return {'task_id': task_id, 'status': 'failed', 'error': str(e)} + + # Launch all tasks simultaneously + start_time = time.time() + tasks = [resource_intensive_task(i) for i in range(num_concurrent)] + results = await asyncio.gather(*tasks, return_exceptions=True) + duration = time.time() - start_time + + # Analyze results + completed = sum(1 for r in results if isinstance(r, dict) and r['status'] == 'completed') + failed = sum(1 for r in results if isinstance(r, dict) and r['status'] == 'failed') + exceptions = sum(1 for r in results if isinstance(r, Exception)) + + # The system should handle some failures gracefully + # but should complete at least some requests + total_processed = completed + failed + exceptions + processing_rate = total_processed / num_concurrent + + assert processing_rate >= 0.5, f"Too many requests not processed: {processing_rate:.2%}" + assert duration < 600, f"Recovery took too long: {duration:.2f}s" # 10 minutes max + + print(f"Resource Exhaustion Test Results:") + print(f" Concurrent Requests: {num_concurrent}") + print(f" Completed: {completed}") + print(f" Failed: {failed}") + print(f" Exceptions: {exceptions}") + print(f" Duration: {duration:.2f}s") + print(f" Processing Rate: {processing_rate:.2%}") + + +# ================================================================ +# BENCHMARK UTILITIES +# ================================================================ + +class PerformanceBenchmark: + """Utility class for performance benchmarking""" + + @staticmethod + def measure_execution_time(func): + """Decorator to measure execution time""" + async def wrapper(*args, **kwargs): + start_time = time.time() + result = await func(*args, **kwargs) + execution_time = time.time() - start_time + + if hasattr(result, 'update') and isinstance(result, dict): + result['execution_time'] = execution_time + + return result + return wrapper + + @staticmethod + def memory_profiler(func): + """Decorator to profile memory usage""" + async def wrapper(*args, **kwargs): + process = psutil.Process() + + # Memory before + gc.collect() + memory_before = process.memory_info().rss / 1024 / 1024 + + result = await func(*args, **kwargs) + + # Memory after + memory_after = process.memory_info().rss / 1024 / 1024 + memory_used = memory_after - memory_before + + if hasattr(result, 'update') and isinstance(result, dict): + result['memory_used_mb'] = memory_used + + return result + return wrapper + + +# ================================================================ +# STANDALONE EXECUTION +# ================================================================ + +if __name__ == "__main__": + """ + Run performance tests as standalone script + Usage: python test_performance.py + """ + import sys + import os + from unittest.mock import patch + + # Add the training service root to Python path + training_service_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, training_service_root) + + print("=" * 60) + print("TRAINING SERVICE PERFORMANCE TEST SUITE") + print("=" * 60) + + # Setup logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # Run performance tests + pytest.main([ + __file__, + "-v", + "--tb=short", + "-s", # Don't capture output + "--durations=10", # Show 10 slowest tests + "-m", "not slow", # Skip slow tests unless specifically requested + ]) + + print("\n" + "=" * 60) + print("PERFORMANCE TESTING COMPLETE") + print("=" * 60) \ No newline at end of file