431 lines
17 KiB
Python
431 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Script para gerar 500 dados mockados de rotas para o ERP SAAS PraFrota
|
|
Baseado nas especificações da documentação técnica
|
|
"""
|
|
|
|
import json
|
|
import random
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict, Any
|
|
|
|
# Dados reais extraídos do CSV
|
|
REAL_PLATES = [
|
|
"TAS4J92", "MSO5821", "TAS2F98", "RJZ7H79", "TAO3J98",
|
|
"TAN6I73", "SGD4H03", "NGF2A53", "TAS2F32", "RTT1B46",
|
|
"EZQ2E60", "TDZ4J93", "SGL8D98", "TAS2F83", "RVC0J58",
|
|
"EYP4H76", "FVV7660", "RUN2B51", "RUQ9D16", "TAS5A49",
|
|
"RUN2B49", "SHX0J21", "FHT5D54", "SVG0I32", "RUN2B50",
|
|
"FYU9G72", "TAS4J93", "SRZ9B83", "TAQ4G32", "RUP2B50",
|
|
"SRG6H41", "SQX8J75", "TAS4J96", "RTT1B44", "RTM9F10",
|
|
"FLE2F99", "RUN2B63", "RVC0J65", "RUN2B52", "TUE1A37",
|
|
"RUP4H86", "RUP4H94", "RUN2B48", "SVF4I52", "STL5A43",
|
|
"TAS2J46", "TAO3I97", "TAS5A46", "SUT1B94", "LUJ7E05",
|
|
"SST4C72", "SRH6C66", "TAO6E76", "RUN2B55", "RVC8B13",
|
|
"SVF2E84", "SRO2J16", "RVT2J97", "RUN2B58", "SHB4B37",
|
|
"IWB9C17", "FJE7I82", "TAQ4G22", "SGJ9F81", "SVP9H73",
|
|
"OVM5B05", "TAO3J94", "RUP2B56", "TAO4F04", "RUN2B64",
|
|
"GGL2J42", "SRN7H36", "SFM8D30", "TAO6E80", "SVK8G96",
|
|
"SIA7J06", "TAR3E11", "RVC0J64", "RJW6G71", "SSV6C52",
|
|
"RUN2B54", "TAN6I66", "SPA0001", "SVH9G53", "RUN2B62",
|
|
"RVC0J85", "TAR3D02", "RVC4G70", "RUP4H92", "RUN2B56",
|
|
"SGL8F08", "TAO3J93", "LUC4H25", "TAN6H93", "TAQ4G30",
|
|
"RUP4H87", "SHB4B36", "SGC2B17", "RVC0J70", "SVL1G82",
|
|
"RVC0J63", "RVT2J98", "SPA0001", "RVT4F18", "TAR3C45",
|
|
"TAO4E80", "TAN6I62", "SHB4B38", "RTO9B22", "RJE8B51",
|
|
"TAO4F02", "SGJ9G23", "SRU2H94", "RTT1B48", "TAN6I69",
|
|
"RUP2B49", "RUW9C02", "RUP4H91", "RVC0J74", "TAN6H99",
|
|
"FZG8F72", "RUP4H88", "TAS2E35", "RUN2B60", "RTO9B84",
|
|
"GHM7A76", "RTM9F11", "TAN6H97", "SQX9G04", "RVU9160",
|
|
"SGL8E65", "RTT1B43", "TAO4F05", "TOG3H62", "TAS5A47",
|
|
"TAQ6J50", "SRH4E56", "NSZ5318", "RUN2B53", "TAO3J97",
|
|
"SGL8E73", "SHX0J22", "SFP6G82", "SRZ9C22", "RTT1B45",
|
|
"TAN6163", "LTO7G84", "SGL8D26", "TAN6I59", "TAO4E89",
|
|
"TAO4E90", "TAS2J51", "SGL8F81", "RTM9F14", "FKP9A34",
|
|
"TAS2J45", "QUS3C30", "GDM8I81", "TAQ4G36", "RVC0J59",
|
|
"TAS5A44", "RUN2B61", "RVC4G71", "TAS4J95", "TAQ4G37",
|
|
"SPA0001", "RTB7E19", "TAS2E31", "RUP4H81", "SGD9A92",
|
|
"RJF7I82", "EVU9280", "SPA0001", "SSC1E94", "TAR3E21",
|
|
"TAN6I71", "TAS4J92", "TAN6I57", "TAO4F90", "SGJ2F13",
|
|
"SGJ2D96", "SGJ2G40", "TAR3E14", "KRQ9A48", "RUP2B53",
|
|
"SRN5C38", "SGJ2G98", "SRA7J03", "RIU1G19", "EUQ4159",
|
|
"SRH5C60", "SSB6H85", "SRN6F73", "SRY4B65", "SGL8C62",
|
|
"STU7F45", "SGJ9G45", "RVT4F19"
|
|
]
|
|
|
|
PRODUCT_TYPES = [
|
|
"Medicamentos", "Eletrônicos", "Alimentos Perecíveis", "Alimentos Não Perecíveis",
|
|
"Roupas e Acessórios", "Livros e Papelaria", "Casa e Decoração",
|
|
"Cosméticos", "Automotive", "Brinquedos"
|
|
]
|
|
|
|
MARKETPLACES = ["Mercado Livre", "Shopee", "Amazon"]
|
|
|
|
# Coordenadas das regiões
|
|
REGIONS = {
|
|
"rioDeJaneiro": {
|
|
"center": {"lat": -22.9068, "lng": -43.1729},
|
|
"bounds": {"north": -22.7000, "south": -23.1000, "east": -43.0000, "west": -43.8000},
|
|
"addresses": [
|
|
"Rua das Flores, 123 - Copacabana, Rio de Janeiro - RJ",
|
|
"Av. Atlântica, 456 - Ipanema, Rio de Janeiro - RJ",
|
|
"Rua Barata Ribeiro, 789 - Copacabana, Rio de Janeiro - RJ",
|
|
"Av. Nossa Senhora de Copacabana, 321 - Copacabana, Rio de Janeiro - RJ",
|
|
"Rua Visconde de Pirajá, 654 - Ipanema, Rio de Janeiro - RJ",
|
|
"Av. Rio Branco, 987 - Centro, Rio de Janeiro - RJ",
|
|
"Rua da Carioca, 147 - Centro, Rio de Janeiro - RJ",
|
|
"Av. Presidente Vargas, 258 - Centro, Rio de Janeiro - RJ",
|
|
"Rua Santa Clara, 369 - Copacabana, Rio de Janeiro - RJ",
|
|
"Av. Princesa Isabel, 741 - Copacabana, Rio de Janeiro - RJ"
|
|
]
|
|
},
|
|
"saoPaulo": {
|
|
"center": {"lat": -23.5505, "lng": -46.6333},
|
|
"bounds": {"north": -23.3000, "south": -23.8000, "east": -46.3000, "west": -47.0000},
|
|
"addresses": [
|
|
"Av. Paulista, 1578 - Bela Vista, São Paulo - SP",
|
|
"Rua Augusta, 1000 - Consolação, São Paulo - SP",
|
|
"Av. Faria Lima, 2000 - Pinheiros, São Paulo - SP",
|
|
"Rua Oscar Freire, 500 - Jardins, São Paulo - SP",
|
|
"Av. Rebouças, 3000 - Pinheiros, São Paulo - SP",
|
|
"Rua da Consolação, 1200 - Consolação, São Paulo - SP",
|
|
"Av. Brigadeiro Faria Lima, 1500 - Jardim Paulistano, São Paulo - SP",
|
|
"Rua Haddock Lobo, 800 - Cerqueira César, São Paulo - SP",
|
|
"Av. Nove de Julho, 2500 - Jardim Paulista, São Paulo - SP",
|
|
"Rua Estados Unidos, 600 - Jardim América, São Paulo - SP"
|
|
]
|
|
},
|
|
"minasGerais": {
|
|
"center": {"lat": -19.9167, "lng": -43.9345},
|
|
"bounds": {"north": -19.7000, "south": -20.2000, "east": -43.7000, "west": -44.2000},
|
|
"addresses": [
|
|
"Av. Afonso Pena, 1000 - Centro, Belo Horizonte - MG",
|
|
"Rua da Bahia, 500 - Centro, Belo Horizonte - MG",
|
|
"Av. do Contorno, 2000 - Santa Efigênia, Belo Horizonte - MG",
|
|
"Rua Rio de Janeiro, 800 - Centro, Belo Horizonte - MG",
|
|
"Av. Amazonas, 1500 - Centro, Belo Horizonte - MG",
|
|
"Rua Curitiba, 300 - Centro, Belo Horizonte - MG",
|
|
"Av. Brasil, 2500 - Santa Efigênia, Belo Horizonte - MG",
|
|
"Rua Tupis, 600 - Centro, Belo Horizonte - MG",
|
|
"Av. Francisco Sales, 1200 - Santa Efigênia, Belo Horizonte - MG",
|
|
"Rua Espírito Santo, 400 - Centro, Belo Horizonte - MG"
|
|
]
|
|
},
|
|
"vitoria": {
|
|
"center": {"lat": -20.2976, "lng": -40.2958},
|
|
"bounds": {"north": -20.1000, "south": -20.5000, "east": -40.1000, "west": -40.5000},
|
|
"addresses": [
|
|
"Av. Princesa Isabel, 500 - Centro, Vitória - ES",
|
|
"Rua Sete de Setembro, 200 - Centro, Vitória - ES",
|
|
"Av. Jerônimo Monteiro, 800 - Centro, Vitória - ES",
|
|
"Rua do Comércio, 300 - Centro, Vitória - ES",
|
|
"Av. Marechal Mascarenhas de Moraes, 1000 - Bento Ferreira, Vitória - ES",
|
|
"Rua General Osório, 150 - Centro, Vitória - ES",
|
|
"Av. Nossa Senhora da Penha, 600 - Santa Lúcia, Vitória - ES",
|
|
"Rua Chapot Presvot, 400 - Praia do Canto, Vitória - ES",
|
|
"Av. Saturnino de Brito, 900 - Praia do Canto, Vitória - ES",
|
|
"Rua Joaquim Lírio, 250 - Praia do Canto, Vitória - ES"
|
|
]
|
|
}
|
|
}
|
|
|
|
# Nomes fictícios
|
|
FIRST_NAMES = [
|
|
"João", "Maria", "José", "Ana", "Carlos", "Fernanda", "Pedro", "Juliana",
|
|
"Roberto", "Mariana", "Ricardo", "Camila", "Marcos", "Patrícia", "André",
|
|
"Luciana", "Fernando", "Carla", "Rafael", "Daniela", "Paulo", "Renata",
|
|
"Gustavo", "Vanessa", "Bruno", "Cristina", "Diego", "Tatiana", "Felipe",
|
|
"Amanda", "Rodrigo", "Priscila", "Thiago", "Natália", "Leonardo", "Bianca"
|
|
]
|
|
|
|
LAST_NAMES = [
|
|
"Silva", "Santos", "Oliveira", "Souza", "Rodrigues", "Ferreira", "Alves",
|
|
"Pereira", "Lima", "Gomes", "Costa", "Ribeiro", "Martins", "Carvalho",
|
|
"Almeida", "Lopes", "Soares", "Fernandes", "Vieira", "Barbosa", "Rocha",
|
|
"Dias", "Monteiro", "Cardoso", "Reis", "Araújo", "Moreira", "Freitas",
|
|
"Mendes", "Ramos", "Castro", "Pinto", "Teixeira", "Correia", "Machado"
|
|
]
|
|
|
|
def generate_random_name():
|
|
"""Gera um nome aleatório"""
|
|
return f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}"
|
|
|
|
def generate_phone(area_code):
|
|
"""Gera um telefone aleatório"""
|
|
return f"+55 {area_code} {random.randint(90000, 99999)}-{random.randint(1000, 9999)}"
|
|
|
|
def generate_coordinates_in_region(region_name):
|
|
"""Gera coordenadas aleatórias dentro de uma região"""
|
|
region = REGIONS[region_name]
|
|
bounds = region["bounds"]
|
|
|
|
lat = random.uniform(bounds["south"], bounds["north"])
|
|
lng = random.uniform(bounds["west"], bounds["east"])
|
|
|
|
return {"lat": round(lat, 6), "lng": round(lng, 6)}
|
|
|
|
def generate_route_data(route_id: int, route_type: str, region: str) -> Dict[str, Any]:
|
|
"""Gera dados de uma rota específica"""
|
|
|
|
# Definir códigos de área por região
|
|
area_codes = {
|
|
"rioDeJaneiro": "21",
|
|
"saoPaulo": "11",
|
|
"minasGerais": "31",
|
|
"vitoria": "27"
|
|
}
|
|
|
|
area_code = area_codes[region]
|
|
|
|
# Gerar coordenadas
|
|
origin_coords = generate_coordinates_in_region(region)
|
|
dest_coords = generate_coordinates_in_region(region)
|
|
|
|
# Selecionar endereços
|
|
addresses = REGIONS[region]["addresses"]
|
|
origin_address = random.choice(addresses)
|
|
dest_address = random.choice(addresses)
|
|
|
|
# Para lastMile, usar endereços residenciais
|
|
if route_type == "lastMile":
|
|
marketplace = random.choice(MARKETPLACES)
|
|
origin_address = f"Hub {marketplace} - {region.title()}"
|
|
dest_address = random.choice(addresses) # Endereço residencial
|
|
|
|
# Definir modal baseado no tipo
|
|
if route_type == "lineHaul":
|
|
modal_choices = ["rodoviario", "aereo", "aquaviario"]
|
|
modal_weights = [0.8, 0.15, 0.05]
|
|
else:
|
|
modal_choices = ["rodoviario"]
|
|
modal_weights = [1.0]
|
|
|
|
modal = random.choices(modal_choices, weights=modal_weights)[0]
|
|
|
|
# Definir prioridade
|
|
priority = random.choices(
|
|
["normal", "express", "urgent"],
|
|
weights=[0.7, 0.2, 0.1]
|
|
)[0]
|
|
|
|
# Definir status
|
|
status = random.choices(
|
|
["pending", "inProgress", "completed", "delayed", "cancelled"],
|
|
weights=[0.1, 0.4, 0.35, 0.1, 0.05]
|
|
)[0]
|
|
|
|
# Definir valores baseados no tipo
|
|
if route_type == "lastMile":
|
|
total_value = round(random.uniform(25.0, 150.0), 2)
|
|
total_weight = round(random.uniform(0.5, 15.0), 1)
|
|
estimated_cost = round(total_value * 0.4, 2)
|
|
elif route_type == "lineHaul":
|
|
total_value = round(random.uniform(1500.0, 5000.0), 2)
|
|
total_weight = round(random.uniform(5000.0, 15000.0), 1)
|
|
estimated_cost = round(total_value * 0.35, 2)
|
|
else: # firstMile
|
|
total_value = round(random.uniform(300.0, 2000.0), 2)
|
|
total_weight = round(random.uniform(500.0, 5000.0), 1)
|
|
estimated_cost = round(total_value * 0.45, 2)
|
|
|
|
# Datas
|
|
base_date = datetime.now() - timedelta(days=random.randint(0, 30))
|
|
scheduled_departure = base_date
|
|
|
|
# Definir datas baseadas no status
|
|
actual_departure = None
|
|
estimated_arrival = scheduled_departure + timedelta(hours=random.randint(2, 12))
|
|
actual_arrival = None
|
|
current_location = None
|
|
actual_cost = None
|
|
|
|
if status in ["inProgress", "completed", "delayed"]:
|
|
actual_departure = scheduled_departure + timedelta(minutes=random.randint(-30, 60))
|
|
if status == "completed":
|
|
actual_arrival = estimated_arrival + timedelta(minutes=random.randint(-60, 120))
|
|
current_location = dest_coords
|
|
actual_cost = round(estimated_cost * random.uniform(0.8, 1.3), 2)
|
|
elif status == "inProgress":
|
|
# Posição entre origem e destino
|
|
progress = random.uniform(0.2, 0.8)
|
|
current_location = {
|
|
"lat": round(origin_coords["lat"] + (dest_coords["lat"] - origin_coords["lat"]) * progress, 6),
|
|
"lng": round(origin_coords["lng"] + (dest_coords["lng"] - origin_coords["lng"]) * progress, 6)
|
|
}
|
|
|
|
# Produto tipo
|
|
product_type = random.choice(PRODUCT_TYPES)
|
|
|
|
# Placa do veículo
|
|
vehicle_plate = random.choice(REAL_PLATES)
|
|
|
|
return {
|
|
"id": f"rt_{route_id:03d}",
|
|
"routeNumber": f"RT-2024-{route_id:06d}",
|
|
"type": route_type,
|
|
"modal": modal,
|
|
"priority": priority,
|
|
"driverId": f"drv_{route_id:03d}",
|
|
"vehicleId": f"veh_{route_id:03d}",
|
|
"companyId": "comp_001",
|
|
"customerId": f"cust_{route_id:03d}",
|
|
"origin": {
|
|
"address": origin_address,
|
|
"coordinates": origin_coords,
|
|
"contact": generate_random_name(),
|
|
"phone": generate_phone(area_code)
|
|
},
|
|
"destination": {
|
|
"address": dest_address,
|
|
"coordinates": dest_coords,
|
|
"contact": generate_random_name(),
|
|
"phone": generate_phone(area_code)
|
|
},
|
|
"scheduledDeparture": scheduled_departure.isoformat() + "Z",
|
|
"actualDeparture": actual_departure.isoformat() + "Z" if actual_departure else None,
|
|
"estimatedArrival": estimated_arrival.isoformat() + "Z",
|
|
"actualArrival": actual_arrival.isoformat() + "Z" if actual_arrival else None,
|
|
"status": status,
|
|
"currentLocation": current_location,
|
|
"contractId": f"cont_{route_id:03d}",
|
|
"tablePricesId": f"tbl_{route_id:03d}",
|
|
"totalValue": total_value,
|
|
"totalWeight": total_weight,
|
|
"estimatedCost": estimated_cost,
|
|
"actualCost": actual_cost,
|
|
"productType": product_type,
|
|
"createdAt": (base_date - timedelta(hours=random.randint(1, 48))).isoformat() + "Z",
|
|
"updatedAt": (base_date + timedelta(minutes=random.randint(0, 300))).isoformat() + "Z",
|
|
"createdBy": f"user_{random.randint(1, 10):03d}",
|
|
"vehiclePlate": vehicle_plate
|
|
}
|
|
|
|
def generate_all_routes():
|
|
"""Gera todas as 500 rotas"""
|
|
routes = []
|
|
route_id = 1
|
|
|
|
# Distribuição por tipo (conforme especificação)
|
|
type_distribution = [
|
|
("firstMile", 300),
|
|
("lineHaul", 125),
|
|
("lastMile", 75)
|
|
]
|
|
|
|
# Distribuição por região
|
|
region_distribution = [
|
|
("saoPaulo", 175),
|
|
("rioDeJaneiro", 150),
|
|
("minasGerais", 125),
|
|
("vitoria", 50)
|
|
]
|
|
|
|
# Calcular rotas por tipo e região
|
|
total_routes_per_region = {region: count for region, count in region_distribution}
|
|
|
|
for route_type, type_count in type_distribution:
|
|
# Distribuir este tipo pelas regiões proporcionalmente
|
|
for region, region_total in region_distribution:
|
|
region_proportion = region_total / 500
|
|
routes_for_this_type_region = int(type_count * region_proportion)
|
|
|
|
for _ in range(routes_for_this_type_region):
|
|
if route_id <= 500:
|
|
route = generate_route_data(route_id, route_type, region)
|
|
routes.append(route)
|
|
route_id += 1
|
|
|
|
# Completar até 500 se necessário
|
|
while len(routes) < 500:
|
|
remaining_type = random.choice(["firstMile", "lineHaul", "lastMile"])
|
|
remaining_region = random.choice(["saoPaulo", "rioDeJaneiro", "minasGerais", "vitoria"])
|
|
route = generate_route_data(route_id, remaining_type, remaining_region)
|
|
routes.append(route)
|
|
route_id += 1
|
|
|
|
return routes[:500] # Garantir exatamente 500
|
|
|
|
def main():
|
|
"""Função principal"""
|
|
print("Gerando 500 rotas mockadas...")
|
|
|
|
routes = generate_all_routes()
|
|
|
|
# Calcular estatísticas reais
|
|
type_stats = {}
|
|
status_stats = {}
|
|
region_stats = {}
|
|
|
|
for route in routes:
|
|
# Tipo
|
|
route_type = route["type"]
|
|
type_stats[route_type] = type_stats.get(route_type, 0) + 1
|
|
|
|
# Status
|
|
status = route["status"]
|
|
status_stats[status] = status_stats.get(status, 0) + 1
|
|
|
|
# Região (baseado no telefone)
|
|
phone = route["origin"]["phone"]
|
|
if "11" in phone:
|
|
region = "saoPaulo"
|
|
elif "21" in phone:
|
|
region = "rioDeJaneiro"
|
|
elif "31" in phone:
|
|
region = "minasGerais"
|
|
else:
|
|
region = "vitoria"
|
|
region_stats[region] = region_stats.get(region, 0) + 1
|
|
|
|
# Estrutura final
|
|
data = {
|
|
"routes": routes,
|
|
"metadata": {
|
|
"totalRoutes": len(routes),
|
|
"generatedAt": datetime.now().isoformat() + "Z",
|
|
"version": "1.0",
|
|
"description": "Dados mockados para o módulo de Rotas do ERP SAAS PraFrota",
|
|
"actualDistributions": {
|
|
"byType": type_stats,
|
|
"byStatus": status_stats,
|
|
"byRegion": region_stats
|
|
},
|
|
"specifications": {
|
|
"byType": {
|
|
"firstMile": "60% (300 rotas) - Coleta em centros de distribuição",
|
|
"lineHaul": "25% (125 rotas) - Transporte entre cidades",
|
|
"lastMile": "15% (75 rotas) - Entrega final (Mercado Livre, Shopee, Amazon)"
|
|
},
|
|
"byModal": {
|
|
"rodoviario": "95% (475 rotas)",
|
|
"aereo": "3% (15 rotas)",
|
|
"aquaviario": "2% (10 rotas)"
|
|
},
|
|
"regions": {
|
|
"rioDeJaneiro": "30% (150 rotas)",
|
|
"saoPaulo": "35% (175 rotas)",
|
|
"minasGerais": "25% (125 rotas)",
|
|
"vitoria": "10% (50 rotas)"
|
|
}
|
|
},
|
|
"realVehiclePlates": REAL_PLATES,
|
|
"productTypes": PRODUCT_TYPES,
|
|
"lastMileMarketplaces": MARKETPLACES,
|
|
"coordinates": REGIONS
|
|
}
|
|
}
|
|
|
|
# Salvar arquivo
|
|
output_file = "ROUTES_MOCK_DATA_COMPLETE.json"
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
print(f"✅ Arquivo {output_file} gerado com sucesso!")
|
|
print(f"📊 Estatísticas:")
|
|
print(f" Total de rotas: {len(routes)}")
|
|
print(f" Por tipo: {type_stats}")
|
|
print(f" Por status: {status_stats}")
|
|
print(f" Por região: {region_stats}")
|
|
|
|
if __name__ == "__main__":
|
|
main() |