Programação do zero: caderno operacional

Missão, hipótese e ambiente

Missão: construir uma fatia pequena do sistema de manutenção: receber uma OS já convertida de JSON, formato textual de dados usado entre sistemas, para valores Python; validar seus campos; calcular prioridade e peças; e detectar códigos duplicados.

Hipótese: separar validação, cálculo e apresentação permite testar a regra sem ERP; conjunto reduz o crescimento da detecção de duplicados em entradas grandes, mas uma lista adicional continua necessária quando o contrato exige preservar a primeira ordem de repetição.

Pré-requisitos técnicos: Python 3.11 ou superior e terminal. Consulte com python --version no Windows ou python3 --version quando esse for o comando do ambiente. Duração estimada: 90–150 minutos. Nenhuma biblioteca externa, rede, credencial ou dado real é necessária.

Crie uma pasta temporária programacao-os-lab e, dentro dela, o arquivo os_lab.py. Entregue também evidencias.md com versão, comando, previsões, saída, diagnóstico e decisão. Use apenas IDs sintéticos.

Antes de executar: preveja

Preencha a última coluna somente depois do teste:

Caso Resultado esperado Observado
criticidade 4, sem risco alta
criticidade 1, com risco imediata
criticidade True ValueError
peças com quantidades 2 e 1 total 3
códigos A, B, A, B duplicados A, B nessa ordem
lista vazia nenhum duplicado

O programa completo

Copie exatamente o código. Ele usa apenas a biblioteca padrão do Python.

from collections import Counter
from timeit import repeat
import platform


def ler_id(valor):
    if not isinstance(valor, str) or not valor.startswith("OS-") or len(valor) > 40:
        raise ValueError("id deve ser texto OS-... com ate 40 caracteres")
    return valor


def ler_criticidade(valor):
    if type(valor) is not int:
        raise ValueError("criticidade deve ser um inteiro")
    if not 1 <= valor <= 5:
        raise ValueError("criticidade deve estar entre 1 e 5")
    return valor


def validar_pecas(pecas, limite=10_000):
    if type(pecas) is not list:
        raise ValueError("pecas deve ser uma lista")
    if len(pecas) > limite:
        raise ValueError("quantidade de pecas excede o limite")
    for peca in pecas:
        if type(peca) is not dict:
            raise ValueError("cada peca deve ser um dicionario")
        if not isinstance(peca.get("codigo"), str) or not peca["codigo"].strip():
            raise ValueError("codigo deve ser texto nao vazio")
        if type(peca.get("quantidade")) is not int or peca["quantidade"] < 0:
            raise ValueError("quantidade deve ser inteiro nao negativo")
    return pecas


def classificar_prioridade(criticidade, risco_vida):
    if type(risco_vida) is not bool:
        raise ValueError("risco_vida deve ser booleano")
    if risco_vida:
        return "imediata"
    if criticidade >= 4:
        return "alta"
    return "normal"


def totalizar_pecas(pecas):
    total = 0
    for peca in pecas:
        total = total + peca["quantidade"]
    return total


def duplicados_lento(codigos):
    return sorted({codigo for codigo in codigos if codigos.count(codigo) > 1})


def duplicados_em_ordem(codigos):
    vistos = set()
    repetidos = set()
    resultado = []
    for codigo in codigos:
        if codigo in vistos and codigo not in repetidos:
            repetidos.add(codigo)
            resultado.append(codigo)
        vistos.add(codigo)
    return resultado


def executar_os(os):
    id_os = ler_id(os.get("id"))
    criticidade = ler_criticidade(os.get("criticidade"))
    pecas = validar_pecas(os.get("pecas"))
    return {
        "id": id_os,
        "prioridade": classificar_prioridade(criticidade, os.get("risco_vida")),
        "total_pecas": totalizar_pecas(pecas),
        "duplicados": duplicados_em_ordem([p["codigo"] for p in pecas]),
    }


def exigir_erro(funcao, trecho):
    try:
        funcao()
    except ValueError as erro:
        assert trecho in str(erro)
    else:
        raise AssertionError("era esperado ValueError")


def testar():
    assert classificar_prioridade(4, False) == "alta"
    assert classificar_prioridade(1, True) == "imediata"
    assert totalizar_pecas([]) == 0
    assert totalizar_pecas([{"codigo": "F", "quantidade": 2}]) == 2
    exigir_erro(lambda: ler_id("742"), "OS-")
    exigir_erro(lambda: ler_criticidade(True), "inteiro")
    exigir_erro(lambda: ler_criticidade(6), "entre 1 e 5")
    exigir_erro(lambda: validar_pecas("nao e lista"), "lista")

    exemplos = [[], ["A"], ["A", "A"], ["A", "B", "A", "B"]]
    for codigos in exemplos:
        esperado = sorted(codigo for codigo, n in Counter(codigos).items() if n > 1)
        assert sorted(duplicados_em_ordem(codigos)) == esperado
        assert duplicados_lento(codigos) == esperado

    base = ["A", "B", "A", "C", "B"]
    assert set(duplicados_em_ordem(reversed(base))) == set(duplicados_em_ordem(base))
    print("testes: OK")


def medir():
    print("python:", platform.python_version())
    for tamanho in (100, 1_000, 10_000):
        codigos = [f"P-{i // 2}" for i in range(tamanho)]
        repeticoes = 5 if tamanho < 10_000 else 3
        lento = min(repeat(lambda: duplicados_lento(codigos), number=1, repeat=repeticoes))
        ordem = min(repeat(lambda: duplicados_em_ordem(codigos), number=1, repeat=repeticoes))
        print(f"n={tamanho:>5} lento={lento:.6f}s ordem={ordem:.6f}s")


if __name__ == "__main__":
    testar()
    os_exemplo = {
        "id": "OS-DEMO-742",
        "criticidade": 4,
        "risco_vida": False,
        "pecas": [
            {"codigo": "FUS-10", "quantidade": 2},
            {"codigo": "CAB-02", "quantidade": 1},
            {"codigo": "FUS-10", "quantidade": 1},
        ],
    }
    print("resultado:", executar_os(os_exemplo))
    medir()

Execute:

python os_lab.py

Se seu ambiente usa outro nome, substitua apenas o executável por python3. A duração dos benchmarks varia, mas a saída deve começar assim:

testes: OK
resultado: {'id': 'OS-DEMO-742', 'prioridade': 'alta', 'total_pecas': 4, 'duplicados': ['FUS-10']}
python: 3...

Não avance se testes: OK não aparecer. O processo do Python devolve código de saída zero quando chega ao fim normalmente e diferente de zero quando uma exceção não tratada interrompe a execução. O texto exibido é a primeira evidência, mas não basta afirmar “funcionou”: copie para evidencias.md o comando, a versão e as linhas observadas. Compare a prioridade, o total e os duplicados com suas previsões. Uma divergência deve abrir uma investigação, não ser corrigida no relatório para parecer certa.

Faça uma segunda execução alterando somente criticidade de 4 para 3. Antes de rodar, preveja normal. Depois restaure 4, altere somente risco_vida para True e preveja imediata. Esses dois experimentos isolam cada condição: quando várias entradas são modificadas juntas, não sabemos qual mudança causou o resultado. Essa disciplina — uma hipótese e uma variável por experimento — será reutilizada em depuração, teste de API e avaliação de sistemas de IA.

Leia o programa por blocos

As importações trazem contagem independente, cronômetro de benchmark e versão do runtime. ler_id, ler_criticidade e validar_pecas formam a fronteira: recusam identificador sem prefixo ou longo demais, forma incorreta, faixa inválida, vazio, número negativo e volume excessivo antes do cálculo. O limite evita que uma entrada enorme consuma recursos sem controle.

As três funções seguintes são o núcleo. Classificar usa condições; totalizar mantém o estado total durante a repetição; detectar duplicados combina conjunto para pertencimento com lista para preservar ordem. executar_os coordena as funções e devolve um dicionário, sem rede nem gravação no ERP.

exigir_erro prova que uma operação realmente falhou pelo motivo esperado. testar verifica caminho feliz, vazio, fronteira e inválido. A comparação com Counter funciona como oráculo independente para a propriedade “somente valores com contagem maior que um aparecem”. Inverter a entrada não deve mudar o conjunto de duplicados, embora possa mudar sua ordem.

medir gera entradas sintéticas, aquece implicitamente por repetições e registra o menor tempo. A documentação de timeit explica por que repetições são preferíveis a um único cronômetro. O benchmark é didático: compare curvas na mesma máquina; não publique esses números como capacidade de produção.

Rastreie o estado antes de confiar na intuição

Execute mentalmente totalizar_pecas com quantidades 2, 1 e 1. Em vez de dizer “o laço soma”, registre a operação concreta:

Volta peça atual total antes expressão total depois
início total = 0 0
1 2 0 0 + 2 2
2 1 2 2 + 1 3
3 1 3 3 + 1 4

A tabela é uma execução manual. Ela mostra a invariante do laço: ao iniciar cada nova volta, total representa a soma das peças já visitadas. No código defeituoso, essa propriedade deixa de valer na segunda volta. Escrever a invariante transforma “parece certo” numa afirmação verificável e ajuda a localizar exatamente quando o estado se desvia.

Faça o mesmo para duplicados_em_ordem usando A, B, A. Depois das duas primeiras voltas, vistos contém A e B, repetidos está vazio e resultado está vazio. Na terceira, A já está em vistos, mas ainda não em repetidos; por isso A entra uma única vez no resultado. Se houver um quarto A, a segunda condição impede duplicação na saída.

Provoque uma falha e diagnostique

Troque temporariamente, em totalizar_pecas:

total = total + peca["quantidade"]

por:

total = peca["quantidade"]

Execute novamente. A asserção da peça única ainda passa — por isso um único exemplo é insuficiente. Acrescente antes de exigir_erro:

assert totalizar_pecas([
    {"codigo": "A", "quantidade": 2},
    {"codigo": "B", "quantidade": 1},
]) == 3

Agora o teste falha porque o observado é 1. Registre em evidencias.md: linha, entrada mínima, esperado, observado, hipótese e correção. Restaure a soma e confirme testes: OK. Essa alteração defeituosa deliberada é um mutante; um teste “mata” o mutante quando falha diante dele.

Não capture Exception apenas para esconder a tela vermelha. Um erro silenciado produz evidência enganosa e pode fazer a OS parecer processada quando não foi. Neste laboratório, exigir_erro captura somente ValueError, confere parte da mensagem e falha se nenhuma exceção ocorrer. Em produção, a borda traduziria a falha para uma resposta segura ao usuário e registraria contexto técnico sem payload sensível; o núcleo não deveria continuar com um valor inválido.

Interprete o benchmark sem inventar certeza

Transcreva os três tamanhos numa tabela e calcule aproximadamente quantas vezes o tempo aumentou quando n passou de 1.000 para 10.000. Para a versão lenta, um aumento próximo de cem vezes é compatível com crescimento quadrático, mas ruído e constantes podem afastar a medição desse número. Para a versão com conjuntos, esperamos crescimento muito menor. Não compare tempos obtidos em máquinas diferentes como se apenas o algoritmo tivesse mudado.

O uso de min segue a orientação do timeit: tempos maiores podem conter interrupções de outros processos, enquanto o menor valor aproxima uma execução com menos interferência. Isso não transforma uma amostra curta em estudo de capacidade. Um benchmark profissional também controlaria distribuição dos códigos, consumo de memória, versão do interpretador, aquecimento, carga concorrente e percentis. Aqui, a decisão limitada é: para até 10.000 códigos sintéticos neste ambiente, qual estratégia cresce melhor e qual preserva o contrato de ordem?

Falhas comuns e runbook curto

Sintoma Hipótese Verificação Correção
python não encontrado comando/instalação ausente python --version e python3 --version instalar Python ou usar o comando disponível
IndentationError blocos com recuos diferentes observar linha indicada usar quatro espaços consistentemente
teste esperava erro, mas passou validação permissiva imprimir type(valor) no caso mínimo aplicar tipo e faixa explicitamente
benchmark quase igual entrada pequena ou ruído repetir e aumentar n comparar vários tamanhos, sem I/O medido
execução lenta demais algoritmo quadrático em 10.000 interromper com Ctrl+C reduza n, registre e explique o crescimento

Segurança, aceite e limpeza

Não substitua os dados sintéticos por exportação real do ERP. Não registre nome, descrição livre, token ou credencial. A validação positiva permite apenas estruturas e faixas declaradas; numa aplicação real, SQL deve usar parâmetros, e autorização deve verificar quem pode acessar a OS — validação de dados não concede permissão.

O laboratório está aceito quando:

  • a versão e o comando constam em evidencias.md;
  • a execução termina com testes: OK antes e depois da falha corrigida;
  • o erro lógico tem entrada mínima, esperado, observado e causa;
  • vazio, repetição, inválido e limite possuem evidência;
  • a tabela 100/1.000/10.000 contém resultados reais ou bloqueio documentado;
  • a decisão explica tempo, memória e preservação de ordem, sem afirmar que O(n) mede segundos;
  • nenhum segredo ou dado produtivo aparece nos artefatos.

Desafio de transferência: altere o contrato para retornar duplicados na primeira ordem em que se repetem. Depois adapte o domínio para detectar números de chamados de TI duplicados. As funções mudaram por causa do transporte ou apenas pelos dados e requisitos?

Recuperação ativa: sem olhar, explique por que True é rejeitado como criticidade, qual estado muda em totalizar_pecas, por que o conjunto sozinho não representa a ordem contratual, qual propriedade Counter verifica e por que o benchmark não prova desempenho do ERP.

Para limpar, remova apenas a pasta temporária programacao-os-lab que você criou. Preserve evidencias.md se ela fizer parte do seu portfólio, depois de confirmar que contém somente dados sintéticos.

Teste de fixação

Comprove o que você aprendeu

Responda todas as questões. O gabarito comentado só aparece depois do envio.

1. Qual desenho permite comparar lista quadrática e conjunto linear com evidência mais confiável?
2. Qual propriedade deve valer para qualquer entrada da função que retorna IDs duplicados?
3. O requisito passa a exigir a primeira ordem de aparição dos duplicados. Qual decisão é justificável?

Consulta universal

O que você quer encontrar?

Títulos, capítulos, conceitos, termos, laboratórios e ferramentas em uma única busca.

Digite pelo menos dois caracteres.