1. Como estudar este projeto
O AirTrain representa uma fábrica de aviões LEGO em rodadas de 180 segundos simulados. O objetivo deste manual é ajudar você a entender, executar e alterar seu código, principalmente o motor SimPy. A versão principal explicada é a interface web com Phaser e equipes móveis.
Comece pelos conceitos de processo e evento, acompanhe o percurso de uma peça e, depois, estude o cálculo do trabalho humano. Você precisa reconhecer funções, classes, listas e dicionários em Python; o uso de yield é explicado adiante.
SimPy decide quando cada evento ocorre e quais processos precisam esperar. O código da fábrica define receitas, políticas de produção, competências e indicadores. Phaser desenha o estado e recebe as ações da pessoa que usa o simulador.
As durações e competências iniciais são hipóteses didáticas. O exercício fornece o fluxo e os cenários; não fornece uma calibração de tempos reais dos funcionários. Os resultados apresentados aqui ilustram o comportamento do programa.
Referência desta edição: código local em 12/10/2026; SimPy 4.1.2, Phaser 3.90.0, pygame-ce 2.5.8 e ReportLab 4.4.9. Os trechos de Python abaixo são extraídos dos arquivos pelo gerador deste manual.
2. Mapa dos arquivos e da arquitetura
app.js + Phaser
Session
WorkforceFactory
Factory + SimPy
| Arquivo | O que faz | Comece por |
|---|---|---|
factory.py | Receitas, postos, filas, Kanban, JIT, fornecedores, inspeção e métricas. | Factory, Station.worker |
logistics.py | Depósito único, caixas de matéria-prima, coleta e entrega física. | Warehouse, WarehouseStationMixin |
workforce.py | Equipes, competências, fadiga, pedidos finitos e execução do trabalho. | WorkforceStation.work, labour_clock |
web_server.py | Relógio real, comandos HTTP, persistência e arquivos estáticos. | Session.tick, Session.action |
reporting.py | Comparação de rodadas, observações e geração do PDF. | build_report, export_report_pdf |
web/app.js | Atualiza tela e accordion e envia comandos ao servidor. | poll, applyState, command |
web/factory-scene.js e web/isometric.js | Desenham a fábrica isométrica, posicionam equipes, animam as viagens e exibem peças e cartões. Convertem o ponteiro para coordenadas da fábrica mesmo com zoom e deslocamento. | FactoryScene, Iso.project, Iso.stationAt |
web/lego.js | Blocos e projeção isométrica compartilhados entre Phaser e SVG. | components, project, model |
web/gallery.js e web/report.js | Exploração das montagens e relatório da rodada. | openAssembly, openReport |
web/index.html e arquivos CSS | Estrutura, identidade institucional e adaptação da tela. team.css cuida do accordion; ipb.css aplica o tema. | IDs dos elementos usados por app.js |
app.py | Interface Pygame original. Usa Factory, sem a camada de equipes web. | App.run, main |
tests/ | Verifica conservação, políticas, equipes, pedidos, API, relatórios e geometria. | test_factory.py, test_workforce.py |
station_class = WorkforceStation é o ponto de extensão: o construtor de Factory cria postos da classe indicada pela subclasse. A versão web usa WarehouseStationMixin.worker() e seu planejamento de pedidos; WorkforceStation fornece os métodos de sorteio e execução do trabalho. A classe base continua atendendo a versão Pygame original.
3. SimPy e o significado de yield
Uma simulação de eventos discretos avança entre acontecimentos agendados. Aqui, uma unidade de env.now representa um segundo. Um processo pode ficar esperando uma peça enquanto outros continuam produzindo. Essa concorrência é cooperativa: os processos SimPy não são uma thread do sistema operacional para cada posto.
| Expressão | Leitura no contexto da fábrica |
|---|---|
simpy.Environment() | Cria o relógio e a agenda de eventos. |
env.process(funcao()) | Agenda um gerador Python como processo. |
yield evento | Suspende somente este processo até o evento ser processado. |
yield env.timeout(5) | Espera cinco segundos simulados, não cinco segundos reais. |
yield fila.get() | Espera e retira um objeto da fila. |
yield env.all_of(eventos) | Espera todos os eventos, que podem avançar simultaneamente. |
Uma função que contém yield cria um gerador quando é chamada. Escrever apenas posto() não executa sua sequência de operações no ambiente; é necessário registrá-la com env.process(posto()).
import simpy
def posto(env):
print("Início", env.now)
yield env.timeout(4)
print("Conclusão", env.now)
env = simpy.Environment()
env.process(posto(env))
env.run(until=5)
# Saída: Início 0 / Conclusão 4yield preserva as variáveis locais para a retomada. return encerra o processo e pode devolver um resultado para quem espera por ele. Evite substituir timeout por time.sleep: isso bloquearia a execução real, sem representar corretamente a espera na agenda do SimPy.
Conceitos da biblioteca: SimPy — Basic Concepts. As políticas industriais são implementadas pelo projeto.
4. Store, Container e Resource no projeto
Os três objetos têm funções diferentes. A escolha depende de você precisar guardar peças identificáveis, contar autorizações ou limitar atendimentos simultâneos.
| Objeto | Uso real | Espera causada |
|---|---|---|
Store | outputs[model] guarda objetos Item. orders guarda pedidos e prepared_orders guarda pares de modelo e processo de preparação. | get() espera quando não há objeto. As Stores do projeto não têm capacidade finita configurada. |
Container | cards conta cartões Kanban; order_slots conta vagas para pedidos JIT em aberto. | get(1) espera quando não há unidade disponível. |
Resource | suppliers[color] permite um atendimento por vez para cada cor. | request() espera quando o fornecedor está ocupado. |
outputs é um dicionário de filas: existe uma para AT-470 e outra para AT-120. Assim, quem precisa de uma cabine AT-120 não retira uma cabine AT-470. O modelo é conhecido pelo pedido; não é necessário filtrar uma fila única.
O posto tem um único processo worker(), que executa um lote por vez. Na versão web, as pessoas colaboram nesse lote por meio de labour_clock(). Aumentar uma equipe não cria bancadas independentes.
put() e get() criam eventos. Alguns put() aparecem sem yield porque o processo não precisa esperar por sua conclusão. Isso depende das filas sem limite e da contagem correta dos cartões. Se você acrescentar capacidade finita, reavalie esses pontos para representar o bloqueio de saída.
Referência: SimPy — Shared Resources.
5. Como uma peça percorre a fábrica
Item representa um lote ou subconjunto rastreável. id identifica o objeto; model indica o avião; stage identifica sua etapa; born registra o início mais antigo entre seus componentes; bricks conserva os materiais; owner indica o posto responsável; ready informa se a operação terminou.
Esse encadeamento está em inputs_for(). A matéria-prima dos cinco postos iniciais está em RAW. Asas e motores são fabricados em pares; os demais lotes correspondem a uma unidade de subconjunto.
WarehouseStationMixin.worker()escolhe o modelo e reserva um cartão quando necessário.collect()verifica os materiais necessários. Nos postos iniciais, escolhe um coletor para buscar blocos; nas montagens, espera todos os subconjuntos entregues na própria bancada.- Quando precisa de blocos, o coletor caminha até as caixas, retira o kit e volta. Componentes já recebidos são consumidos localmente, sem uma segunda viagem ao depósito.
- O código soma os blocos com
Counter, remove os objetos de entrada e cria o subconjunto em montagem, preservando o menorborn. - Após o trabalho,
store_output()envia uma pessoa com o lote ao próximo posto determinado pornext_station(). A fila só recebe o objeto depois da descarga no destino. AT-120 segue deassembly_wingsparainspection; AT-470 passa porassembly_engines. - Na inspeção, rejeitados são contabilizados imediatamente; aprovados são entregues ao cliente. O próximo lote espera o retorno do entregador.
owner identifica a bancada de origem, o funcionário em trânsito ou o posto que recebeu o lote. Todos os lotes permanecem em factory.items até serem incorporados, entregues ou rejeitados. Assim, o WIP inclui transporte sem duplicar peças.
Uma viagem também é um processo SimPy
def phase(self, worker, phase, duration, origin, destination, cargo=()):
worker.trip = {"phase": phase, "station": self.key, "from": origin, "to": destination,
"started": self.env.now, "ends": self.env.now + duration,
"cargo": [{"model": p.model, "stage": p.stage, "bricks": p.bricks, "ready": p.ready} for p in cargo]}
if phase in ("Indo ao depósito", "Levando ao próximo posto", "Entregando avião"):
target = f" · {STATIONS[destination]}" if destination in STATIONS else ""
self.factory.events.append((self.env.now, f"{worker.profile['name']}: {phase.lower()}{target}"))
worker.logistics_intervals.append((self.env.now, self.env.now + duration))
self.state(phase)
yield self.env.timeout(duration)yield env.timeout(duration) consome tempo simulado. trip informa início, fim, origem, destino e carga ao Phaser. Ao pausar o relógio, a posição também para. O funcionário acumula tempo de logística e fadiga, mas não tempo produtivo de montagem durante a viagem.
As cinco caixas usam simpy.Container: get(n) retira blocos; put(n) recebe fornecedores. Subconjuntos entregues usam as filas outputs[model] do produtor, mas sua posse (owner) e localização física são do posto consumidor. incoming no snapshot lista essas partes para desenhá-las no destino. Os blocos ficam no depósito; os subconjuntos seguem diretamente entre bancadas. As mesas e filas começam vazias. As caixas recebem o material exato do pedido, exceto no cenário 6, que espera reposição.
O accordion do depósito ajusta 2 s por trecho, 0,25 s por peça e 0,5 s por visita, inicialmente. A coleta é cobrada por bloco retirado. O consumo de subconjuntos já entregues não acrescenta tempo de caminhada/coleta; a descarga foi paga pelo produtor. São tempos demonstrativos constantes, ainda sem efeito da distância ou competência. Há um coletor por lote, escolhido entre os disponíveis com menor tempo acumulado de logística. Não há fila de atendimento nem limite de pessoas no depósito.
6. Montagem com all_of
def prepare_inputs(self, model):
"""Request all inputs concurrently; components keep their WIP identity until used."""
parents = inputs_for(self.key, model)
if parents:
requests = [self.env.process(self.factory.stations[k].take(model, self.key)) for k in parents]
result = yield self.env.all_of(requests)
return [result[event] for event in requests]
if self.factory.scenario == 6:
colors = sorted({piece[-1] for piece in RAW[model][self.key]})
yield self.env.all_of([self.env.process(self.factory.supply(color, self.key)) for color in colors])
return []O trecho acima pertence à versão base/Pygame. Na versão web, Warehouse.ready() verifica se todos os componentes já chegaram ao posto consumidor; collect() os consome localmente. Apenas operações que usam blocos brutos fazem retirada física no depósito. O princípio de esperar todas as entradas permanece.
parents contém os postos que fornecem componentes. A compreensão de lista cria um processo take() por entrada. Esses processos começam sem que o código espere o primeiro terminar. all_of só libera a montagem quando todos terminam.
O resultado associa cada evento ao objeto devolvido por ele. Por isso result[event] recupera a peça retornada pelo respectivo take(). Não confunda esse objeto com o pedido ou com o próprio evento.
No exemplo sincronizacao, cauda, cabine e cockpit ficam prontos em 5, 10 e 7 segundos. Solicitados juntos em t=0, permitem iniciar o corpo em t=10. Somar 5 + 10 + 7 representaria uma fabricação sequencial diferente da implementação.
No cenário 6 web, Warehouse.restock() usa all_of para esperar todas as cores necessárias ao lote e repor as caixas. Depois disso ocorre a coleta física.
Referência: SimPy — Events.
7. Push, Kanban e JIT
Factory.mode_for() atribui a política de cada posto. A diferença central está na autorização para iniciar o próximo lote.
| Política | Autorização | Implementação |
|---|---|---|
| Push | Segue a sequência disponível. | Não reserva cartão e não exige pedido JIT para cada início. |
| Kanban | Precisa de cartão disponível. | yield self.cards.get(1) acontece antes da preparação dos componentes. |
| JIT | Precisa de demanda do posto seguinte. | request_parents() coloca um modelo em orders; plan_orders() propaga a necessidade para trás. |
A devolução do cartão
def take(self, model, destination):
if self.mode == "JIT":
self.orders.put(model)
item = yield self.outputs[model].get()
item.owner = destination
if self.mode == "Kanban":
self.cards.put(1) # Ticket returns when the lot moves to its next station.
self.factory.transfers.append((self.env.now, self.key, destination, model))
return itemO trecho acima mostra a regra base. Na versão web, a devolução ocorre em collect(), no consumo do componente entregue ao próximo posto. Em Kanban, o cartão volta quando o lote sai da fila para entrar na montagem do consumidor. Ele não volta simplesmente porque a fabricação terminou. Na inspeção, que não possui saída intermediária, a devolução ocorre depois de finish().
Com dois cartões, um pode estar associado a um lote pronto e o outro a uma operação ou reserva. Quando nenhum está disponível, o posto fica Sem cartão. O limite é compartilhado entre os modelos e expresso em lotes; duas autorizações para asas correspondem a até dois pares envolvidos nesse ciclo.
A propagação do pedido JIT
def plan_orders(self):
# Propagate demand without moving or reserving physical parts in advance.
while True:
model = yield self.orders.get()
self.request_parents(model)
yield self.prepared_orders.put(model)plan_orders() propaga demandas sem reservar nem transportar materiais antecipadamente. Só o processo worker() coleta materiais e executa operações na bancada. Separar essas responsabilidades permite que os postos trabalhem simultaneamente.
Quando a inspeção é JIT, customer() limita pedidos em aberto com order_slots. O padrão é dois. Uma vaga é devolvida ao entregar o avião aprovado ao cliente ou registrar uma rejeição na inspeção. No cenário misto invertido, a inspeção é Kanban: o mecanismo de vagas do cliente JIT não é ativado.
8. Cenários, avaria e fornecedores
| Cenário | Comportamento |
|---|---|
| 1 | Push. |
| 2 | Push e avaria no corpo entre 60 e 90 s. |
| 3 | Kanban. |
| 4 | Kanban e a mesma avaria. |
| 5 | JIT. |
| 6 | JIT e fornecedores separados por cor R, W e Y. |
| 7 | Componentes Kanban e montagem JIT. mixed_reverse=True inverte as políticas. |
Em Station.work(), a versão básica divide a operação em trechos para parar exatamente na avaria. Se uma tarefa de 12 s começa em t=55, executa 5 s, espera de 60 a 90 e conclui os 7 s restantes em t=97. Isso é um exemplo com duração fixa, sem a camada de fadiga.
Na versão web, labour_clock() deixa de reduzir o trabalho restante durante a avaria. O lote e o progresso ficam preservados. A retomada depende também de haver pessoas disponíveis no corpo. Esta implementação não usa simpy.Interrupt nem PreemptiveResource.
def supply(self, color, destination):
with self.suppliers[color].request() as request:
yield request
delay = self.supplier_rng[color].uniform(15, 30)
self.supplier_delays.append((color, self.env.now, delay, destination))
yield self.env.timeout(delay)
self.events.append((self.env.now, f"Fornecedor {color}: peças para {STATIONS[destination]}"))O with libera o fornecedor ao terminar. Cada atendimento demora de 15 a 30 segundos após obter o recurso. Duas solicitações da mesma cor podem esperar uma pela outra; o tempo total em fila pode ultrapassar 30 s. Cores diferentes têm recursos próprios. O atraso é por solicitação de cor do lote, não por bloco individual.
9. Como o SimPy espera o trabalho da equipe
def work(self, duration):
self.remaining = duration
event = self.env.event()
self.factory.jobs[self.key] = {"remaining": duration, "total": duration,
"since": self.env.now, "event": event}
self.state("Aguardando funcionário")
yield eventEste é o ponto mais importante da versão com funcionários móveis. duration representa esforço de referência sorteado, em segundos de trabalho padrão. O método cria um evento ainda não concluído, registra o serviço em jobs e espera por esse evento.
| Campo de jobs | Significado |
|---|---|
remaining | Esforço que ainda falta executar. |
total | Esforço inicial, usado para calcular o progresso visual. |
since | Instante de criação; impede contabilizar trabalho antes do início da operação. |
event | Evento esperado por WarehouseStationMixin.worker() para continuar o lote. |
labour_clock() avança em intervalos de TICK = 0.25. Em cada passo, consulta quem está no posto, verifica avaria e indisponibilidade, soma velocidades e reduz remaining. Quando o esforço chega a zero, remove o serviço e chama job["event"].succeed().
monta o lote
cria evento e espera
consome esforço
libera a saída
Assim, o motor continua sendo uma simulação por eventos, com uma aproximação em passos fixos para o trabalho humano. A conclusão observável é liberada no fim do passo. O tempo produtivo contabilizado pode ser uma fração desse passo, pois o código limita o esforço ao necessário. Essa diferença explica a resolução de até 0,25 s por operação.
Adicionar ou mover alguém altera a equipe consultada pelos passos seguintes. O esforço sorteado não é sorteado outra vez. Se todas as pessoas saírem, o serviço continua em jobs, esperando equipe. A posição da pessoa no canvas não é o dado de produção: o posto é o campo profile["station"] no Python.
10. Tempos, competências e fadiga
O sorteio inicial
def sample_duration(self, model):
team = self.factory.workers_at(self.key)
base = self.factory.config["employees"][self.key]["base_seconds"][model]
spread = sum(w.profile["variability"] for w in team) / len(team) if team else self.employee["variability"]
return self.rng.triangular(base * (1 - spread), base * (1 + spread), base)O valor-base vem do posto e do modelo de avião. A variabilidade é a média das variabilidades da equipe presente no início do lote. Se o posto estiver vazio, usa o parâmetro-base do posto. Para base de 10 s e variabilidade de 0,25, o esforço é sorteado entre 7,5 e 12,5, com moda 10.
A velocidade durante a execução
velocidade_individual = competencia_no_posto * (1 - 0.35 * fadiga)
velocidade_da_equipe = soma_das_velocidades_das_pessoas_disponiveis
esforco_executado = segundos_produtivos * velocidade_da_equipe
Exemplo aritmético com velocidades constantes: competências 1,0 e 0,8, ambas sem fadiga, somam 1,8. Um serviço com esforço 9 exige 9 / 1,8 = 5 s. Na execução real do modelo, a fadiga muda e as conclusões são liberadas nos passos de 0,25 s; portanto esse cálculo é uma aproximação explicativa.
A fadiga varia entre 0 e 1. Cresce 0,002 por segundo produtivo ou de logística e recupera 0,003 por segundo ocioso. Durante descanso, o código usa recuperação de 0,015. A atualização ocorre a cada passo. Com fadiga 1, a redução de velocidade chega a 35%.
intervention(worker_id, "training") ocupa a pessoa por 30 s e concede +0,10 na competência do posto quando o treinamento termina, até 3,0. Descanso ocupa 20 s. Colegas disponíveis continuam trabalhando. A contagem e as competências persistem ao arquivar a equipe; treinamento interrompido pela nova rodada não concede o ganho.
O que muda na versão Pygame
Station.sample_duration() divide o tempo-base pela competência antes de sortear a duração. A versão básica não utiliza labour_clock(). A versão web sorteia esforço e aplica competências individuais durante a operação. Não divida o esforço pela competência na web e depois aplique a velocidade novamente: isso contaria o efeito duas vezes.
Os geradores aleatórios do processamento pertencem aos postos (Station.rng), com sementes derivadas de seed e do índice do posto. Qualidade e fornecedores têm geradores separados. Os funcionários não têm um gerador próprio nesta implementação.
11. Pedidos finitos e sequência de aviões
A interface web sempre fornece um pedido. order_sequence() valida quantidades inteiras de 0 a 100 de cada modelo, exigindo pelo menos um avião, e constrói a lista exata de fabricação.
pedido = {
"quantities": {"AT-470": 2, "AT-120": 1},
"sequence": "alternate"
}
# Resultado: ["AT-470", "AT-120", "AT-470"]
# "470_first": ["AT-470", "AT-470", "AT-120"]
# "120_first": ["AT-120", "AT-470", "AT-470"]
WorkforceFactory.__init__() copia a configuração, substitui a sequência e ativa finite_order. Em Push e Kanban, cada posto para depois de percorrer sua sequência finita. Motores e montagem dos motores filtram AT-120. Em JIT, a demanda recebida e o limite de pedidos do cliente controlam os inícios.
Construir WorkforceFactory diretamente com order=None não ativa essa limitação. Por isso os exemplos deste manual sempre fornecem o pedido, reproduzindo o comportamento da interface web.
Uma reprovação consome o avião fabricado e não gera reposição automática. O pedido pode continuar pendente mesmo quando todos os lotes planejados terminaram. completed_at só é preenchido quando as quantidades aprovadas atendem todo o pedido. O relógio continua até 180 s, mesmo se a entrega acabar antes.
12. Relógio real, aceleração e limite de 180 s
def tick(self):
now = time.monotonic()
elapsed = now - self.last_wall
self.last_wall = now
if self.running and not self.factory.done:
self.factory.advance(self.factory.env.now + elapsed * self.speed)
if self.factory.done:
self.running = False
self.archive()time.monotonic() mede o tempo real decorrido. Se passaram 0,1 s reais e a velocidade é 10×, o alvo avança 1 s simulado. O processo de relógio do servidor chama tick() aproximadamente a cada 0,05 s reais. Isso é diferente do passo humano de 0,25 s simulados.
O navegador consulta o estado aproximadamente a cada 200 ms, mas não controla a agenda do SimPy. Minimizar ou fechar a aba não pausa a fábrica; o comando pause pausa o avanço. finish chama advance(180) e processa o restante sem esperar três minutos reais. O projeto usa Environment, não RealtimeEnvironment.
def advance(self, target):
target = min(float(target), self.duration)
if target > self.env.now:
self.env.run(until=target)
# Include completions exactly at the target, especially t=180.
while self.env.peek() <= target:
self.env.step()Um env.run(until=180) com alvo numérico para antes de processar eventos normais marcados exatamente em 180. O laço com peek() e step() processa esses eventos e seus desdobramentos no mesmo instante. Assim uma conclusão no limite entra no relatório. Eventos depois desse limite permanecem pendentes; não há esvaziamento da fábrica após a rodada.
Use Factory.advance() ao controlar a fábrica. Não chame env.run() sem limite nesse modelo: sample() agenda amostras indefinidamente. Avanços válidos são progressivos; não existe mecanismo de voltar o relógio.
Referência do comportamento da fronteira: SimPy — Environments.
13. Do clique no Phaser ao evento no SimPy
O layout mantém o U da referência IMG_0243.JPG: componentes acima, cauda à direita e montagem/inspeção abaixo. Há um único depósito central, com cinco caixas de blocos representados por miniaturas de dimensões e cores corretas. Subconjuntos aparecem nas bancadas que os recebem; o depósito não armazena produtos intermediários. A expedição/qualidade fica na abertura esquerda, com quatro pilhas por modelo e resultado da inspeção. delivered contém aprovados por modelo; rejected_by_model contém rejeitados por modelo. A soma destes últimos corresponde a rejected, preservando os indicadores do relatório. As pilhas não são estoques de WIP. Todas as mesas começam vazias; miniaturas aparecem apenas quando os materiais chegam à bancada. Ao trabalhar, o operador se aproxima da mesa, vira de costas para a tela e movimenta as mãos junto aos encaixes. Caixas sem estoque mantêm uma amostra visual com a legenda explícita “0 peças · amostra”.
A vista isométrica está separada em factory-scene.js (desenho, animações e câmera) e isometric.js (projeção, posições e destinos). Iso.project(u, v, z) transforma coordenadas do piso em pixels; a altura desloca o objeto para cima. No arraste, pointer.positionToCamera() remove o efeito do zoom e do deslocamento antes de Iso.stationAt() identificar o posto. Objetos mais próximos da frente recebem maior profundidade de desenho.
Os botões + e − ajustam o zoom; arrastar uma área livre desloca a câmera. Enquadrar restaura a visão completa. Zoom e câmera são apenas visuais. Os percursos usam trip.started, trip.ends e o tempo SimPy para posicionar os funcionários; coleta e transporte consomem tempo da rodada.
- A pessoa arrasta um funcionário no canvas ou usa Mover no accordion.
command()envia JSON porPOST /api/action.Session.action()atualiza o tempo corrente, valida o comando e chamafactory.move().- A alocação muda no Python. O trabalho em andamento fica no posto.
- O retorno ou a próxima consulta
GET /api/statealimentaapplyState(). renderRoster(),renderProfile()eFactoryScene.sync()mostram os dados atualizados.
{
"action": "assign",
"worker": "worker-1",
"station": "wings"
}
O servidor usa threading.RLock ao avançar a fábrica e atender consultas/comandos. O relógio real e as requisições HTTP podem ter threads diferentes, mas o estado da simulação é acessado sob esse bloqueio. Isso não transforma os processos SimPy em tarefas paralelas do sistema operacional.
O botão Excluir funcionário aparece apenas quando o posto tem mais de uma pessoa. remove_worker() repete essa verificação no servidor e bloqueia pessoas em transporte, treinamento ou descanso. A remoção preserva os jobs e registra o perfil e as estatísticas em removed_workforce; o esforço anterior continua em labour_seconds. A equipe ativa é persistida, e a figura é removida do Phaser.
No accordion, expandedWorker controla quem está aberto e selected identifica a pessoa selecionada. Um único nó #profile é movido para o painel aberto. Os valores são atualizados sem reconstruir todos os campos a cada consulta. Esses estados são da interface; não mudam competência nem produtividade.
web/lego.js guarda posições de blocos em coordenadas de pinos x, y, z. A projeção transforma essas coordenadas em pontos 2D; model() desenha no Phaser e svg() na montagem ampliada. Alterar a geometria visual não altera a receita de materiais em RAW. Se mudar um produto, revise ambos e os testes de geometria.
O desenho das mãos usa o tempo de animação do Phaser. A produção usa env.now. Nunca incremente entregas ou consuma estoque porque uma animação terminou; essas transições pertencem ao Python.
14. Como ler estatísticas e relatórios
| Indicador | Cálculo e interpretação |
|---|---|
| Entregas | Aviões aprovados por modelo. Rejeitados não entram. |
| WIP | Unidades de subconjuntos em items, incluindo operações em execução e peças já retiradas que esperam no destino. Asas/motores contam duas unidades por lote. Não é o número de aviões. |
| Ciclo | Média dos intervalos entre entregas aprovadas consecutivas. Com entregas em 40, 55 e 75 s, vale (15 + 20) / 2 = 17,5 s. Menos de duas entregas: None. |
| Lead time | Tempo entre o início do componente mais antigo e a entrega aprovada. Não começa necessariamente na emissão do pedido; espera anterior à entrada de matéria-prima não entra nesse relógio. |
| Throughput | Total aprovado × 60 / tempo simulado, em aviões por minuto. |
| Tempo produtivo individual | productive_seconds / env.now. Conta apenas o trabalho efetivamente atribuído à pessoa. |
| Esforço individual | standard_seconds: tempo produtivo ponderado pela velocidade efetiva. Unidade: segundos de trabalho de referência. |
| Lotes individuais | completed_lots soma participações das pessoas ativas no trecho que conclui o lote. Uma pessoa que ajudou e saiu antes pode não receber essa participação. Não some as participações para obter a produção da fábrica. |
| Utilização do posto | Tempo registrado no estado Trabalhando / duração. Na web é uma aproximação atualizada pelo passo humano. labour_seconds soma pessoa-segundos e pode superar o tempo da rodada. |
| Desvio percentual | (Atual − anterior) / anterior × 100. Base zero, primeira rodada ou ciclo indefinido produzem ausência de valor. A anterior é a rodada anterior arquivada, inclusive parcial. |
reporting.build_report() organiza os indicadores nas linhas do exercício e export_report_pdf() compõe o documento com ReportLab. O relatório não executa outra simulação: lê os resultados arquivados. A análise crítica é texto salvo em report_metadata.
Um aumento de WIP não é automaticamente melhoria. Menos ciclo e menos estoque podem ser desejáveis, mas dependem das entregas e do pedido atendido. Compare também cenário, duração, equipe inicial e intervenções; uma variação percentual sozinha não identifica a causa.
15. Configuração, salvamento e reinício
| Arquivo | Conteúdo e efeito |
|---|---|
config.json | Tempos-base, variabilidade inicial, semente, cartões, vagas JIT, qualidade e orientação mista. É carregado ao iniciar o servidor. |
equipe_web.json | Perfis, alocação e treinamentos concluídos. Se já existir, seus perfis substituem a equipe inicial derivada da configuração. |
logistica_web.json | Tempos de caminhada, coleta por peça e manuseio por visita. Aplicar inicia nova rodada. |
pedido_web.json | Quantidades e tipo de sequência usados na próxima inicialização. |
historico_web.json | Resultados arquivados, numeração das rodadas e análises. O salvamento escreve um arquivo temporário antes de substituí-lo. |
resultados/web/ | CSV, JSON de exportação e PDFs dos relatórios. |
archive() usa deepcopy para registrar o resultado sem deixar referências mutáveis ligadas à rodada atual. saved impede arquivar duas vezes a mesma rodada. Perfis são persistidos imediatamente em edições e realocações; ganhos de treinamento são persistidos quando a equipe é salva, inclusive no arquivamento/encerramento normal.
A nova rodada usa perfis atuais, cria outro ambiente SimPy, zera bancadas, subconjuntos, fadiga e estatísticas e repõe as caixas conforme o pedido/cenário da rodada. Fechar normalmente o servidor arquiva uma rodada iniciada ainda não salva. A próxima inicialização começa uma rodada nova; não restaura a agenda SimPy no ponto em que parou.
Para experimentar sem alterar seus dados, use --data-dir tmp/meu-ensaio. Isso isola equipe, pedido e resultados, mas Session ainda lê o config.json da raiz. Para uma configuração inteiramente independente, construa WorkforceFactory em um script com default_config(), como nos exemplos.
16. Exemplos executáveis e resultados esperados
Abra o PowerShell na pasta simulation-lean. O arquivo docs/exemplos_simpy.py contém oito exemplos completos. Eles só imprimem resultados; não alteram equipe, configuração nem histórico salvos.
.\.venv\Scripts\python.exe docs/exemplos_simpy.py todos
.\.venv\Scripts\python.exe docs/exemplos_simpy.py fundamentos
.\.venv\Scripts\python.exe docs/exemplos_simpy.py kanban
.\.venv\Scripts\python.exe docs/exemplos_simpy.py equipe
| Exemplo | O que observar |
|---|---|
fundamentos | Uma Store liga fabricação de 4 s à montagem de 3 s. As cabines são montadas em t=7 e t=11; não em t=7 e t=14, pois os postos trabalham simultaneamente. |
recursos | Um Resource de capacidade 1 atende duas solicitações de 5 s. Conclusões em t=5 e t=10. |
sincronizacao | Três componentes em paralelo liberam o corpo em t=10. |
kanban | Com dois cartões, os lotes iniciam em t=0, t=2 e t=10. O terceiro espera o consumo do primeiro. |
limite | Mostra a diferença entre parar o relógio em 180 e processar as conclusões desse instante. |
rodada | Executa a fábrica web completa: semente 42, cenário 3, equipe inicial, pedido de 10 aviões de cada modelo. Nesta versão: 4 entregas de AT-470, 3 de AT-120 e ciclo de aproximadamente 19,21 s, com os tempos padrão de logística. |
cenarios | Compara os sete cenários usando novas fábricas com a mesma configuração, equipe e pedido. |
equipe | Compara o corpo com uma ou duas pessoas. Nesta configuração, as entregas mudam de 7 para 8 aviões e o WIP de 13 para 12. O efeito depende também dos demais postos, das peças e do pedido. |
Rodada completa sem interface
def rodada():
config = default_config()
equipe = initial_roster(config)
pedido = {'quantities': {'AT-470': 10, 'AT-120': 10}, 'sequence': 'alternate'}
fabrica = WorkforceFactory(config, scenario=3, roster=equipe, order=pedido)
fabrica.advance(180)
resultado = fabrica.result()
assert resultado['complete'] and resultado['time_seconds'] == 180
print(json.dumps({k: resultado[k] for k in ['delivered', 'cycle_seconds', 'order']}, ensure_ascii=False, indent=2))Use default_config() para repetir a demonstração. load_config() usa os valores salvos no seu projeto e pode produzir outros resultados. Não reutilize a fábrica já avançada para comparar cenários: crie uma instância nova em cada experimento.
Resultado da comparação demonstrativa
| Cenário | Entregas | WIP | Ciclo em s |
|---|---|---|---|
| 1 | 7 | 22 | 19,21 |
| 2 | 7 | 22 | 20,25 |
| 3 | 7 | 13 | 19,21 |
| 4 | 6 | 9 | 20,60 |
| 5 | 5 | 8 | 29,25 |
| 6 | 1 | 5 | Indefinido |
| 7 | 7 | 11 | 20,00 |
Valores obtidos ao executar cenarios nesta edição. Não são um ranking universal de políticas Lean. A fábrica inicia vazia, os parâmetros são demonstrativos e o pedido é finito. Para estimar variabilidade, repita o experimento com várias sementes e compare distribuições de resultados.
17. Onde alterar cada comportamento
| Objetivo | Ponto de alteração | Verificação necessária |
|---|---|---|
| Mudar tempo-base | config.json → employees → posto → base_seconds. | Reiniciar servidor; manter a mesma equipe/pedido ao comparar. |
| Mudar competência individual | Editor de perfil; em scripts, perfis passados a WorkforceFactory. | Conferir função correta e limite 0,1–3. |
| Mudar fadiga ou treinamento | labour_clock() e intervention(). | Atualizar textos de ajuda, limites e testes; garantir que pessoas indisponíveis não contribuam. |
| Mudar cartões ou vagas JIT | kanban_cards e jit_orders. | Conservação de cartões e limites de pedidos. |
| Ajustar logística | Accordion do depósito; processos em logistics.py. | Conferir posse das peças, WIP, cartão no consumo e recebimento após descarga. Se tempos dependerem da distância ou competência, altere o motor e a explicação dos parâmetros. |
| Adicionar retrabalho | finish(), receitas e filas. | Conservação de blocos, lead time, cartões e pedido pendente. |
| Adicionar bancadas paralelas | Redesenhar postos, processos, jobs e filas. | Atualmente jobs tem uma chave por posto. Trocar só MAX_PER_STATION não cria paralelismo de bancadas. |
| Alterar duração web | WorkforceFactory.__init__, ação finish e valores 180/03:00 na interface. | Rever amostragem, avaria, textos e teste do instante final. Não existe hoje um único seletor de duração web. |
| Mudar aparência | CSS e desenho Phaser. | Verificar telas menores, contraste, seleção e arraste; não alterar lógica Python para uma mudança de cor. |
Erros de interpretação frequentes
- Pedido não aparece no estoque: pedido é demanda; estoque contém objetos produzidos.
- Simulação nunca termina: não use
env.run()sem alvo na fábrica, pois há processos periódicos. - Mais pessoas não aumentaram as entregas: outro posto, materiais, cartões ou o pedido podem limitar o fluxo.
- O tempo mudou ao trocar pessoa: competência e fadiga mudam a velocidade, mas o esforço do lote iniciado é preservado.
- O perfil mostra Trabalhando com a rodada pausada: atividade descreve o estado do modelo;
runningcontrola se o relógio está avançando. - Um operador volta vazio após a entrega: o material já foi descarregado, mas a caminhada de retorno ainda consome tempo. Pausar congela essa viagem.
18. Validação, exercícios e referências
O programa verifica regras de conservação: os blocos consumidos devem corresponder aos blocos no WIP, entregues ou rejeitados. Testes também cobrem a pausa de 30 s, o limite de cartões, pedidos finitos, trabalho em equipe e persistência. Ao mudar regras, use essas invariantes para detectar perdas ou duplicações.
.\.venv\Scripts\python.exe -m unittest discover -s tests -v
node --test tests/test_isometric.js tests/test_lego.js
.\.venv\Scripts\python.exe docs/exemplos_simpy.py todos
# Regerar o manual e atualizar seus trechos de código:
.\.venv\Scripts\python.exe docs/gerar_manual.py
Node é necessário apenas para a verificação JavaScript indicada, não para rodar o simulador. Os exemplos pequenos incluem verificações de seus tempos esperados e podem ser executados separadamente.
Exercícios de leitura
- Antes de executar
kanban, explique por que o terceiro lote não começa em t=4. - Localize quem cria o evento em
WorkforceStation.work()e quem o conclui emlabour_clock(). - Repita
equipereforçando outro posto. Observe entregas e WIP, sem presumir que a melhora de velocidade local será global. - Crie duas fábricas idênticas. Avance uma diretamente até 180 e a outra em alvos crescentes; compare as entregas sem introduzir intervenções.
- Configure rejeição em uma cópia da configuração. Explique por que um pedido pode ficar pendente apesar de todos os lotes terem sido produzidos.
Fontes e manutenção
A descrição das regras e os trechos vêm dos arquivos locais listados no mapa. A ficha lego fabric.pdf fundamenta o exercício e seus indicadores; as fotos de detalhe aviao fundamentam a montagem visual. A configuração de equipes, tempos e fadiga é a implementação didática deste projeto.
- SimPy — processos e geradores
- SimPy — controle do ambiente e do tempo
- SimPy — filas e recursos compartilhados
- SimPy — eventos e condições
O gerador renova os trechos e os números de linha. As explicações e os resultados demonstrativos precisam ser revisados quando as regras do motor mudarem. O manual pode ser aberto diretamente como arquivo HTML, sem internet; os links de referência externa exigem conexão.