◇ OpenCyber Academy
Técnico · Capítulo 4 · Português

Capítulo 4 — Scripts e formatos de dados

Ensino gratuito e aberto para quem deseja proteger pessoas e tecnologias. Não é preciso criar uma conta para estudar ou consultar as respostas.

Capítulo 4 — Scripts e formatos de dados

Mapa da aula

Aula 04 publicada. Mapa, exemplo, EX013.

flowchart TD
  N0["4.1 Formatos que você vai encontrar"]
  N1["4.2 Uma ordem de validação que falha fechada"]
  N2["4.3 Coordenadas de registro"]
  N3["4.5 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4
  N4 --> EX["Exemplo resolvido"]
  EX --> Q["Exercício EX013"]

Desenho lógico

flowchart LR
  N0["4.1 Formatos que você vai encontrar"]
  N1["4.2 Uma ordem de validação que falha fechada"]
  N2["4.3 Coordenadas de registro"]
  N3["4.5 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX013"]
[4.1 Formatos que você vai encontrar]
    |
    v
[4.2 Uma ordem de validação que falha fechada]
    |
    v
[4.3 Coordenadas de registro]
    |
    v
[4.5 Atividade prática]
    |
    v
[Síntese do capítulo]
    |
    v
[Exemplo resolvido] --> [Exercício EX013]

Objetivos de aprendizagem e conexão

Ao final deste capítulo, você deverá ser capaz de:

Os scripts do técnico nos capítulos seguintes — decodificadores, auditores, detectores — leem todos dados produzidos por outra pessoa. Este capítulo os torna rigorosos quanto ao que aceitam, porque um script que "conserta" silenciosamente uma entrada ruim produz respostas erradas com confiança.

4.1 Formatos que você vai encontrar

4.2 Uma ordem de validação que falha fechada

Verifique numa ordem em que cada etapa possa presumir que as anteriores passaram:

  1. Estrutura: o número certo de campos. Se não, nada mais no registro é confiável — rejeitar.
  2. Identidade e tipo: estação e tag conhecidas; unidade conforme o esquema (um nível em pés não é um nível em metros); o valor é interpretável como número.
  3. Finitude: float("nan") e float("inf") são interpretados com sucesso, mas não são medições — rejeitar.
  4. Tempo: o timestamp é interpretado no formato combinado; não está no futuro além de uma pequena tolerância.
  5. Plausibilidade: o valor está dentro da faixa física do instrumento e do processo.

Rejeite o que não pode ser interpretado (estrutura errada, unidade errada, não é número). Ponha em quarentena o que é interpretável, mas implausível (fora da faixa, tempo futuro): uma falha real de sensor e um ataque real se parecem com isso, e um operador precisa vê-lo, em vez de vê-lo descartado em silêncio. Nunca force a conversão: transformar "12,5" em 12,5 ou 125 adivinhando o separador decimal cria dados que nunca foram medidos.

4.3 Coordenadas de registro

Toda constatação deve dizer de onde veio — arquivo, linha e campo —, para que outra pessoa possa verificá-la. O csv.reader.line_num conta as linhas físicas lidas até então, incluindo o cabeçalho, de modo que a primeira linha de dados é a linha 2. Campos entre aspas que contêm quebras de linha fazem as linhas físicas diferirem dos registros; o line_num informa a linha em que o registro terminou, o que continua sendo uma coordenada utilizável.

Quando colar uma constatação num chamado, mantenha o número da linha junto ao nome da estação, para que a próxima pessoa possa abrir a exportação e ir direto à linha. Se um campo entre aspas ocupar várias linhas, diga isso, porque a coordenada passa a marcar o fim do registro, e não o seu início.

4.4 Exemplo resolvido: validar uma exportação de estações (sintético)

Uma exportação do historiador da Kestrel Water lista uma leitura por estação. O momento da análise é 06:00Z de 12 de outubro de 2026.

Laboratório 4 (salve como lab04.py e execute com python3 lab04.py):

# Lab 4 - validate a telemetry export with record coordinates (offline, standard library only)
# Synthetic CSV export from Kestrel Water stations; "now" is 2026-10-12T06:00:00Z.
import csv, io, json, math
from datetime import datetime, timezone
export = """station,tag,value,unit,ts
KW-01,LEVEL_M,3.42,m,2026-10-12T05:58:00Z
KW-02,CL2_MGL,0.61,mg/L,2026-10-12T05:58:00Z
KW-03,LEVEL_M,,m,2026-10-12T05:58:00Z
KW-04,CL2_MGL,7.9,mg/L,2026-10-12T05:58:00Z
KW-05,FLOW_LS,"12,5",L/s,2026-10-12T05:58:00Z
KW-06,LEVEL_M,2.10,ft,2026-10-12T05:58:00Z
KW-07,LEVEL_M,2.95,m,2026-10-12 05:58
KW-08,LEVEL_M,nan,m,2026-10-12T05:58:00Z
KW-09,FLOW_LS,41.0,L/s,2026-10-12T07:15:00Z
KW-10,LEVEL_M,1.88,m,2026-10-12T05:58:00Z,extra
"""
SCHEMA = {"LEVEL_M": ("m", 0.0, 12.0), "CL2_MGL": ("mg/L", 0.0, 5.0), "FLOW_LS": ("L/s", 0.0, 400.0)}
now = datetime(2026, 10, 12, 6, 0, tzinfo=timezone.utc)
result = {"accept": [], "reject": [], "quarantine": []}
reader = csv.reader(io.StringIO(export))
header = next(reader)
for row in reader:
    where = f"line {reader.line_num}"
    if len(row) != len(header): result["reject"].append((where, f"{len(row)} fields, expected {len(header)}")); continue
    r = dict(zip(header, row))
    unit, lo, hi = SCHEMA[r["tag"]]
    if r["unit"] != unit: result["reject"].append((where, f"{r['station']} unit {r['unit']!r}, expected {unit!r}")); continue
    try: v = float(r["value"])
    except ValueError: result["reject"].append((where, f"{r['station']} value {r['value']!r} not a number")); continue
    if not math.isfinite(v): result["reject"].append((where, f"{r['station']} value not finite")); continue
    try:
        ts = datetime.strptime(r["ts"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
    except ValueError: result["reject"].append((where, f"{r['station']} timestamp {r['ts']!r} not RFC 3339 UTC")); continue
    if ts > now: result["quarantine"].append((where, f"{r['station']} timestamp {(ts - now).seconds // 60} min in the future")); continue
    if not lo <= v <= hi: result["quarantine"].append((where, f"{r['station']} {r['tag']}={v} outside {lo}-{hi}")); continue
    result["accept"].append((where, r["station"]))
for k in ("reject", "quarantine"):
    for where, why in result[k]: print(f"{k:<10} {where:<8} {why}")
print(json.dumps({k: len(v) for k, v in result.items()}))

Saída esperada (Python 3.13.5):

reject     line 4   KW-03 value '' not a number
reject     line 6   KW-05 value '12,5' not a number
reject     line 7   KW-06 unit 'ft', expected 'm'
reject     line 8   KW-07 timestamp '2026-10-12 05:58' not RFC 3339 UTC
reject     line 9   KW-08 value not finite
reject     line 11  6 fields, expected 5
quarantine line 5   KW-04 CL2_MGL=7.9 outside 0.0-5.0
quarantine line 10  KW-09 timestamp 75 min in the future
{"accept": 2, "reject": 6, "quarantine": 2}

Só duas das dez linhas são aceitas. Cada rejeição nomeia a sua linha: um valor vazio (linha 4), uma vírgula decimal dentro de um campo entre aspas (linha 6), um nível em pés (linha 7), um timestamp sem segundos nem deslocamento (linha 8), nan (linha 9) e um campo a mais (linha 11). As linhas em quarentena são as interessantes: cloro a 7,9 mg/L está fora da faixa do instrumento — uma falha de sensor, um erro de escala ou um valor manipulado —, e o timestamp da KW-09 está 75 minutos no futuro, o que, no Capítulo 7, você aprenderá a suspeitar ser um problema de relógio. Ambas são passadas a uma pessoa, com as suas coordenadas.

O resumo em JSON na última linha é deliberadamente legível por máquina, para que um job de monitoramento possa alertar quando a contagem de rejeições subir.

4.5 Atividade prática

Acrescente ao Laboratório 4 uma verificação de identidade da estação: só KW-01 a KW-12 são válidas. Depois acrescente uma linha KW-13,LEVEL_M,2.0,m,2026-10-12T05:58:00Z e confirme que ela é rejeitada com o seu número de linha. Por fim, faça o analisador de timestamps aceitar um deslocamento explícito +00:00, além de Z, sem aceitar timestamps ingênuos (sem fuso).

Rejeite a KW-13 com o seu número de linha e um motivo que nomeie o conjunto permitido. Para os timestamps, aceite tanto Z quanto um deslocamento explícito de mais zero, e continue rejeitando um horário local sem fuso. Execute o laboratório de novo e confirme que a contagem de rejeições do resumo em JSON aumenta em um para a nova estação.

4.6 Erros comuns e limites

Exercícios — capítulo 4

EX013 — Preveja antes de executar. Objetivo: raciocinar sobre o validador. Insumos: o Laboratório 4 com três novas linhas: KW-11,LEVEL_M,12.0,m,2026-10-12T05:59:00Z; KW-12,LEVEL_M,inf,m,2026-10-12T05:59:00Z; KW-02,CL2_MGL,-0.0,mg/L,2026-10-12T05:59:00Z. Passos: prever cada veredito e o novo resumo, depois executar. Evidência esperada: previsões, saída real, explicação de qualquer surpresa. Rubrica: 3 = aceitar, rejeitar (não finito), aceitar; resumo {"accept": 4, "reject": 7, "quarantine": 2}; observa que −0,0 deveria ser normalizado; 2 = um errado, mas explicado; 1 = palpites; 0 = nada.

EX014 — Rejeitar ou pôr em quarentena? Objetivo: aplicar a distinção. Insumos: (a) vazão de −3,0 L/s; (b) unidade "M" em vez de "m"; (c) nível de 11,9 m num tanque de 12 m; (d) timestamp 4 s no futuro. Passos: decidir e justificar. Evidência esperada: quatro vereditos. Rubrica: 3 = (a) quarentena (interpretável, implausível; pode ser fluxo reverso ou offset); (b) rejeitar, ou normalizar apenas se o esquema permitir explicitamente unidades sem distinção de maiúsculas — não adivinhar; (c) aceitar, talvez com um alarme de nível alto; (d) aceitar dentro de uma tolerância de relógio combinada; 2 = um fraco; 1 = rejeita todos; 0 = nada.

EX015 — JSON com armadilhas. Objetivo: endurecer a análise de JSON. Insumos: {"station": "KW-03", "value": NaN}. Passos: mostrar o que o json.loads devolve por padrão e escrever a mudança de uma linha que o rejeita. Evidência esperada: código e comportamento. Rubrica: 3 = por padrão devolve um float nan; json.loads(s, parse_constant=lambda c: (_ for _ in ()).throw(ValueError(c))) ou uma função equivalente que lança exceção; explica que a RFC 8259 não permite NaN; 2 = funciona, sem explicação; 1 = tentativa; 0 = nada.

EX016 — Resumo para monitoramento. Objetivo: desenhar um resultado legível por máquina. Insumos: Laboratório 4. Passos: estender o resumo em JSON com contagens por motivo e por estação e propor uma condição de alerta sobre ele. Evidência esperada: JSON de exemplo e a condição. Rubrica: 3 = chaves estáveis, contagens por motivo, alerta como "qualquer quarentena de CL2_MGL, ou rejeições > 10 % das linhas em duas exportações consecutivas"; 2 = sem divisão por motivo; 1 = apenas prosa; 0 = nada.

Síntese do capítulo

Os dados operacionais chegam malformados, ambíguos e, às vezes, hostis. Analise com analisadores de verdade, valide numa ordem que falhe fechada, nunca force conversões e relate cada constatação com coordenadas. Rejeite o que não consegue interpretar; ponha em quarentena o que consegue interpretar, mas não deveria acreditar; e mantenha os resumos legíveis por máquina, para que o próprio validador possa ser monitorado.

Continue estudando — capítulo 04 de 18

← Capítulo 03 · Índice de capítulos do curso · Capítulo 05 →

Siga para a próxima lição escrita.