NILM — Desagregação Não Intrusiva de Cargas¶
Este documento descreve o algoritmo de NILM (Non-Intrusive Load Monitoring) do EnergIA, a lógica de recomendação de horário de uso derivada dele, o diagrama de arquitetura de ponta a ponta e a metodologia de avaliação de acurácia — item exigido pelo briefing do Desafio 6 ("relatório de acurácia obtida").
1. Abordagem: detecção de eventos (event-based)¶
O briefing do Desafio 6 recomenda, para uma PoC, a "análise de transições de potência ativa (event-based), que identifica liga/desliga de equipamentos por saltos característicos no sinal agregado" — é exatamente a técnica implementada em analytics/services.py::detect_power_steps.
A cada leitura consecutiva de TelemetryReading.active_power, calcula-se o delta absoluto. Deltas acima de um limiar configurável (200 W por padrão) são registrados como um degrau de potência (PowerStepEvent), com:
delta_w— magnitude do salto em Watts.delta_a— magnitude do salto de corrente em Amperes (informativo).duration_s— para degraus de subida, o tempo até a potência retornar perto da base anterior (estimativa de quanto tempo o aparelho ficou ligado), limitado a uma janela de 24h para evitar custo O(n²).hour_of_day— hora local em que o evento ocorreu (0-23).
Nenhum degrau é identificado automaticamente de forma definitiva: todo evento novo nasce com status='pending' e appliance=None. A classificação (seção 2) só preenche um palpite (suggested_appliance_name) para pré-preencher o formulário de identificação manual — os totais de consumo por aparelho (LoadDisaggregation) só refletem aparelhos que o usuário confirmou manualmente.
2. Classificação em três camadas¶
analytics/services.py::_classify_event tenta, em ordem, da mais específica à mais genérica:
Camada A — Árvore de decisão por medidor¶
Um DecisionTreeClassifier (scikit-learn) treinado exclusivamente com os eventos confirmados manualmente (status='confirmed') daquele medidor, usando [delta_w, duration_s, hour_of_day] como features e o nome do aparelho como rótulo.
Exige no mínimo 6 eventos confirmados cobrindo pelo menos 2 aparelhos distintos; abaixo disso, a camada é pulada. A predição só é aceita se a probabilidade máxima for ≥ 60% — caso contrário, cai para a camada B.
Por que só eventos confirmados treinam o modelo: eventos auto (classificados automaticamente) nunca entram no treino. Se entrassem, um erro de classificação se tornaria dado de treino para o próximo erro — um loop de auto-reforço já identificado e corrigido no projeto (ver feedback_django_ops do histórico de decisões).
Camada B — Correspondência com o histórico confirmado¶
_match_known_appliance compara o delta_w do novo evento contra cada evento já confirmado do medidor (não uma média por aparelho), aceitando o aparelho do evento histórico mais próximo dentro de 15% de tolerância. Funciona desde a primeira confirmação — ao contrário da árvore de decisão, que exige 6+ amostras.
Camada C — Assinaturas fixas¶
Faixas de potência típicas e conhecidas (KNOWN_SIGNATURES): Chuveiro Elétrico (3000-8000 W), Ar-Condicionado (800-4000 W), Forno Elétrico (1000-3500 W), Geladeira (100-400 W). Só retorna um palpite quando o delta_w cai em exatamente uma faixa — deltas ambíguos (ex.: 1500 W, que cabe tanto em Ar-Condicionado quanto em Forno Elétrico) retornam None deliberadamente, em vez de arriscar um palpite confiante e possivelmente errado.
3. Metodologia de avaliação de acurácia¶
Comando: python manage.py evaluate_nilm [--meter ID] [--test-ratio 0.3] [--min-events 6]
Para cada medidor com pelo menos --min-events eventos confirmados:
- Os eventos confirmados são ordenados cronologicamente e divididos em treino (mais antigos) e teste (mais recentes), na proporção
1 - test_ratio/test_ratio(padrão 70%/30%). - Para cada evento do conjunto de teste, a pipeline de 3 camadas é executada usando apenas o conjunto de treino como histórico disponível (
_classify_event(..., history=train)) — o rótulo verdadeiro do evento de teste nunca é usado para treinar ou como referência de correspondência, evitando vazamento. - Reporta acurácia geral e, por aparelho, precisão/recall/F1/suporte (
sklearn.metrics.precision_recall_fscore_support).
$ python manage.py evaluate_nilm
Casa Principal — 14 evento(s) de treino / 6 de teste
Acurácia geral: 83.3% (6 eventos de teste)
Aparelho Precisão Recall F1 Suporte
Chuveiro Elétrico 100.0% 100.0% 100.0% 3
Geladeira 66.7% 66.7% 66.7% 3
=== Geral (todos os medidores avaliados) ===
Acurácia geral: 83.3% (6 eventos de teste)
Nota de honestidade sobre os limites da PoC: a saída acima é ilustrativa (dados sintéticos de teste automatizado), não uma métrica de produção — o EnergIA hoje tem uma única residência instrumentada, e a Camada A (árvore de decisão) só entra em ação a partir de 6 eventos confirmados por medidor cobrindo 2+ aparelhos. Com poucos rótulos locais, a avaliação real tende a se apoiar mais nas Camadas B/C. Rode o comando periodicamente à medida que mais eventos forem confirmados para acompanhar a acurácia real evoluir; números fabricados não substituem essa medição contínua.
Limitação conhecida do split cronológico¶
Se o usuário confirmar todos os eventos de um aparelho em um período e de outro aparelho só depois, o split 70/30 pode concentrar um aparelho inteiro no treino e outro no teste, produzindo um relatório de acurácia sobre um subconjunto de aparelhos apenas naquela rodada. Isso é esperado e documentado — não é um bug — mas reforça a recomendação de rodar o comando regularmente conforme novos eventos forem confirmados.
4. Recomendação de horário de uso (Sprint 17 do PRD)¶
A partir da tarifa branca (ver docs/hardware.md... não — ver seção de tarifas do PRD, Sprint 16) e dos aparelhos já confirmados pelo NILM, analytics/services.py::suggest_best_usage_window(appliance, tariff) calcula:
- O posto horário mais barato (
recommended_slot) e o mais caro (worst_slot) da tarifa branca ativa. - O consumo estimado por uso (
appliance.typical_power / 1000 × 1 hora, aproximação documentada — ciclos reais de chuveiro/máquina de lavar costumam ficar próximos disso). - A economia estimada em R$:
custo no posto mais caro − custo no posto recomendado.
get_usage_suggestions(meter, tariff) aplica isso a todos os aparelhos conhecidos do medidor, ordenado pela maior economia — é o que alimenta o card "Recomendação de horário de uso" na tela de Desagregação (web) e a seção equivalente do contexto do assistente (WhatsApp/chat), permitindo respostas como "qual o melhor horário para usar o chuveiro?" a partir dos mesmos dados, sem um intent matcher dedicado — o LLM já responde consultando o contexto textual, no mesmo padrão usado para tarifas e faturas.
Como reforço proativo (opcional, também implementado): quando um degrau de alta potência (≥ 1000 W) é detectado durante o posto de ponta de uma tarifa branca ativa, _maybe_alert_high_power_during_peak envia um alerta via WhatsApp sugerindo o deslocamento para o posto mais barato. Este e os demais alertas proativos (anomalia, limite configurado pelo usuário, previsão de fatura alta) estão documentados em docs/alertas.md — incluindo o card "Último alerta" no dashboard e o modo demonstração.
5. Diagrama de arquitetura (ponta a ponta)¶
flowchart LR
subgraph Edge [Camada Edge — C++ / firmware]
ESP[ESP32 + CT clamp + ZMPT101B]
end
subgraph Broker [Comunicação]
HMQ[(HiveMQ Cloud — MQTTS 8883)]
end
subgraph Processamento [Processamento — Django]
SUB[Subscriber MQTT]
TEL[(TelemetryReading — TimescaleDB)]
NILM[detect_power_steps + classificação 3 camadas]
EVT[(PowerStepEvent / Appliance / LoadDisaggregation)]
ANOM[detect_consumption_anomaly]
TAR[Tarifa branca: get_period_for_datetime]
REC[suggest_best_usage_window]
end
subgraph Aplicacao [Aplicação]
WEB[Dashboard web / app Flet]
BOT[Assistente WhatsApp / chat — RAG]
end
ESP -- MQTT/TLS --> HMQ
HMQ --> SUB
SUB --> TEL
TEL --> NILM
NILM --> EVT
TEL --> ANOM
EVT --> REC
TAR --> REC
ANOM -- alerta proativo --> BOT
REC -- alerta proativo --> BOT
ANOM -- card Ultimo alerta --> WEB
EVT --> WEB
REC --> WEB
TEL --> WEB
WEB --> BOT
A camada Edge (aquisição/firmware) é responsabilidade de uma frente de hardware separada (ver
docs/hardware.md) — este repositório Django consome os dados já processados (Vrms, Irms, potência, kWh) publicados via MQTT.
6. Referências¶
analytics/services.py— implementação completa (detecção, classificação, recomendação, anomalia).analytics/management/commands/evaluate_nilm.py— comando de avaliação.EnergIA_vs_Desafio6_Analise.docx(pastaarquivos/, não versionada) — mapeamento contra os requisitos do Desafio 6.PRD.md, seção 13-A (Sprints 16-21) — plano de adequação ao desafio.