
A linha de base ingênua no meu detector de quedas por radar atingiu o mesmo recall de 1,0. Ela também disparou sete alarmes falsos em uma única noite.
No plantão noturno sintético que criei para o Vigil, a linha de base comercial dispara nove alertas entre 02:00 e 06:00, e sete deles estão errados. Um ventilador de teto, capturado a uma velocidade de pico de 5,0 m/s. Um cão de terapia, com seção reta radar de 0,27. Um residente sentando com força em um assento a 2,92 m/s. Dois desses nove alertas são quedas reais, e uma delas ocorre em um banheiro: um traço de centroide que vai de 1,53 m em pé, descendo por 1,07, 0,84, 0,625 e 0,344 antes de se estabilizar em 0,119 m, nível do chão, respiração presente, sem recuperação.
Cada evento nesse plantão é sintético, rotulado e fundamentado na física, gerado a partir de uma semente fixa, e fui eu quem programou ambos os detectores. É por isso que posso dizer a parte desconfortável com clareza. A linha de base também pegou essa queda no banheiro.
O Vigil é a camada de inteligência que construí para atuar entre um fluxo de recursos de radar e um sistema de chamada de enfermagem em residenciais para idosos. Ele retorna ALERT, SUPPRESS ou ROUTE TO HUMAN, com uma justificativa anexada a cada decisão que a instituição pode registrar. A rota da demonstração é https://veriprajna.com/demos/smart-facility-fall-detection. Comecei o desenvolvimento assumindo que a parte difícil era enxergar a queda. O benchmark discordou de mim logo na primeira execução.
O recall era o número com o qual eu queria abrir
Executei o benchmark esperando que a sensibilidade a quedas fosse o destaque, e é um bom número: recall de 1,0 para a cascata em um conjunto fixo de 360 eventos sintéticos ruidosos e rotulados. A coluna ao lado é o que mudou o artigo que achei que estava escrevendo. A linha de base ingênua consiste em duas cláusulas aritméticas, qualquer movimento rápido ou baixo é uma queda (peak_v > 2.0 OR min_cz < 0.45), e no mesmo conjunto ela também atinge recall de 1,0. A sensibilidade é onde vive o marketing de detecção de quedas, e ambos os detectores estão cravados no topo dela.
A separação reside inteiramente na linha que ninguém coloca em um slide. A especificidade para fatores de confusão é de 1,0 para a cascata e de 0,167 para a linha de base, uma taxa de alarmes falsos de 0,833 por evento benigno. Projete isso para os 30 disparos de movimento benigno por quarto por dia que o benchmark assume, e a linha de base chega a 25,0 alarmes falsos por quarto por dia. A faixa publicada para sensores comerciais concorrentes é de 5 a 15 alarmes falsos por quarto por dia, e a fadiga de alarmes, e não a sensibilidade do sensor, é documentada como o principal motivo pelo qual essas implantações falham.
Devo dizer isto antes que um leitor técnico diga por mim. Os pesos de fusão em data/fall_model.json foram ajustados por tools/fit_fall_classifier.py nos próprios geradores de cenários da demonstração, os mesmos geradores que produzem o conjunto de 360 eventos. Essa é a objeção mais forte que qualquer um pode levantar contra os meus dois resultados de 1,0, e é também por isso que me importo mais com o 0,167 do que com qualquer um deles. A falha da linha de base não é um artefato da minha configuração de treinamento. É o que um limiar faz quando o mundo contém ventiladores de teto.
Os sete alarmes falsos decidem se alguém ainda estará prestando atenção quando o verdadeiro chegar.
Os fatores de confusão foram criados para derrotar uma única característica
Meu primeiro instinto foi melhorar o classificador, e foi o instinto errado. Passei o início do desenvolvimento tratando isso como um problema de discriminação: encontrar a característica que separa uma queda de uma não queda, dar-lhe grande peso e seguir em frente. Os geradores de cenários que eu já havia escrito tornaram isso impossível de propósito.
Cada fator de confusão é gerado para se sobrepor a uma queda real em alguma característica individual. O ato de sentar com força na Cam 5 apresenta um pico de velocidade de 2,92 m/s, a magnitude de uma queda, e se estabiliza em 0,46 m. Seu primo do banheiro na Cam 10 atinge um pico de 3,31 m/s e se estabiliza em 0,44 m. O cão de terapia e o ato de se abaixar para pegar uma toalha fazem ambos o centroide descer, que é a outra metade da regra da linha de base. Qualquer teste isolado que eu pudesse escrever era derrotado por projeto, razão pela qual a linha de base é genuinamente enganada com 0,167, em vez de ser enganada por um espantalho que configurei para perder.
A velocidade era a característica sobre a qual eu tinha mais certeza, e foi a que não sobreviveu no classificador. O que sobreviveu é um modelo logístico sobre quatro características: proximidade do chão, energia de impacto, queda de descida e um proxy de seção reta radar, fundidos em um P(fall) calibrado. Nenhum termo de velocidade atinge o P(fall). Ainda há uma linha desatualizada na docstring do módulo de quando achei que atingiria. É numpy puro, pequeno o bastante para que qualquer pessoa possa abrir classifier.py e manter a coisa toda na cabeça, o que, em um fluxo de segurança da vida humana, vale mais para mim do que mais um ponto de AUC.
Coloquei a supressão da Cam 10 na frente das pessoas primeiro, porque o painel resume toda a discordância em uma única linha.

Quatro condições, uma janela de 8 segundos
Escrevi o verificador narrativo temporal como a parte que eu gostaria de ler se fosse alguém de fora. temporal.py exige quatro condições dentro da mesma janela de 8 segundos, com a posição em pé estabelecida no seu quinto inicial: um centroide mediano acima de 1,2 m, uma descida superior a 0,6 m junto com uma velocidade de pico acima de 1,8 m/s em algum ponto da janela, um impacto de banda larga sustentado cuja média móvel de 3 quadros excede 0,50, e o centroide atingindo efetivamente abaixo de 0,30 m. O teste de impacto sustentado existe porque um pico em um único quadro custa pouco, mas um corpo atingindo o chão não.
A string de motivo emitida pelo aplicativo diz "standing → descent → impact → floor", que é como uma enfermeira lê um incidente, mas a implementação aplica a operação lógica AND a essas condições ao longo da janela em vez de impor uma ordem. Não é uma máquina de estados, e prefiro escrever isso eu mesmo do que ver um engenheiro encontrar isso no código-fonte e se perguntar o que mais o texto promocional simplificou.
Apenas então o gate adiciona a confirmação de respiração acima de 0,20 e uma confiança de queda de pelo menos 0,70. Esses três valores — nível do chão em 0,30 m, respiração em 0,20 e o piso de confiança de 0,70 — vivem em código simples, fora de qualquer modelo. A direção é o que importa: as condições determinísticas precisam se sustentar antes que a pontuação do modelo seja sequer consultada, de modo que um número de confiança nunca possa fabricar um alerta por conta própria. Um P(fall) menor ainda pode transformar um ALERT em um SUPPRESS, o que representa a assimetria correta para uma camada autorizada a permanecer em silêncio, mas não autorizada a inventar. Mais um limiar determinante reside fora desse bloco documentado, um valor codificado diretamente no código p_fall >= 0.40 em gate.py que pode enviar um evento com múltiplos ocupantes para verificação humana apenas com base na pontuação do modelo. Menciono isso porque, caso contrário, "três limiares documentados" estaria prometendo mais do que cumpre.
As supressões são o registro que uma auditoria regulatória de fato exige
Construí o Decision Ledger antes de construir qualquer coisa que se parecesse com um produto, porque a pergunta que eu não conseguia responder nunca foi "vocês pegaram a queda?". Era "por que nenhum alerta foi disparado no Quarto 203 às 2:13?", e a resposta já precisa estar em um registro formal quando alguém perguntar. Dez dos doze eventos do plantão são supressões, e cada um traz o valor da característica registrado em seu log: o alvo não humano da Cam 6 com seção reta radar de 0,27 frente ao mínimo humano de 0,55; os atos de abaixar da Cam 7 e da Cam 12, onde o centroide para em 0,60 m e 0,59 m, com energia de impacto de 0,07 frente a um limiar de 0,50.

Essas dez linhas suprimidas são exatamente o que um auditor regulatório questiona, porque representam os eventos em que nada aconteceu e alguém ainda tem que explicar o motivo.
Apenas parte da calibração por quarto está de fato conectada a uma decisão. O ventilador de teto da Cam 1 é suprimido porque o mapa de clutter do Quarto 214 contém uma entrada Doppler de localização fixa em (1.5, 1.5, 2.45 m) e check_clutter mascara o sinal naquele voxel. Esse fluxo é real. As alturas de assento e de cama por quarto em rooms.json, 0,42 m no Banheiro do Quarto 118 e 0,45 m no Quarto 203, junto com as entradas de barras de apoio ao lado delas, são dados de calibração que nenhum fluxo de código da V1 lê; a faixa de assento que utilizo para rotular o ato de sentar com força é um único teste global de 0,38 a 0,60 m. Há inclusive uma chave long_lie_sec: 180.0 naquele arquivo que nada consome. A calibração por quarto é o trabalho de integração pelo qual uma implantação real paga, e, nesta versão, apenas as máscaras Doppler estão conectadas a uma decisão.
A exportação é um JSON de auditoria de plantão cobrindo cada alerta, direcionamento e supressão com seus valores de características decisivas e o motivo da política, que é exatamente o que uma pasta de CMS F689 ou QAPI exige. A anotação clínica do incidente é redigida separadamente a partir dessa evidência estruturada e exibida no painel de incidentes, não dentro do JSON.
O alerta que deixei o sistema disparar e a queda que não permiti que ele afirmasse
Coloquei o ápice do plantão deliberadamente em um banheiro. É o cômodo de maior risco e o único local onde uma câmera não é uma opção viável: dezenove estados norte-americanos promulgaram leis regulamentando câmeras em quartos de instituições de longa permanência para idosos, geralmente permitindo-as no quarto do residente mediante consentimento, enquanto banheiros continuam excluídos na prática por motivos de privacidade. Dados de radar não contêm imagem, o que é exatamente o motivo pelo qual eles podem ir onde uma câmera não pode.
A Cam 3 é esse evento, e o Vigil retorna ALERT, categoria long_lie, confiança de 0,99, tempo no chão de 4,8 s, com a escala de acionamento armada no técnico de enfermagem (CNA) de imediato, no enfermeiro de plantão aos 90 s e no diretor de enfermagem (DON) aos 180 s. O texto do crachá de chamada de enfermagem diz "Room 118B Bathroom: Fall Detected, 99% confidence. Resident on floor 5s. Breathing confirmed." O despacho emite tanto um sinal de contato seco Rauland legado quanto uma carga de dados MQTT/REST Ascom/Austco, e ambos passam por um stub de adaptador registrado em log. Nenhum hardware de chamada de enfermagem está conectado a nada disso. A razão pela qual ainda assim vale a pena construir isso é a permanência prolongada no chão: metade dos idosos que permanecem no chão por mais de uma hora morre dentro de seis meses.

Um número naquele painel que me recuso a vender. Os 7,0 segundos do impacto ao alerta são calculados em gate.py como o temporizador de retenção mais três segundos, uma constante. O aplicativo exibe isso, e citarei a exibição, mas trata-se de aritmética em vez de uma velocidade de sistema medida, e chamar isso de latência de benchmark seria o tipo de pequena inverdade que custa as grandes verdades que estão ao lado dela.
O evento do qual tenho mais orgulho é aquele que o Vigil recusa. A Cam 2 é uma queda real na verdade de campo e o P(fall) atinge 0,99, e o Vigil ainda assim não a afirma: o quarto contém dois alvos, o rastreamento de pessoa única está fora do escopo da V1, e o gate retorna ROUTE TO HUMAN at low confidence. A linha de base ingênua dispara automaticamente e leva o crédito por uma detecção que não mereceu. Duas quedas reais ocorreram naquele plantão. O Vigil alertou sobre uma e enviou a outra para checagem da equipe, e não descreverei isso como pegar todas as quedas, porque não é. Em todo o benchmark, 40 de 40 quedas com múltiplos ocupantes são direcionadas para um humano, sem alertas excessivos e sem nenhuma perda.
O que o placar tem o direito de reivindicar
A linha de ressalva sob a janela modal de Resultados do Plantão foi a primeira parte daquele painel que redigi. A janela modal relata 0 alarmes falsos para o motor contra 7 da tecnologia existente neste plantão, 1 de 2 quedas reais detectadas, 1 direcionada a um humano, 100% de especificidade para fatores de confusão em 360 eventos rotulados, e 0,0 contra 25 alarmes falsos projetados por quarto por dia.

Esses números descrevem um conjunto de referência sintético fixo e nada mais. Não representam acurácia em produção, nem um resultado clínico, nem uma alegação médica validada, e jamais uma garantia para uma instituição. Um piloto real tem como meta menos de 2 alarmes falsos por quarto por dia após a calibração em modo sombra, e esse é o número que eu apresentaria a um Diretor de Enfermagem, porque é aquele pelo qual eu poderia ser cobrado. O 0,0 é uma evidência de que o mecanismo separa quedas de fatores de confusão em um conjunto que posso lhe entregar; não é uma promessa sobre um edifício pelo qual nunca caminhei.
O padrão que agora exijo de um alerta de segurança da vida
Saí desse projeto com uma definição muito mais restrita sobre no que a detecção de quedas precisa ser boa. A detecção é um limiar, e um limiar já atinge recall de 1,0 no meu próprio conjunto de testes. O trabalho que conquista a atenção de um enfermeiro é a recusa: o mapa de clutter que sabe qual voxel o ventilador ocupa, o teste de impacto que não aceita um único quadro, a condição de alcance do chão que separa o ato de sentar com força de uma queda e um gate escrito de modo que um auditor regulatório, e não um modelo, consiga entender por que o sistema fez o que fez.
O passo a passo completo está disponível em https://veriprajna.com/demos/smart-facility-fall-detection, e as dez linhas suprimidas no registro são onde o plantão é de fato decidido.
E se você prefere assistir ao plantão em vez de me ler descrevendo-o, aqui está a noite inteira sendo executada de ponta a ponta.
Um sistema que dispara alarmes para o ventilador de teto é silenciado em menos de uma semana, e um sistema silenciado não detecta absolutamente nada. O comportamento mais sofisticado que pude dar a essa camada foi a capacidade de recusar, com registro formal e com o número decisivo anexado. Na Cam 2, esse número foi de 0,99, e a decisão correta ainda foi repassar o evento para uma pessoa.


