| """ |
| Test data generators for creating realistic test scenarios. |
| """ |
|
|
| import uuid |
| import random |
| from datetime import date, datetime, timedelta |
| import numpy as np |
| from faker import Faker |
|
|
| fake = Faker() |
|
|
| def generate_drivers(count: int = 50, ev_ratio: float = 0.2) -> list: |
| """ |
| Generate realistic driver data. |
| |
| Args: |
| count: Number of drivers to generate |
| ev_ratio: Percentage of drivers that are EV (0.0 to 1.0) |
| |
| Returns: |
| List of dictionaries containing driver data |
| """ |
| drivers = [] |
| |
| for i in range(count): |
| is_ev = i < (count * ev_ratio) |
| driver_id = f"drv_{i:03d}" |
| |
| drivers.append({ |
| "id": driver_id, |
| "name": fake.name(), |
| "vehicle_capacity_kg": 100.0 if is_ev else 150.0, |
| "preferred_language": random.choice(["en", "ta", "hi", "te", "kn"]), |
| |
| "gameified_experience_points": random.randint(100, 5000), |
| "is_ev": is_ev, |
| "battery_range_km": 120.0 if is_ev else None, |
| "charging_time_minutes": 45 if is_ev else None, |
| "experience_years": np.random.uniform(0.5, 5.0), |
| "complexity_debt": np.random.uniform(0, 3.0) |
| }) |
| |
| |
| random.shuffle(drivers) |
| return drivers |
|
|
| def generate_routes(count: int = 50, difficulty_levels: list = [1.0, 1.5, 2.0, 2.5, 3.0]) -> list: |
| """ |
| Generate realistic route data. |
| |
| Args: |
| count: Number of routes to generate |
| difficulty_levels: List of difficulty multipliers |
| |
| Returns: |
| List of dictionaries containing route data |
| """ |
| routes = [] |
| |
| for i in range(count): |
| route_id = f"rt_{i:03d}" |
| diff = difficulty_levels[i % len(difficulty_levels)] |
| |
| routes.append({ |
| "route_id": route_id, |
| "cluster_id": i, |
| "stops": int(8 + diff * 4), |
| "total_weight_kg": 40 + diff * 20, |
| "total_distance_km": 35 + diff * 15, |
| "parking_difficulty": diff, |
| "traffic_complexity": diff, |
| "estimated_time_minutes": int((35 + diff * 15) * 4) |
| }) |
| |
| return routes |
|
|
| def generate_packages(count: int = 50, center_lat: float = 12.9716, center_lng: float = 77.5946) -> list: |
| """ |
| Generate realistic package data. |
| |
| Args: |
| count: Number of packages |
| center_lat: Warehouse latitude |
| center_lng: Warehouse longitude |
| |
| Returns: |
| List of dictionaries valid for PackageInput schema |
| """ |
| packages = [] |
| |
| for i in range(count): |
| |
| lat_offset = random.uniform(-0.1, 0.1) |
| lng_offset = random.uniform(-0.1, 0.1) |
| |
| packages.append({ |
| "id": f"pkg_{i:04d}", |
| "weight_kg": round(random.uniform(0.5, 15.0), 1), |
| "fragility_level": random.randint(1, 5), |
| "address": fake.address(), |
| "latitude": center_lat + lat_offset, |
| "longitude": center_lng + lng_offset, |
| "priority": random.choice(["NORMAL", "NORMAL", "NORMAL", "HIGH", "EXPRESS"]) |
| }) |
| |
| return packages |
|
|
| def generate_allocation_request(drivers: list, routes: list, date_str: str = None) -> dict: |
| """ |
| Generate a complete allocation request payload. |
| Note: This transforms the internal route/driver structures into the API schema format. |
| |
| Args: |
| drivers: List of driver dicts from generate_drivers |
| routes: List of route dicts from generate_routes |
| date_str: Date string for allocation (YYYY-MM-DD) |
| |
| Returns: |
| Dictionary matching AllocationRequest schema |
| """ |
| if not date_str: |
| date_str = date.today().isoformat() |
| |
| |
| |
| |
| |
| |
| |
| |
| total_packages_needed = sum(r["stops"] * 2 for r in routes) |
| packages = generate_packages(total_packages_needed) |
| |
| |
| api_drivers = [] |
| for d in drivers: |
| api_drivers.append({ |
| "id": d["id"], |
| "name": d["name"], |
| "vehicle_capacity_kg": d["vehicle_capacity_kg"], |
| "preferred_language": d["preferred_language"] |
| }) |
| |
| return { |
| "allocation_date": date_str, |
| "warehouse": {"lat": 12.9716, "lng": 77.5946}, |
| "packages": packages, |
| "drivers": api_drivers |
| } |
|
|