feat: adiciona quadro de horários formatado com tabulate ao Modelo 2

This commit is contained in:
2026-06-07 20:41:17 -03:00
parent 1cab2bbe36
commit e478cc72e0
4 changed files with 223 additions and 44 deletions

View File

@@ -371,6 +371,82 @@ for i in aeroportos_malha:
gerou_vazio = True
if not gerou_vazio:
print(" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.")
# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME
from tabulate import tabulate
import datetime
# 1. Construir a lista de todos os voos a serem realizados
voos_para_fazer = []
# Cópia para saber quantos voos de passageiros faltam alocar
D_copy = {k: v for k, v in D.items()}
for i in aeroportos_malha:
for j in aeroportos_malha:
total_voos = D[(i, j)] + int(Y[(i, j)].varValue)
for _ in range(total_voos):
if D_copy[(i, j)] > 0:
tipo = 'Passageiro'
D_copy[(i, j)] -= 1
else:
tipo = 'Vazio (Reposicionamento)'
voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]})
# 2. Simular a alocação sequencial de horários
tabela_horarios = []
aeronave_id = 1
while voos_para_fazer:
voo_atual = voos_para_fazer.pop(0)
hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0)
tempo_delta = datetime.timedelta(hours=voo_atual['tempo'])
hora_chegada = hora_atual + tempo_delta
tabela_horarios.append([
f"Aeronave {aeronave_id}",
voo_atual['origem'],
voo_atual['destino'],
voo_atual['tipo'],
hora_atual.strftime('%H:%M'),
hora_chegada.strftime('%H:%M'),
f"{voo_atual['tempo']:.2f} h"
])
hora_atual = hora_chegada
local_atual = voo_atual['destino']
# Continuar traçando o caminho para esta aeronave até que não caiba mais nas 24h
while True:
prox_voo = None
for idx, v in enumerate(voos_para_fazer):
if v['origem'] == local_atual:
if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600:
prox_voo = voos_para_fazer.pop(idx)
break
if prox_voo:
tempo_delta = datetime.timedelta(hours=prox_voo['tempo'])
hora_chegada = hora_atual + tempo_delta
tabela_horarios.append([
f"Aeronave {aeronave_id}",
prox_voo['origem'],
prox_voo['destino'],
prox_voo['tipo'],
hora_atual.strftime('%H:%M'),
hora_chegada.strftime('%H:%M'),
f"{prox_voo['tempo']:.2f} h"
])
hora_atual = hora_chegada
local_atual = prox_voo['destino']
else:
break
aeronave_id += 1
print("\\n== QUADRO DE HORÁRIOS DIÁRIOS ==")
print(tabulate(tabela_horarios, headers=["Aeronave", "Origem", "Destino", "Tipo", "Partida", "Chegada", "Duração"], tablefmt="github"))
"""))
nb.cells = cells

View File

@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
"id": "07d7e46d",
"id": "d163e704",
"metadata": {},
"source": [
"# Alocação de Frotas (Fleet Scheduling) - Aeronaves C-97\n",
@@ -21,13 +21,13 @@
{
"cell_type": "code",
"execution_count": 1,
"id": "3f9fa4c6",
"id": "1cda871c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:16.312196Z",
"iopub.status.busy": "2026-06-07T23:37:16.311487Z",
"iopub.status.idle": "2026-06-07T23:37:16.933888Z",
"shell.execute_reply": "2026-06-07T23:37:16.933301Z"
"iopub.execute_input": "2026-06-07T23:41:13.265917Z",
"iopub.status.busy": "2026-06-07T23:41:13.265674Z",
"iopub.status.idle": "2026-06-07T23:41:13.946892Z",
"shell.execute_reply": "2026-06-07T23:41:13.946177Z"
}
},
"outputs": [],
@@ -53,7 +53,7 @@
},
{
"cell_type": "markdown",
"id": "6054dba5",
"id": "3aa25feb",
"metadata": {},
"source": [
"## 1. Leitura e Limpeza dos Dados\n",
@@ -63,13 +63,13 @@
{
"cell_type": "code",
"execution_count": 2,
"id": "da258d7f",
"id": "5f12213f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:16.935502Z",
"iopub.status.busy": "2026-06-07T23:37:16.935296Z",
"iopub.status.idle": "2026-06-07T23:37:17.297220Z",
"shell.execute_reply": "2026-06-07T23:37:17.296482Z"
"iopub.execute_input": "2026-06-07T23:41:13.949035Z",
"iopub.status.busy": "2026-06-07T23:41:13.948756Z",
"iopub.status.idle": "2026-06-07T23:41:14.387479Z",
"shell.execute_reply": "2026-06-07T23:41:14.386681Z"
}
},
"outputs": [],
@@ -101,7 +101,7 @@
},
{
"cell_type": "markdown",
"id": "2424db2d",
"id": "29a370cf",
"metadata": {},
"source": [
"## 2. Análise dos Esquadrões e Matrículas (Aeronaves)\n",
@@ -111,13 +111,13 @@
{
"cell_type": "code",
"execution_count": 3,
"id": "8b308e12",
"id": "4f559f60",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:17.298752Z",
"iopub.status.busy": "2026-06-07T23:37:17.298630Z",
"iopub.status.idle": "2026-06-07T23:37:17.310776Z",
"shell.execute_reply": "2026-06-07T23:37:17.310206Z"
"iopub.execute_input": "2026-06-07T23:41:14.388969Z",
"iopub.status.busy": "2026-06-07T23:41:14.388784Z",
"iopub.status.idle": "2026-06-07T23:41:14.401597Z",
"shell.execute_reply": "2026-06-07T23:41:14.401016Z"
}
},
"outputs": [
@@ -176,7 +176,7 @@
},
{
"cell_type": "markdown",
"id": "4909bfef",
"id": "3d637614",
"metadata": {},
"source": [
"## 3. Análise Estatística das Demandas Diárias\n",
@@ -187,13 +187,13 @@
{
"cell_type": "code",
"execution_count": 4,
"id": "b38abd07",
"id": "ca8b936f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:17.312196Z",
"iopub.status.busy": "2026-06-07T23:37:17.312078Z",
"iopub.status.idle": "2026-06-07T23:37:17.621631Z",
"shell.execute_reply": "2026-06-07T23:37:17.620923Z"
"iopub.execute_input": "2026-06-07T23:41:14.403000Z",
"iopub.status.busy": "2026-06-07T23:41:14.402870Z",
"iopub.status.idle": "2026-06-07T23:41:14.726896Z",
"shell.execute_reply": "2026-06-07T23:41:14.726161Z"
}
},
"outputs": [
@@ -424,7 +424,7 @@
},
{
"cell_type": "markdown",
"id": "6bab0068",
"id": "782a4736",
"metadata": {},
"source": [
"## Diagrama de Rede dos Trechos\n",
@@ -434,13 +434,13 @@
{
"cell_type": "code",
"execution_count": 5,
"id": "6198127c",
"id": "9fedd535",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:17.623083Z",
"iopub.status.busy": "2026-06-07T23:37:17.622958Z",
"iopub.status.idle": "2026-06-07T23:37:17.740324Z",
"shell.execute_reply": "2026-06-07T23:37:17.739229Z"
"iopub.execute_input": "2026-06-07T23:41:14.728337Z",
"iopub.status.busy": "2026-06-07T23:41:14.728199Z",
"iopub.status.idle": "2026-06-07T23:41:14.849420Z",
"shell.execute_reply": "2026-06-07T23:41:14.848842Z"
}
},
"outputs": [
@@ -497,7 +497,7 @@
},
{
"cell_type": "markdown",
"id": "35633067",
"id": "551febf3",
"metadata": {},
"source": [
"## 4. Fleet Assignment Model (FAM) - Nível Brasil\n",
@@ -514,13 +514,13 @@
{
"cell_type": "code",
"execution_count": 6,
"id": "c9a2018d",
"id": "bb15ee0b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:17.742588Z",
"iopub.status.busy": "2026-06-07T23:37:17.742459Z",
"iopub.status.idle": "2026-06-07T23:37:17.764890Z",
"shell.execute_reply": "2026-06-07T23:37:17.764350Z"
"iopub.execute_input": "2026-06-07T23:41:14.850971Z",
"iopub.status.busy": "2026-06-07T23:41:14.850776Z",
"iopub.status.idle": "2026-06-07T23:41:14.876417Z",
"shell.execute_reply": "2026-06-07T23:41:14.875894Z"
}
},
"outputs": [
@@ -609,7 +609,7 @@
},
{
"cell_type": "markdown",
"id": "666ba740",
"id": "37f9e364",
"metadata": {},
"source": [
"## 5. Modelo 2: Minimização de Aeronaves\n",
@@ -623,13 +623,13 @@
{
"cell_type": "code",
"execution_count": 7,
"id": "7b9b8019",
"id": "08433efd",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-07T23:37:17.766458Z",
"iopub.status.busy": "2026-06-07T23:37:17.766338Z",
"iopub.status.idle": "2026-06-07T23:37:19.072202Z",
"shell.execute_reply": "2026-06-07T23:37:19.071522Z"
"iopub.execute_input": "2026-06-07T23:41:14.877926Z",
"iopub.status.busy": "2026-06-07T23:41:14.877797Z",
"iopub.status.idle": "2026-06-07T23:41:16.250728Z",
"shell.execute_reply": "2026-06-07T23:41:16.250087Z"
}
},
"outputs": [
@@ -646,7 +646,22 @@
"Taxa de ocupação da frota (Tempo Voo / Tempo Disponível): 69.6%\n",
"\n",
"Voos de Reposicionamento Vazio Gerados (Y) para fechar o ciclo diário:\n",
" -> De SBAN para SBSJ: 1 voo(s) vazio(s) | Tempo de voo: 2.1h\n"
" -> De SBAN para SBSJ: 1 voo(s) vazio(s) | Tempo de voo: 2.1h\n",
"\n",
"== QUADRO DE HORÁRIOS DIÁRIOS ==\n",
"| Aeronave | Origem | Destino | Tipo | Partida | Chegada | Duração |\n",
"|------------|----------|-----------|--------------------------|-----------|-----------|-----------|\n",
"| Aeronave 1 | SBGL | SBGR | Passageiro | 00:00 | 00:51 | 0.86 h |\n",
"| Aeronave 1 | SBGR | SBGL | Passageiro | 00:51 | 01:43 | 0.86 h |\n",
"| Aeronave 1 | SBGL | SBSJ | Passageiro | 01:43 | 02:24 | 0.69 h |\n",
"| Aeronave 1 | SBSJ | SBGL | Passageiro | 02:24 | 03:06 | 0.69 h |\n",
"| Aeronave 1 | SBGL | SBBR | Passageiro | 03:06 | 05:26 | 2.33 h |\n",
"| Aeronave 1 | SBBR | SBGL | Passageiro | 05:26 | 07:45 | 2.33 h |\n",
"| Aeronave 2 | SBGR | SBBR | Passageiro | 00:00 | 02:10 | 2.17 h |\n",
"| Aeronave 2 | SBBR | SBGR | Passageiro | 02:10 | 04:20 | 2.17 h |\n",
"| Aeronave 3 | SBAN | SBSJ | Vazio (Reposicionamento) | 00:00 | 02:08 | 2.14 h |\n",
"| Aeronave 3 | SBSJ | SBBR | Passageiro | 02:08 | 04:17 | 2.15 h |\n",
"| Aeronave 3 | SBBR | SBAN | Passageiro | 04:17 | 04:35 | 0.30 h |\n"
]
}
],
@@ -739,7 +754,83 @@
" print(f\" -> De {i} para {j}: {int(Y[(i, j)].varValue)} voo(s) vazio(s) | Tempo de voo: {tempos_voo[(i,j)]*Y[(i,j)].varValue:.1f}h\")\n",
" gerou_vazio = True\n",
"if not gerou_vazio:\n",
" print(\" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.\")\n"
" print(\" Nenhum voo de reposicionamento vazio foi necessário! A malha já é circular/balanceada.\")\n",
"\n",
"# GERAR TABELA DE HORÁRIOS COM TABULATE E DATETIME\n",
"from tabulate import tabulate\n",
"import datetime\n",
"\n",
"# 1. Construir a lista de todos os voos a serem realizados\n",
"voos_para_fazer = []\n",
"\n",
"# Cópia para saber quantos voos de passageiros faltam alocar\n",
"D_copy = {k: v for k, v in D.items()}\n",
"\n",
"for i in aeroportos_malha:\n",
" for j in aeroportos_malha:\n",
" total_voos = D[(i, j)] + int(Y[(i, j)].varValue)\n",
" for _ in range(total_voos):\n",
" if D_copy[(i, j)] > 0:\n",
" tipo = 'Passageiro'\n",
" D_copy[(i, j)] -= 1\n",
" else:\n",
" tipo = 'Vazio (Reposicionamento)'\n",
" voos_para_fazer.append({'origem': i, 'destino': j, 'tipo': tipo, 'tempo': tempos_voo[(i, j)]})\n",
"\n",
"# 2. Simular a alocação sequencial de horários\n",
"tabela_horarios = []\n",
"aeronave_id = 1\n",
"\n",
"while voos_para_fazer:\n",
" voo_atual = voos_para_fazer.pop(0)\n",
" hora_atual = datetime.datetime(2025, 1, 1, 0, 0, 0)\n",
" \n",
" tempo_delta = datetime.timedelta(hours=voo_atual['tempo'])\n",
" hora_chegada = hora_atual + tempo_delta\n",
" \n",
" tabela_horarios.append([\n",
" f\"Aeronave {aeronave_id}\",\n",
" voo_atual['origem'],\n",
" voo_atual['destino'],\n",
" voo_atual['tipo'],\n",
" hora_atual.strftime('%H:%M'),\n",
" hora_chegada.strftime('%H:%M'),\n",
" f\"{voo_atual['tempo']:.2f} h\"\n",
" ])\n",
" \n",
" hora_atual = hora_chegada\n",
" local_atual = voo_atual['destino']\n",
" \n",
" # Continuar traçando o caminho para esta aeronave até que não caiba mais nas 24h\n",
" while True:\n",
" prox_voo = None\n",
" for idx, v in enumerate(voos_para_fazer):\n",
" if v['origem'] == local_atual:\n",
" if (hora_atual - datetime.datetime(2025, 1, 1, 0, 0, 0) + datetime.timedelta(hours=v['tempo'])).total_seconds() <= 24 * 3600:\n",
" prox_voo = voos_para_fazer.pop(idx)\n",
" break\n",
" \n",
" if prox_voo:\n",
" tempo_delta = datetime.timedelta(hours=prox_voo['tempo'])\n",
" hora_chegada = hora_atual + tempo_delta\n",
" tabela_horarios.append([\n",
" f\"Aeronave {aeronave_id}\",\n",
" prox_voo['origem'],\n",
" prox_voo['destino'],\n",
" prox_voo['tipo'],\n",
" hora_atual.strftime('%H:%M'),\n",
" hora_chegada.strftime('%H:%M'),\n",
" f\"{prox_voo['tempo']:.2f} h\"\n",
" ])\n",
" hora_atual = hora_chegada\n",
" local_atual = prox_voo['destino']\n",
" else:\n",
" break\n",
" \n",
" aeronave_id += 1\n",
"\n",
"print(\"\\n== QUADRO DE HORÁRIOS DIÁRIOS ==\")\n",
"print(tabulate(tabela_horarios, headers=[\"Aeronave\", \"Origem\", \"Destino\", \"Tipo\", \"Partida\", \"Chegada\", \"Duração\"], tablefmt=\"github\"))\n"
]
}
],

View File

@@ -13,5 +13,6 @@ dependencies = [
"pulp>=3.3.2",
"pymap3d>=3.2.0",
"seaborn>=0.13.2",
"tabulate>=0.10.0",
"vincenty>=0.1.4",
]

11
uv.lock generated
View File

@@ -176,6 +176,7 @@ dependencies = [
{ name = "pulp" },
{ name = "pymap3d" },
{ name = "seaborn" },
{ name = "tabulate" },
{ name = "vincenty" },
]
@@ -189,6 +190,7 @@ requires-dist = [
{ name = "pulp", specifier = ">=3.3.2" },
{ name = "pymap3d", specifier = ">=3.2.0" },
{ name = "seaborn", specifier = ">=0.13.2" },
{ name = "tabulate", specifier = ">=0.10.0" },
{ name = "vincenty", specifier = ">=0.1.4" },
]
@@ -1965,6 +1967,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
]
[[package]]
name = "tabulate"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" },
]
[[package]]
name = "terminado"
version = "0.18.1"