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:
- analisar dados operacionais em CSV e JSON com a biblioteca padrão, sem divisão de strings escrita à mão;
- validar cada registro frente a um esquema explícito: número de campos, unidades, forma numérica, finitude, formato de timestamp e faixa física;
- relatar cada problema com as suas coordenadas de registro (arquivo e linha), para que outra pessoa possa encontrá-lo;
- escolher entre rejeitar e pôr em quarentena, e resumir os resultados num formato legível por máquina.
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
- CSV — exportações de historiadores, IHMs e planilhas. A RFC 4180 descreve o formato comum, incluindo as aspas: um campo que contém uma vírgula precisa estar entre aspas duplas (RFC 4180). Dividir linhas nas vírgulas quebra no primeiro valor entre aspas; use o
csv.reader, que também acompanha oline_num. - JSON — APIs, exportações de configuração e saída de ferramentas (RFC 8259). Use
json.loads; note que o analisador do Python aceita os tokens não padronizadosNaNeInfinity, a menos que você passeparse_constantpara rejeitá-los. - Timestamps — use RFC 3339 / ISO 8601 com deslocamento explícito, de preferência UTC com
Z(RFC 3339). Um timestamp sem deslocamento é ambíguo em até um dia e nas mudanças de horário de verão. - Quadros binários — a decodificação com
structdo Capítulo 2; sempre verifique os comprimentos antes de desempacotar.
4.2 Uma ordem de validação que falha fechada
Verifique numa ordem em que cada etapa possa presumir que as anteriores passaram:
- Estrutura: o número certo de campos. Se não, nada mais no registro é confiável — rejeitar.
- 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.
- Finitude:
float("nan")efloat("inf")são interpretados com sucesso, mas não são medições — rejeitar. - Tempo: o timestamp é interpretado no formato combinado; não está no futuro além de uma pequena tolerância.
- 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
- Dividir o CSV nas vírgulas.
- Forçar a conversão de valores ambíguos em vez de rejeitá-los.
- Deixar
nanpassar porquenan > hié falso. - Aceitar timestamps ingênuos e presumir horário local.
- Relatar "6 rejeitados" sem coordenadas, o que ninguém consegue verificar.
-
O Laboratório 4 verifica leituras isoladas; taxa de variação e valores travados exigem uma série (Capítulo 8).
-
Deixar nan passar porque as comparações com ele são falsas, de modo que uma verificação de faixa nunca dispara.
- Relatar apenas totais, sem coordenadas, de modo que ninguém consegue encontrar as linhas ruins.
- Converter à força uma vírgula decimal num float e gravar o valor inventado no historiador.
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.