El proceso de decisión post-entrega es el momento crítico donde SIGA transforma una situación de incertidumbre en una decisión óptima basada en datos, predicciones y optimización multi-criterio.
def capturar_contexto(vehicle_id: str, timestamp: datetime) -> Context:
"""Captura toda la información relevante para la decisión"""
context = Context()
# Posición actual del vehículo
context.current_position = get_vehicle_position(vehicle_id)
context.current_zone = get_zone_from_position(context.current_position)
# Cargas disponibles en radio 150km
context.available_loads = get_available_loads(
center=context.current_position,
radius_km=150,
vehicle_capacity=get_vehicle_capacity(vehicle_id)
)
# Estado de la flota completa
context.fleet_state = get_fleet_positions()
context.vehicles_per_zone = calculate_vehicle_distribution()
# Predicciones para las próximas 72 horas
context.demand_predictions = predict_demand_all_zones(
horizon_hours=72,
from_zone=context.current_zone
)
# Información adicional
context.day_of_week = timestamp.weekday()
context.hour_of_day = timestamp.hour
context.weather_forecast = get_weather_forecast(context.current_zone)
return context
| Criterio | Peso | Descripción |
|---|---|---|
| Valor Inmediato | 30% | Cargas disponibles ahora |
| Valor Futuro | 40% | Demanda predicha 24-72h |
| Costo Reposicionamiento | 20% | Km vacíos necesarios |
| Balance Flota | 10% | Equilibrio geográfico |
def calcular_matriz_decision(context: Context) -> DecisionMatrix:
"""Evalúa cada opción según múltiples criterios"""
matrix = DecisionMatrix()
for option in context.available_options:
score = Score()
# Valor inmediato
score.immediate_value = calculate_immediate_revenue(option)
# Valor futuro de la posición
score.future_value = calculate_position_value(
destination=option.destination,
horizon_hours=48
)
# Costo de llegar allí
score.repositioning_cost = calculate_empty_km_cost(
from_pos=context.current_position,
to_pos=option.destination
)
# Impacto en balance de flota
score.fleet_balance = calculate_fleet_balance_impact(
move_to=option.destination,
current_distribution=context.vehicles_per_zone
)
# Score total ponderado
score.total = (
score.immediate_value * 0.3 +
score.future_value * 0.4 -
score.repositioning_cost * 0.2 +
score.fleet_balance * 0.1
)
matrix.add_option(option, score)
return matrix
def evaluar_escenario_esperar(context: Context) -> Scenario:
"""Evalúa el valor de esperar en la posición actual"""
scenario = Scenario(type="WAIT")
# Probabilidad de carga en las próximas horas
load_probability = predict_load_probability(
zone=context.current_zone,
time_windows=[4, 8, 12, 24] # horas
)
# Costo de espera (tiempo muerto)
waiting_cost = calculate_waiting_cost(
hourly_cost=DRIVER_HOURLY_COST + VEHICLE_DEPRECIATION,
expected_wait_hours=load_probability.expected_wait_time
)
# Valor esperado
scenario.expected_value = (
load_probability.expected_revenue * load_probability.confidence -
waiting_cost
)
# Riesgos
scenario.risks = [
"Posible espera >24h",
"Competencia de otros camiones",
"Pérdida de oportunidades en otras zonas"
]
return scenario
def evaluar_escenario_reposicionar(context: Context, target_zone: str) -> Scenario:
"""Evalúa el valor de moverse a una zona con demanda predicha"""
scenario = Scenario(type="REPOSITION")
# Costo de reposicionamiento
repo_cost = calculate_repositioning_cost(
from_zone=context.current_zone,
to_zone=target_zone,
empty_km=True
)
# Valor futuro de la posición
future_value = predict_position_value(
zone=target_zone,
arrival_time=estimate_arrival_time(context.current_zone, target_zone),
horizon_hours=48
)
# Probabilidad de éxito
success_probability = calculate_success_probability(
target_zone=target_zone,
competing_vehicles=count_vehicles_heading_to(target_zone)
)
scenario.expected_value = (
future_value * success_probability - repo_cost
)
scenario.recommendation = f"Reposicionar a {target_zone}"
scenario.justification = f"Alta demanda esperada: {future_value}€"
return scenario
def generar_recomendaciones(scenarios: List[Scenario]) -> Recommendations:
"""Genera top 3 recomendaciones con justificación"""
# Ordenar por valor esperado
sorted_scenarios = sorted(
scenarios,
key=lambda s: s.expected_value,
reverse=True
)
recommendations = Recommendations()
for i, scenario in enumerate(sorted_scenarios[:3]):
rec = Recommendation()
rec.rank = i + 1
rec.action = scenario.recommendation
rec.expected_value = scenario.expected_value
rec.confidence = scenario.confidence
# Justificación en lenguaje natural
rec.justification = generate_natural_language_justification(scenario)
# Datos para transparencia
rec.calculation_details = {
"immediate_value": scenario.immediate_value,
"future_value": scenario.future_value,
"costs": scenario.total_costs,
"risks": scenario.risks
}
recommendations.add(rec)
return recommendations
Camión: MAD-2847
Ubicación: Valencia (Puerto)
Hora: Martes 14:30
Capacidad: 24 toneladas
┌─────────────────────────────────────────┐
│ ANÁLISIS DE DECISIÓN │
├─────────────────────────────────────────┤
│ Contexto: │
│ • 2 cargas disponibles Valencia │
│ • 5 camiones en zona (competencia alta) │
│ • Demanda Barcelona +85% en 24h │
│ • Demanda Madrid +45% en 48h │
├─────────────────────────────────────────┤
│ Recomendaciones: │
│ │
│ 1. 🥇 Ir a Barcelona vacío │
│ Valor: +€580 │
│ Justificación: Alta probabilidad │
│ (85%) de carga premium mañana 8AM │
│ │
│ 2. 🥈 Esperar carga local │
│ Valor: +€220 │
│ Justificación: 2 cargas disponibles │
│ pero 5 camiones compitiendo │
│ │
│ 3. 🥉 Carga marginal a Castellón │
│ Valor: +€120 │
│ Justificación: Ingreso inmediato │
│ pero posición final subóptima │
└─────────────────────────────────────────┘
| Métrica | Target | Actual |
|---|---|---|
| Tiempo respuesta | 2.1 seg | |
| Precisión recomendaciones | >80% | 84% |
| Adopción usuarios | >90% | 92% |
| Valor capturado vs óptimo | >85% | 87% |
⬅️ Volver a Procesos Core | ➡️ Siguiente: Valoración de Posiciones