Esta ontologia formaliza fenômenos de saúde administrados pelo Sistema Único de Saúde (SUS) brasileiro — internação hospitalar (SIH/AIH), rede física de estabelecimentos (CNES), produção ambulatorial (SIA), agravos de notificação compulsória (SINAN), mortalidade (SIM) e nascimento (SINASC) — sobre um meta-modelo de nível superior no estilo BFO: tudo que existe é um Continuant (algo que persiste através do tempo) ou um Occurrent (algo que acontece e tem partes temporais), nunca os dois. Sobre essa base, um pipeline de observação explícito (Measurement→Interpretation→Assertion) trata todo dado como resultado de um ato de medir e interpretar, nunca um fato bruto, com proveniência obrigatória alinhada a PROV-O.
O núcleo foi testado contra os seis sistemas administrativos originais do SUS — com cobertura completa dos grupos de arquivo declarados pelo PySUS em cinco deles e 48 de 49 bases de agravo no sexto — mais três sistemas fora do desenho administrativo original. Formalizado integralmente em OWL 2 DL + SHACL — nenhum dos 28 axiomas depende de SWRL. Verificado por reasoner de lógica de descrição (HermiT) e por consulta agregada direta (Oxigraph) em escala real: a checagem de consistência completa de ~90 mil indivíduos e ~700 mil triplas RDF termina em 60-61 segundos. Comparado formalmente contra quatro referências externas: BFO, SNOMED CT, HL7 FHIR e o OMOP Common Data Model.
Motivação e escopo
Terminologias clínicas estabelecidas (SNOMED CT) e padrões de interoperabilidade (HL7 FHIR) não cobrem bem — por desenho, não por deficiência de execução — a camada especificamente administrativa do SUS. Quatro problemas concretos motivaram este projeto:
Fragmentação administrativa — um único evento de saúde real (uma internação, por exemplo) frequentemente aparece em múltiplos registros de faturamento diferentes, ligados entre si só por um identificador administrativo compartilhado (como o número da AIH), não por uma referência declarada explicitamente.
Versionamento por sub-registro — um único estabelecimento tem várias famílias de dados cadastrais independentes (equipamentos, leitos, habilitações, ...), cada uma publicada em sua própria cadência: a cadência de PUBLICAÇÃO de um arquivo mensal não é a mesma coisa que a cadência de MUDANÇA real do dado.
Vínculo profissional-estabelecimento multi-dimensional — a mesma pessoa pode ter múltiplos vínculos simultâneos válidos com o mesmo estabelecimento, diferenciados por ocupação e, opcionalmente, por equipe — a identidade de um vínculo é uma chave composta, não só "pessoa + estabelecimento".
Comparação entre sistemas administrativos distintos — quando dois registros de sistemas diferentes talvez se refiram ao mesmo evento real, essa correspondência é sempre uma alegação incerta e explícita, nunca uma relação primitiva assumida entre os dois registros.
Esta ontologia formaliza exatamente essa camada administrativa, mantendo compatibilidade estrutural com o meta-modelo de nível superior BFO (ver §8.1).
Escopo confirmado, com evidência real de linha de dado
No SIH, RJ (AIH Rejeitada) usa a mesma estrutura rica de dados que RD, só com desfecho de rejeição; SP (Serviços Profissionais) fragmenta uma internação em linhas de atos profissionais individuais; ER (Rejeitada com Erro) é um registro de log de erro de processamento, estruturalmente muito mais pobre — só 13 campos contra 90-113 dos outros três. No CNES, EE (Estabelecimento de Ensino) só foi publicado até 2018 — uma primeira busca restrita a um mês recente não o encontrou, uma segunda com janela de anos mais ampla achou 51 linhas reais. No SIA, os grupos de Autorização de Procedimento de Alta Complexidade (laudos diversos, medicamentos, nefrologia, quimioterapia, radioterapia) constam na documentação oficial mas não apareceram em nenhuma combinação de estado/ano testada no catálogo de dados consultado. No SINAN, só INFL (influenza) não teve nenhuma linha de dado em dezessete anos testados (2009-2025) — ausência estrutural do código neste dataset, não uma janela estreita, plausivelmente porque a vigilância de influenza sempre foi publicada sob outro sistema. No SIM, DOR é a forma histórica de mortalidade codificada em CID-9, anterior a 1996. No SINASC, DNR é o mesmo universo de nascidos vivos republicado por UF de residência da mãe, para uso administrativo do estado receptor quando o parto ocorre em outro estado.
Fora de escopo, deliberadamente
Terminologia clínica de granularidade fina — achados clínicos individuais, procedimentos codificados no nível de detalhe que a SNOMED CT cobre. Não é uma lacuna a preencher; é uma delimitação de escopo confirmada pela própria documentação da SNOMED CT (ver §8.2).
Meta-modelo formal
2.1 · Continuant / Occurrent
A disjunção Continuant/Occurrent é declarada como owl:equivalentClass sobre a união dos dois ramos — logicamente, qualquer indivíduo da ontologia tem que cair num dos dois, nunca nos dois ao mesmo tempo. A bifurcação de Continuant em IndependentContinuant/GenericallyDependentContinuant existe para acomodar objetos puramente informacionais — uma Measurement ou uma Assertion não são coisas físicas que persistem, mas também não são eventos; são conteúdo informacional que depende de algum portador sem depender de um portador ESPECÍFICO.
As duas árvores nunca se cruzam por mereologia direta — só via documents / hasProvenance / about, garantido estruturalmente pelo domínio e alcance de hasPart/partOf.
2.2 · Pipeline de observação
Nenhum dado entra na ontologia como fato bruto. Todo valor observado é, primeiro, uma medição (Measurement); uma ou mais medições combinam-se numa interpretação (Interpretation — por exemplo, cruzar peso e idade gestacional); uma ou mais interpretações sustentam uma alegação final (Assertion — por exemplo, "a causa básica do óbito foi X"). Toda Feature tem que derivar de uma dessas três, nunca diretamente de um Continuant/Occurrent. Uma Assertion pode ser revisada no máximo uma vez (revises, propriedade funcional): quando isso acontece, as duas instâncias permanecem na base — a anterior é marcada como superada, nunca apagada. Modela, por exemplo, o padrão real do atestado de óbito brasileiro: a causa básica original preenchida na hora, depois revisada após investigação, com as duas versões preservadas.
2.3 · Proveniência
Provenance (que inclui Evidence como subtipo) é alinhada ao vocabulário PROV-O do W3C via rdfs:seeAlso, sem importar o vocabulário inteiro. Todo Occurrent tem que ter pelo menos uma Provenance — nenhum evento ou processo existe na base sem um documento ou atividade de origem que o comprove. Consequência direta da própria natureza administrativa do domínio: o SUS não registra eventos de saúde diretamente, registra DOCUMENTOS sobre eventos de saúde — a proveniência não é um metadado opcional, é constitutiva do próprio dado.
2.4 · Módulos temporal e espacial
O módulo temporal alinha-se livremente a OWL-Time e acrescenta cinco conceitos específicos do domínio: Snapshot (uma publicação periódica) versus Version (uma mudança real de estado — contar snapshots como versões superestima a taxa real de mudança); Validity (um intervalo delimitado, ancorado a um instrumento normativo que o concedeu — o padrão real por trás de habilitações, metas, incentivos, regras contratuais, status filantrópico e status de estabelecimento de ensino no CNES, seis confirmações independentes); Competence (um período de referência administrativa); EpidemiologicalWeek (o calendário epidemiológico OMS/CDC, que não coincide com a semana civil). O módulo espacial alinha-se livremente a GeoSPARQL, acrescentando Region (subdivisão territorial administrativa) e CatchmentArea (área de abrangência de um serviço, que não coincide necessariamente com uma fronteira administrativa).
Estrutura modular
O modelo é dividido em onze arquivos, cada um com uma responsabilidade clara.
| Arquivo | Conteúdo | Depende de |
|---|---|---|
owl/core.ttl | O meta-modelo inteiro (§2), a camada populacional (Family/Household/FamilyMembership, com a regra de responsável familiar único em OWL DL), indicadores/inferência, representação, conhecimento (protocolos, esquemas de classificação), dinâmica, LinkageAssertion. 15 dos 28 axiomas codificados aqui. | — |
owl/sus_extensions.ttl | Classes específicas dos sistemas SUS: Hospitalization/AIHRecord, HealthFacility/Affiliation/Team, AggregatedEncounterRecord, NotifiableCaseInvestigation/CaseOutcome/EntryType, Involvement. | core.ttl |
owl/shapes.ttl | Constraints SHACL (validação sob mundo fechado) para os axiomas 15, 21, 22, 23 e a regra de responsável familiar único — a metade de cada um que exige rejeitar dado FALTANTE, algo que OWL DL puro não consegue fazer estruturalmente. | — |
owl/batch_check.py | Verificação em escala real via consulta SPARQL agregada direta (Oxigraph) dos axiomas 15, 19, 21, 22, 23 — sempre com teste sintético antes de rodar contra dado real. | core.ttl, sus_extensions.ttl |
owl/test_axioms_22_23.py | Teste sintético das duas metades (DL via HermiT; SHACL/agregada via Oxigraph) dos axiomas 22 e 23. | core.ttl, sus_extensions.ttl |
owl/test_family_membership.py | Teste sintético + dado real completo do CadÚnico (4.807.996 famílias) da regra de responsável familiar único. | core.ttl, sus_extensions.ttl |
owl/pysus_to_rdf.py | Biblioteca de mapeamento reutilizável — generaliza dez mapeamentos já validados contra dado real (internação, vínculo profissional, qualificação de estabelecimento, nascimento, óbito, dimensão de equipe, agravo de notificação individual, atividade agregada de agravo, participação em violência, vínculo familiar) numa API única (to_rdf(mapeador, **parâmetros)), com URIs determinísticas por chave natural e uma CLI. Ver §10. | core.ttl, sus_extensions.ttl |
owl/store.py | Armazenamento persistente em disco (Oxigraph), populado incrementalmente a partir de pysus_to_rdf.py, com manifest de cargas já feitas. Ver §11. | core.ttl, sus_extensions.ttl, pysus_to_rdf.py |
owl/sparql_endpoint.py | Endpoint SPARQL 1.1 Protocol sobre o armazenamento persistente, só leitura, com a biblioteca padrão do Python. Ver §12. | store.py |
owl/quality_monitor.py | Monitor de qualidade de dado contínuo — reaproveita as consultas de axioma já validadas (não copia), roda contra o estado atual do armazenamento persistente, classifica cada checagem e grava relatório com timestamp. Ver §13. | batch_check.py, store.py |
owl/abox_sample.ttl | ABox de referência pequeno (300 Hospitalization + 192 Affiliation, dado real, 93 pessoas reais distintas) para checagem interativa rápida. | ambos TBox |
Catálogo de classes
4.1 · Núcleo (core.ttl)
| Classe | Superclasse | Papel |
|---|---|---|
Continuant, Occurrent | — | topo, disjuntos e exaustivos |
IndependentContinuant, GenericallyDependentContinuant | Continuant | disjuntos entre si |
Person, Organization, Place, Role, Artifact | IndependentContinuant | mutuamente disjuntos |
Document, Observation, Measurement, Interpretation, Assertion, Provenance | GenericallyDependentContinuant | pipeline de observação (§2.2) |
Evidence | Provenance | — |
Process, Event | Occurrent | disjuntos entre si |
Pregnancy, PrenatalCare, Labor, Delivery, Puerperium | Process | exemplo canônico (gestação/parto, SINASC/SIM) |
PregnancyOutcome | Occurrent | equivalente à união disjunta de Birth/FetalDeath |
Birth, FetalDeath | Event, PregnancyOutcome | disjuntos entre si |
Newborn | Person | o recém-nascido, pessoa desde o nascimento |
EncounterRecord, IndividualEncounter, AggregatedEncounterRecord | GenericallyDependentContinuant | distinção registro individual vs. agregado |
AggregateActivity | Process | o processo real por trás de um registro agregado — nunca decomponível de volta em instâncias individuais |
Population, Territory, Cohort, Family, Household | IndependentContinuant/Place/Population | Family≠Household: um domicílio pode ter mais de uma família cadastrada |
FamilyRole (ResponsavelFamiliar, Conjuge, FilhoDependente, OutroParenteDependente, NaoParenteDependente) | Role | os cinco papéis possíveis numa família cadastrada |
FamilyMembership | GenericallyDependentContinuant | reificação Person↔Family↔Role — o papel em si precisa ser registrado |
Indicator | Measurement | indicador epidemiológico/de gestão |
Inference (Statistical, MachineLearning, Causal, Mechanistic, Simulation) | Occurrent | cinco paradigmas de inferência sob incerteza |
Feature, Representation (FeatureSet, Graph, TimeSeries, Embedding), DerivedVariable | GenericallyDependentContinuant | camada de representação para análise/aprendizado de máquina |
Knowledge (Guideline, Protocol, EvidenceSynthesis, ClinicalRecommendation, Policy, Mechanism) | GenericallyDependentContinuant | conhecimento formal como entidade de primeira classe, não regra externa |
ClassificationScheme | Knowledge | um esquema de codificação (ex.: CID-10), com supersedes (funcional) e crosswalk (sem limite de cardinalidade — a correspondência entre esquemas raramente é 1:1) |
State, Transition, Trajectory | GenericallyDependentContinuant/Event | módulo de dinâmica — sequência ordenada de estados que um sujeito atravessa |
LinkageAssertion | Assertion | a alegação, incerta e explícita, de que dois registros de sistemas diferentes se referem ao mesmo evento |
Affiliation | GenericallyDependentContinuant | forma abstrata do vínculo profissional; chave concreta em sus_extensions.ttl (axioma 19) |
TimePoint, TimeInterval, Snapshot, Version, Validity, Competence, ReferenceDate, ObservationDate, EpidemiologicalWeek | GenericallyDependentContinuant | módulo temporal (§2.4) |
SpatialObject, Point, Polygon, Region, CatchmentArea | GenericallyDependentContinuant/Place | módulo espacial (§2.4) |
4.2 · Extensões SUS (sus_extensions.ttl)
| Classe | Superclasse | Papel |
|---|---|---|
Hospitalization | Process | uma internação hospitalar real (SIH) |
Admission, Discharge | Event | os dois eventos ordenados de uma internação (axioma 21) |
AIHRecordStatus (Approved, Rejected, RejectedWithError) | — | mutuamente disjuntos |
AIHRecord | Document | o documento de faturamento que documenta uma Hospitalization; tem exatamente um status |
HealthFacility | Place | operado por ≥1 Person|Organization — inclui estabelecimentos de pessoa física |
Team | Organization | quarta dimensão opcional da chave de Affiliation |
NotifiableCaseInvestigation | Process | investigação de agravo de notificação compulsória (SINAN) |
SymptomOnset, Notification, Closure | Event | eventos ordenados de uma investigação |
Investigation | Process | o processo de investigação epidemiológica |
CaseOutcome (Cure, DeathByThisCause, DeathByOtherCause, CaseTransfer, Abandonment) | Occurrent | núcleo estável entre agravos, mas não um enum fechado (axioma 24) |
EntryType (NewCase, Relapse, ReentryAfterAbandonment, UnknownEntry, TransferEntry, PostMortem) | — | tipo de entrada, exatamente um por investigação |
Involvement | GenericallyDependentContinuant | participação de uma pessoa num evento, papel escopado à instância — genérico o suficiente para vítima/agressor/testemunha OU profissional executando um ato |
InvolvementRole (VictimRole, PerpetratorRole, WitnessRole) | — | — |
linkageMethod (DeterministicLinkage, ProbabilisticLinkage) | — | método de vínculo de uma LinkageAssertion |
Catálogo de relações (seleção)
| Propriedade | Domínio | Alcance | Natureza |
|---|---|---|---|
hasPart / partOf | Occurrent | Occurrent | inversas; nunca cruzam para GDC (axioma 16) |
resultsIn | Occurrent | — | com cardinalidade específica por subclasse |
produces | Occurrent | IndependentContinuant | ex.: Birth produces Newborn |
generates / generatedBy | Occurrent | Occurrent | ex.: Delivery generates Birth — distinta de produces por necessidade de tipo (ver §7) |
hasProvenance | — | Provenance | obrigatória em todo Occurrent/Assertion |
documents | Occurrent | Observation | — |
about | GDC | — | ex.: AggregatedEncounterRecord about AggregateActivity |
revises | Assertion | Assertion | funcional (0 ou 1) |
derivedFrom | Feature | — | — |
person / facility / affiliationRole / team | Affiliation | Person/Facility/Role/Team | chave de identidade (axioma 19) |
hasStatus | AIHRecord | AIHRecordStatus | cardinalidade exatamente 1 |
atFacility | Hospitalization | HealthFacility | liga uma internação ao estabelecimento onde aconteceu, pelo mesmo código real usado pelo vínculo profissional — identidade confirmada em TRÊS sistemas administrativos, em escala nacional (ver §11) |
priorEpisode | NotifiableCaseInvestigation | idem | irreflexiva |
involvesPerson / involvesOccurrent / involvementRole | Involvement | Person/Occurrent/Role | — |
hasInvolvement | Occurrent | Involvement | owl:inverseOf involvesOccurrent |
isPrimarySubject | Involvement | booleano | funcional; ortogonal ao papel (axioma 22) |
memberPerson / memberFamily / familyRole | FamilyMembership | Person/Family/Role | — |
hasFamilyMembership | Family | FamilyMembership | inverso; base da restrição "no máximo um responsável" |
partOfHousehold | Family | Household | — |
from / to | Transition | State | ambas funcionais, cardinalidade 1 |
hasState | Trajectory | State | pelo menos 1 |
linkedOccurrentDocumentation / confidence | LinkageAssertion | GDC / decimal | — |
Axiomas — forma final, 28
Cada axioma indica seu status de formalização — como ele é efetivamente verificado, não só declarado:
Um achado central atravessa a formalização inteira: nenhum dos 28 axiomas precisa de SWRL (a linguagem de regras que pareceu necessária em alguns casos durante o desenvolvimento). A razão é uma distinção lógica repetida de forma independente em pelo menos três grupos de axiomas: toda vez que um axioma tem a forma "no máximo N", essa metade é uma restrição DL real — mesmo sob mundo aberto (a suposição padrão de OWL: o que não está declarado é desconhecido, não falso), um reasoner ainda detecta contradição genuína quando o dado afirma explicitamente mais instâncias do que o máximo permite. Mas toda vez que um axioma tem a forma "pelo menos N", essa metade NÃO é real sob mundo aberto: um reasoner sempre pode hipotetizar um indivíduo anônimo desconhecido que satisfaz o mínimo — dado real que FALTA a relação nunca aciona uma contradição. Essa metade precisa de SHACL ou consulta agregada, que trabalham sob mundo fechado. SWRL, que só deriva fatos novos sob mundo aberto e nunca rejeita dado, nunca era de fato a ferramenta certa para nenhum dos dois casos.
Composição Process / Event
Todo Birth é generatedBy exatamente um Delivery.
Todo Delivery é partOf exatamente uma Pregnancy.
Toda Pregnancy tem resultsIn ≥1 PregnancyOutcome (união disjunta Birth|FetalDeath — uma gestação tem um desfecho, nunca os dois, nunca nenhum).
Todo Birth produces exatamente um Newborn — gêmeos são múltiplos Births dentro da mesma Pregnancy, não um Birth com múltiplos Newborns.
Uma Person pode ter múltiplos Roles simultaneamente — confirma explicitamente que nenhum limite é imposto.
Todo Occurrent tem hasProvenance ≥1 Provenance (§2.3).
Indicator / Feature
Todo Indicator tem appliesTo exatamente um de {Population, Territory, Cohort} — um indicador se aplica a um agregado, nunca a um indivíduo.
Nenhum Indicator tem appliesTo uma Person diretamente — a contraparte do axioma 7: um valor sobre uma única pessoa é uma Measurement, não um Indicator.
Toda Feature derivedFrom ≥1 de {Measurement, Interpretation, Assertion}.
Toda Assertion tem hasProvenance própria e rastreável — mesmo que Measurement/Interpretation possam herdar a proveniência do evento que as contém.
Dynamics
Toda Transition conecta exatamente um State de origem (from) a exatamente um de destino (to).
Toda Trajectory tem hasState ≥1 State.
Todo Mechanism relaciona TIPOS, nunca instâncias — uma instância pode ser consistente com um mecanismo, nunca literalmente É um mecanismo. Não expressável em OWL 2 DL padrão sem "punning" (tratar uma classe também como indivíduo).
Revisão de Assertion
Assertion.revises → Assertion é 0 ou 1; quando presente, as duas instâncias permanecem — a revisada marcada superseded, nunca removida (§2.2).
Se Assertion.revises existe, as duas Assertions têm que ter hasProvenance DISTINTAS — senão a revisão não tem conteúdo real, seria a mesma fonte "revisando" a si mesma.
GDC nunca tem hasPart/partOf com IndependentContinuant ou Occurrent — garantido estruturalmente pelo domínio/alcance dessas propriedades, não precisa de restrição adicional.
Fragmentação administrativa e versionamento
Múltiplos registros administrativos do mesmo tipo documentam o MESMO Occurrent sse compartilham identificador administrativo OU são administrativamente contíguos; caso contrário, cada grupo documenta um Occurrent DISTINTO, ligado ao anterior só via Trajectory. Confirmado numa granularidade fina: dentro do SIH, o grupo SP (Serviços Profissionais) fragmenta uma internação em linhas de atos profissionais individuais, ligadas pelo mesmo número de AIH.
Dois Snapshots consecutivos do mesmo Continuant documentam a mesma Version sse o campo de última alteração real é idêntico — cadência de publicação e cadência de mudança real são independentes.
Affiliation é identificada pela TRIPLA (person, facility, affiliationRole), estendida opcionalmente por team; duas instâncias com a mesma chave são violação de unicidade, não fundidas por sameAs. Originalmente owl:hasKey — semanticamente mais correto, mas caro demais para terminar em escala real (~90 mil indivíduos). Trocado por consulta agregada: a checagem de consistência do modelo inteiro passou de não terminar em 15 minutos para terminar em 61 segundos.
O axioma 17 só se aplica quando o Occurrent de destino é individualmente resolúvel; AggregateActivity nunca satisfaz essa condição. A granularidade agregado/individual é propriedade de cada FICHA, não do sistema: dentro do SINAN, duas bases (notificação de surto e tracoma) têm campos de contagem, não atributos de paciente — são AggregateActivity, não NotifiableCaseInvestigation.
Para todo Process cujo hasPart seja sequência ordenada, o TimePoint de cada parte deve ser ≥ o da parte anterior. Verificado contra 35.636 internações reais: zero violações.
Um Occurrent pode ter múltiplos participantes via Involvement. No máximo um pode ter isPrimarySubject=true (real sob mundo aberto — contradição genuína se dois estiverem marcados). Pelo menos um deve ter quando há mais de um participante (SHACL — ausência não é, por si, contradição sob mundo aberto).
priorEpisode, quando presente, aponta para um Occurrent distinto (nunca a mesma instância — irreflexiva) do mesmo sujeito. Proibido quando caso novo (inclusão de classe geral, real sob mundo aberto). Obrigatório quando recidiva/reingresso (SHACL, mesmo motivo do axioma 22).
Disciplina de generalização
Regras de coerência específicas de um agravo/sistema particular são regras de Knowledge.Protocol, nunca axioma universal fixo — mesmo quando descobertas testando um único caso. O conjunto de desfechos válidos de CaseOutcome é, pela mesma razão, parametrizado por Protocol: em 35 das 44 bases reais do SINAN testadas, o núcleo do enum de desfecho é quase universal, mas muitas bases usam códigos adicionais específicos daquele agravo.
Confiabilidade de registro e escopo de versionamento
AIHRecord tem hasStatus exatamente 1 {Approved, Rejected, RejectedWithError}; só Approved sustenta documentedBy→Hospitalization com confiança plena — agregações de faturamento devem excluir os demais por padrão.
A identidade de Version (axioma 18) é escopada por SUB-REGISTRO, não pelo Continuant inteiro — cada família de sub-registro de um estabelecimento avança independentemente, sem presença garantida em toda instância.
Revisão multi-canal e linkage entre sistemas
Assertion.revises não pressupõe origem única — qualquer canal com Provenance própria e rastreável qualifica, incluindo recodificação automática sob novo Protocol. O axioma 15 continua obrigatório para qualquer canal.
corroborates/conflictsWith entre registros de sistemas diferentes são predicados DERIVADOS, só definidos na presença de uma LinkageAssertion válida — sem ela, dois registros de sistemas diferentes simplesmente não são comparáveis. Nenhuma dessas relações é declarada como primitiva.
Status de validação
Toda verificação abaixo, quando roda contra dado real, foi antecedida por um teste sintético equivalente — para garantir que "zero violações" no dado real signifique ausência real de erro, não uma consulta mal escrita que nunca dispara. A checagem de consistência lógica completa usa o reasoner HermiT via Owlready2; as verificações em escala real usam consulta SPARQL agregada direta contra Oxigraph — escolhido depois de medir que a arquitetura padrão de validação SHACL (uma consulta Python separada por nó-alvo) não escalava além de milhares de instâncias, independente de qual triple store estivesse por trás. Compilar as checagens numa única consulta agregada com variável livre resolveu o gargalo de arquitetura.
| Verificação | Ferramenta | Escala | Resultado |
|---|---|---|---|
| Consistência lógica do esquema + dados completo | HermiT (Owlready2) | ~90.000 ind. / ~700.000 triplas (histórico — ver bug de identidade) | consistente, 60-61s |
| Axioma 15 (proveniência distinta em revisão) | Consulta agregada | dado real completo | 0 violações, <0,001s |
| Axioma 21 (ordenação admissão/alta) | Consulta agregada | 35.636 internações | 0 violações, 0,175s |
| Axioma 19 (unicidade de chave de vínculo) | Consulta agregada | 55.171 linhas | 0 chaves duplicadas, 0,036s |
| Axioma 23, metade obrigatória | Consulta agregada | 103.330 notif. tuberculose | estrutura confirmada; metade obrigatória não-testável (ver §9) |
| Axioma 22 (sujeito primário único) | Consulta agregada + HermiT | 442.680 notif. violência | 0 violações, 1,7s; consistente, 6,6s (amostra) |
| Axioma 19, dimensão equipe | Consulta agregada | 2.139 vínculos com equipe | 0 com mais de uma equipe distinta |
| Axiomas 15/21/22/23, forma sintética (SHACL) | SHACL/consulta agregada | casos construídos | detecta violação, deixa passar caso legítimo |
| Axiomas 19/22/23, forma sintética (DL) | HermiT | casos construídos | 10/10 + 1/1 corretos |
| Regra de responsável familiar único | pandas (escala completa) + HermiT | 4.807.996 famílias reais | 4.807.996/4.807.996 com exatamente 1 — 0 exceções |
| Regra de responsável familiar único, sintética | HermiT + Oxigraph | casos construídos | 4/4 corretos |
Ambos do mesmo tipo: reusar uma propriedade tipada para os dois lados de uma relação assimétrica. Birth colapsava na classe vazia (owl:Nothing — o modelo inteiro tornava-se logicamente inconsistente) porque o axioma "todo nascimento é gerado por exatamente um parto" tinha reusado, por engano, uma propriedade cujo alcance era Continuant, não Occurrent — um Delivery é um Occurrent, forçando contradição direta com a disjunção fundamental do modelo. Corrigido introduzindo generates/generatedBy (Occurrent→Occurrent), distinta de produces (Occurrent→IndependentContinuant). AggregatedEncounterRecord colapsava pelo mesmo motivo estrutural, numa relação diferente.
Nenhum dos dois bugs teria sido pego por revisão em prosa — "todo nascimento é gerado por exatamente um parto" lê-se perfeitamente bem em português. Um reasoner de lógica de descrição pega o erro porque força a checagem de tipo que a prosa não força.
Encontrado ao popular o armazenamento persistente descrito em §11: depois de carregar 652.607 linhas reais de vínculo profissional, uma consulta simples de contagem respondeu Person: 1. A função que sanitiza uma chave natural para URI trocava cada caractere não-alfanumérico por um único sublinhado — inofensivo para chaves normais, mas destrutivo para o identificador de profissional real, mascarado por proteção de privacidade com caracteres de controle de comprimento fixo: os 437.757 profissionais reais distintos deste arquivo colapsavam todos na mesma URI.
Nenhuma das checagens de consistência lógica acima acusou nada — um colapso de identidade não torna um grafo inconsistente, pode até facilitar passar por uma checagem de cardinalidade máxima. Corrigido com um escape genuinamente injetivo (cada byte não-alfanumérico vira 2 dígitos hex, largura fixa). Reverificado: mesmas conclusões em todos os axiomas, mas contagem honesta de 858.393 triplas no ABox real completo, não as ~700.000 antigas — o grafo tinha, de fato, muito menos indivíduos distintos do que o real.
Relação com ontologias e padrões estabelecidos
8.1 · BFO
AlinhamentoO meta-modelo Continuant/Occurrent/GenericallyDependentContinuant (§2.1) é um subconjunto do mesmo compromisso ontológico da Basic Formal Ontology. A comparação não é de cobertura — BFO é um meta-modelo puro, sem conteúdo de domínio por desenho — é de alinhamento, e as categorias usadas aqui são literalmente categorias BFO, não uma reinvenção paralela.
8.2 · SNOMED CT
Gap confirmadoA lacuna original que motivou este projeto — representação pobre de cadastro de estabelecimentos, regionalização, vínculo profissional — confirma-se, mas por delimitação de escopo documentada pela própria SNOMED CT: terminologia clínica é o escopo declarado, conteúdo administrativo está explicitamente fora. Não é falha de execução, é um escopo que nunca incluiu isso.
8.3 · HL7 FHIR
MistoDuas convergências genuínas: PractitionerRole (pessoa+função+organização, múltiplos papéis) converge com o axioma 19; o recurso Linkage (vínculo entre registros como entidade de primeira classe) converge com o axioma 28. Uma divergência não-pejorativa: Encounter.partOf exige declaração explícita de hierarquia; o axioma 17 deriva o agrupamento de um identificador compartilhado — premissas diferentes sobre como o dado é produzido, nenhuma superior à outra.
8.4 · OMOP CDM
Convergência parcialA comparação mais próxima do domínio — mesmo problema geral (harmonizar dado multi-fonte), mas para inferência estatística populacional, não para preservar granularidade administrativa. Três divergências (VISIT_DETAIL agrupa por decisão de ETL; PROVIDER colapsa vínculo multi-papel numa linha; CONDITION_ERA agrupa por proximidade temporal semântica, não identificador). FACT_RELATIONSHIP converge em forma com LinkageAssertion, diverge em conteúdo (sem confiança/probabilidade). Gap de escopo limpo em gestão de caso de agravo — a própria comunidade OHDSI descreve o OMOP como modelo de pesquisa observacional, não de vigilância epidemiológica.
Limitações conhecidas
Axioma 23, metade obrigatória, contra dado real: formalizada e testada sinteticamente com sucesso, mas o extrato público de tuberculose do SINAN não carrega — por desenho de proteção de privacidade de um agravo de notificação compulsória — nenhuma chave que ligue duas notificações do mesmo paciente ao longo do tempo. Não-testável além da confirmação de estrutura do campo de tipo de entrada. Bloqueio permanente, não uma questão de tempo — o único item desta lista sem solução disponível.
Axioma 22 contra dado real: testado contra 442.680 notificações reais de violência, zero violações. Ressalva de construção: o sistema não captura identidade de agressor, só flags de tipo de relação — a marcação de sujeito primário (sempre a vítima) é garantida pela própria semântica do formulário, não uma variável independente que o dado pudesse ter contradito. O valor do teste está na escala e num achado colateral honesto: o campo resumo "número de envolvidos" não bate com a contagem real de flags marcadas — são duas dimensões diferentes, uma conta pessoas, a outra descreve tipos de relação.
Esquemas de classificação:
supersedes/crosswalkconfirmados em duas formas independentes. Primeiro, com "Natureza"/"Natureza Jurídica" do CNES: em três cortes temporais reais (2006, 2015, 2022), o campo antigo estava 100% preenchido em 2006, coexistiu com o novo em 2015, e em 2022 já estava vazio enquanto o novo permanecia totalmente preenchido — confirmação datada desupersedes. No ano de coexistência, 4 de 7 códigos antigos mapeavam para múltiplos códigos novos — confirmando por quecrosswalkfoi desenhado sem limite de cardinalidade. Segundo, de forma diferente, com a causa básica de óbito do SIM: o mesmo NOME de campo é usado nas eras CID-10 (moderna) e CID-9 (histórica, até 1996) — não um campo novo substituindo um antigo, o MESMO campo reinterpretado sob esquema diferente ao longo do tempo.Multiplicidade de equipe no vínculo profissional: confirmada e regulamentada por portaria ministerial como regra de
Protocol(condicionada a compatibilidade de carga horária); testada contra 2.139 vínculos reais com equipe preenchida — zero casos de mais de uma equipe observados. Permitida pela regulamentação, mas não observada nesta amostra específica, restrita a menos de 2% da produção ambulatorial total (procedimentos de atenção primária/estratégia de saúde da família).
Conectando ao DATASUS: pysus_to_rdf.py
Todo o trabalho de validação descrito em §7 foi feito com código de teste — cada verificação em escala real tinha sua própria função de mapeamento, escrita para checar um axioma específico, com caminho de arquivo fixo, descartada depois de rodar. Não existia nenhuma camada reutilizável entre "baixar um arquivo do DATASUS" e "ter RDF conforme esta ontologia".
owl/pysus_to_rdf.py é essa camada. Generaliza dez mapeamentos já validados contra dado real (as mesmas verificações descritas em §7) numa biblioteca com uma API pública única:
from pysus_to_rdf import to_rdf turtle_text, contagem, rotulo = to_rdf("sih_hospitalization", state="GO", year=2025, month=12, group="RD") turtle_text, contagem, rotulo = to_rdf("cnes_affiliation", path="/caminho/para/PFMG2211.parquet") turtle_text, contagem, rotulo = to_rdf("cnes_validity", group="HB", state="GO", year=2025, month=12) turtle_text, contagem, rotulo = to_rdf("sim_death", state="SP", year=2022)
Os dez mapeadores disponíveis: sih_hospitalization (internação — Hospitalization/Admission/Discharge/AIHRecord, com o status mapeado automaticamente conforme o grupo de arquivo: RD→Approved, RJ→Rejected, ER→RejectedWithError; também liga cada internação ao HealthFacility onde aconteceu, pelo mesmo código real de estabelecimento que o vínculo profissional usa — ver §11 para a verificação de que essa identidade é real), cnes_affiliation (vínculo profissional — Affiliation/Person/HealthFacility/Role), cnes_validity (qualificação de estabelecimento — Validity/HealthFacility, a partir de qualquer um dos seis grupos de qualificação do CNES: HB habilitação, GM gestão e metas, IN incentivos, RC regra contratual, EF estabelecimento filantrópico, EE estabelecimento de ensino; usa o sentinela real CMPT_FIM == "999999" como ausência de data de fim, nunca uma data inventada), sinasc_birth (nascimento — Birth/Delivery/Pregnancy/Newborn, a partir do grupo DN do SINASC; cada linha vira uma Pregnancy própria mesmo em gestação múltipla, porque o extrato público não carrega uma chave que ligue nascimentos múltiplos à mesma gestação — limitação documentada, não escondida), sim_death (óbito — Death/CauseOfDeath, a partir do grupo DO do SIM; liga CauseOfDeath.revises quando CAUSABAS_O difere de CAUSABAS, o mesmo padrão de revisão do axioma 14/§2.2), sia_team_affiliation (a dimensão de equipe do vínculo, via o campo PA_INE; estabelecimento e ocupação também compartilham identidade real com os outros mapeadores — ver §11 —, mas pessoa continua com identidade própria, por ser um tipo de identificador genuinamente diferente), sinan_case (investigação de agravo — NotifiableCaseInvestigation/EntryType/CaseOutcome, generalizado para as 48 bases de agravo do SINAN; o mapa de tipo de entrada continua verificado só para tuberculose, mas o mapa de desfecho EVOLUCAO_TO_CASEOUTCOME já cobre 35 dos outros 44 agravos), sinan_aggregate_activity (atividade agregada — AggregateActivity/AggregatedEncounterRecord, para as duas bases do SINAN cuja ficha é uma contagem populacional, não um caso individual: NTRA notificação de surto e TRAC tracoma — a mesma distinção agregado/individual do axioma 20), sinan_violence_involvement (participação em notificação de violência — Involvement), cadunico_family_membership (vínculo familiar — Family/Person/FamilyMembership/FamilyRole).
Duas propriedades de design, ambas verificadas, não só pretendidas: primeiro, as URIs de cada indivíduo são determinísticas — derivadas da própria chave natural do registro de origem (número de AIH, CPF, CNES, identificador de família), nunca de um contador ou de um identificador aleatório — confirmado rodando o mesmo mapeador duas vezes sobre o mesmo arquivo real e comparando byte a byte: o resultado é idêntico. Essa é a propriedade que faltava para qualquer uso além de um teste único — sem ela, carregar o mesmo dado duas vezes num armazenamento persistente duplicaria os indivíduos a cada rodada, em vez de atualizar o que já existe. Segundo, o mesmo bug real de biblioteca descrito em §9 (o filtro por grupo de arquivo do PySUS não funciona para SIH/SIA/CNES) é contornado uma única vez, dentro da própria biblioteca — quem usa pysus_to_rdf.py nunca precisa saber que esse bug existe.
Esta biblioteca resolve o problema de MAPEAMENTO — dado uma competência de um sistema, produzir RDF correto. Ela não inclui, deliberadamente, armazenamento persistente, atualização incremental agendada, nem um serviço de consulta exposto — cada um desses é uma extensão natural sobre a mesma base, não construída aqui por escolha de escopo.
Armazenamento persistente: owl/store.py
A extensão natural mencionada no fim de §10 foi construída em seguida. Toda verificação em escala real descrita em §7 usava um triple store EM MEMÓRIA, criado, populado e descartado dentro de um único processo. owl/store.py abre um triple store real em disco, carrega o esquema formal (TBox) uma única vez, e expõe load_competencia(mapeador, **params) — roda um mapeador de pysus_to_rdf.py e insere o resultado no armazenamento persistente:
from store import get_store, load_competencia store = get_store() load_competencia("sih_hospitalization", state="GO", year=2025, month=12, group="RD") load_competencia("cnes_affiliation", path="/caminho/para/PFMG2211.parquet")
Cada carga é registrada num manifest (mapeador, parâmetros, contagem, data) para pular uma carga já feita em vez de reprocessar. Essa otimização depende de, mas não substitui, a garantia de §10: como cada indivíduo tem URI determinística, inserir a mesma competência duas vezes nunca duplicaria triplas de qualquer forma — RDF é um conjunto. Reconfirmado contra dado real em escala: carregar a mesma competência forçadamente uma segunda vez não altera a contagem total de triplas.
O bug de identidade descrito em §7 foi encontrado exatamente aqui — ao popular o armazenamento pela primeira vez e perguntar "quantos Person distintos existem de fato?". Corrigido, o armazenamento final, com os seis mapeadores originais carregados contra dado real em cache, chegou a 8.268.107 triplas e 453.427 indivíduos distintos de Person — a contagem real, não mais colapsada. Nenhum axioma foi alterado; o bug estava na camada de geração de identidade (URI), não em nenhuma restrição lógica.
Uma segunda melhoria de robustez, motivada pelo mesmo episódio, mas sem nenhum bug confirmado. Dois dos mapeadores (vínculo profissional do cadastro de estabelecimentos, e vínculo familiar do CadÚnico) cunhavam URIs de Person com o mesmo prefixo genérico, sem indicar de qual fonte de dado a pessoa vinha — diferente do mapeador de equipe de atenção primária, que já namespaced suas URIs de pessoa desde o início. Isso nunca causou colisão real (os universos de chave não se sobrepõem na prática), mas a ausência de colisão dependia do FORMATO das chaves de origem, não de uma garantia de desenho. Corrigido dando a cada fonte seu próprio prefixo — aplicado nos três lugares do código onde a mesma lógica de mapeamento existia, não só um, porque corrigir só um teria criado uma inconsistência nova (a mesma pessoa real recebendo URIs diferentes conforme o código que gerou o RDF) em vez de eliminar uma. Reverificado: nenhuma contagem mudou, agora confirmado por consulta que o total se divide exatamente entre as três fontes, sem sobreposição — garantia estrutural, não mais coincidência de formato de dado.
Expansão para escala nacional, e um segundo bug real de identidade, de causa diferente do primeiro. O armazenamento inicial só tinha uma amostra pequena por sistema. Usando dado real já disponível localmente e busca de rede ao vivo para preencher lacunas, a cobertura foi ampliada para escala nacional em dois sistemas: internações hospitalares em TODOS os 27 estados reais para uma competência de referência, e vínculo profissional em 25 dos 27 estados (dois não puderam ser convertidos automaticamente de um formato de arquivo legado pela própria ferramenta de acesso — limitação real e documentada). Notificações de tuberculose passaram a cobrir oito anos reais em vez de um.
Foi exatamente ao carregar o mesmo agravo em anos diferentes que o segundo bug apareceu: o mapeador de investigação de agravo cunhava a URI de cada indivíduo a partir só do código do agravo mais a POSIÇÃO da linha no arquivo — nunca de chave natural, porque este sistema de notificação não inclui, por desenho, um identificador individual de notificação. Inofensivo com um arquivo por vez; carregar o MESMO agravo de anos diferentes colide, porque a linha na posição zero de um ano e a linha na posição zero de outro geram a mesma URI, colapsando duas notificações reais distintas numa só. Confirmado antes de corrigir: a contagem total no armazenamento era visivelmente menor que a soma aritmética esperada, e a diferença batia exatamente com o tamanho do maior arquivo individual.
Um projeto de terceiros neste mesmo ambiente, que também consome a mesma biblioteca externa de acesso a dado do SUS, foi estudado em busca de padrões alternativos. Três resultados: confirmação de que um bug de filtro já documentado nesta especificação é específico de uma geração mais nova da biblioteca (uma geração mais antiga, usada por esse projeto, não tem o problema — trocar de geração não valeria o retrabalho); uma suposição do código desse projeto, de que um certo campo seria um indicador universal de tipo de entrada para qualquer agravo, verificada contra dado real e corretamente descartada (o campo é constante nos dados testados, sem correlação com o campo já validado aqui); e, como efeito colateral, um bug real alheio a este projeto (o outro projeto usa esse campo constante para um filtro que nunca exclui nada na prática) — encontrado de graça, não perseguido por estar fora de escopo.
Correção: a URI passou a ser derivada do arquivo de origem (nome do arquivo, ou agravo mais ano) além da posição da linha. Reverificado após recarregar o armazenamento: a contagem total de investigações bateu exatamente com a soma aritmética esperada. O armazenamento final chegou a quase sete vezes o tamanho anterior. Nenhum axioma alterado — como no primeiro bug de identidade, este também estava só na camada de geração de URI.
Um alerta real do monitor de qualidade contínuo (§13), e a confirmação empírica de um comportamento só previsto até então. Rodar o monitor contra o armazenamento expandido produziu, pela primeira vez, um alerta genuíno — uma checagem que antes sempre relatava zero violações passou a relatar mais de cem. Investigado: todas as ocorrências vinham do mapeador de vínculo de equipe, cada uma com exatamente dois vínculos distintos para a mesma pessoa/estabelecimento/papel, mas com equipes diferentes.
Não era bug — era confirmação empírica, pela primeira vez, de uma multiplicidade de equipe que a regulamentação permite explicitamente, mas que uma amostra bem menor nunca tinha exposto. O problema estava na checagem: agrupava só por pessoa/estabelecimento/papel, sem incluir a equipe como parte da chave. Corrigida para incluir a equipe quando presente (tratando sua ausência como um valor próprio, sem deixar de detectar duplicidade genuína nesse caso), a checagem voltou a reportar zero violações.
O primeiro vínculo real de modelo entre dois sistemas administrativos distintos, habilitado pela mesma escala. Até este ponto, o mapeamento de internação nunca ligava a internação ao estabelecimento de saúde onde ela aconteceu, apesar de o arquivo de origem carregar o código do estabelecimento em toda linha — só o mapeamento de vínculo profissional criava indivíduos de estabelecimento. A ideia sempre foi logicamente óbvia, mas só ficou genuinamente testável depois que os dois sistemas passaram a ter cobertura nacional simultânea — testar contra um único estado teria sido confirmação fraca demais para justificar a extensão.
Adicionada uma nova relação, usando deliberadamente a MESMA identidade de estabelecimento que o vínculo profissional já usa para o mesmo código real — não uma URI nova por coincidência de nome, mas a mesma URI por design. Testado primeiro numa amostra pequena: 100% dos estabelecimentos reais das internações de um estado também apareciam no cadastro de vínculo profissional do mesmo estado. Confirmado em escala nacional: cobertura completa das internações reais com o novo vínculo; entre milhares de estabelecimentos reais distintos referenciados, a grande maioria também tinha vínculo profissional real do outro sistema — a pequena fração sem contrapartida é inteiramente consistente com os dois únicos estados cujo cadastro de profissionais não pôde ser carregado, por uma limitação de conversão de arquivo já documentada.
Nenhum axioma alterado — extensão de vocabulário, testada e verificada em escala nacional, mas sem o mesmo peso lógico formal dos 28 axiomas já catalogados.
O mesmo vínculo, estendido a um terceiro sistema, e uma decisão de namespace revisitada campo a campo. O mapeamento de vínculo de equipe do sistema de produção ambulatorial tinha, desde sua criação, todas as suas URIs marcadas com um prefixo próprio, distinguindo-as em bloco de tudo que os outros mapeadores criam — uma escolha cautelosa na época, nunca revisitada campo a campo.
Verificado agora contra dado real: o código de estabelecimento e o código de ocupação usados por esse sistema são, de fato, o MESMO espaço de identificador nacional que o cadastro de estabelecimentos e o vínculo profissional já usam — sobreposição real de mais de 97% em ambos. Um terceiro campo — o identificador de pessoa — foi deliberadamente MANTIDO separado: é um número de cartão de saúde, tipo de identificador genuinamente diferente do usado no vínculo profissional comum, sem tabela de conversão confiável disponível. Fundir os dois sem essa tabela teria sido uma alegação de identidade não verificada.
Corrigido para estabelecimento e ocupação compartilharem identidade, mantendo pessoa separada. Recarregado o armazenamento inteiro: quase metade dos estabelecimentos reais do vínculo de equipe também tinha vínculo profissional comum real carregado — e nenhum, zero, também aparecia como estabelecimento de uma internação real. Este segundo número não é um problema: o vínculo de equipe vem de atenção primária, tipicamente sem internação — domínio genuinamente diferente, não falha da identidade compartilhada. A mesma disciplina que evita presumir bug diante de um número inesperado vale para não presumir sucesso ou falha sem investigar.
Expansão para dez mapeadores, e a escala atual. As fases de trabalho seguintes estenderam pysus_to_rdf.py de seis para dez mapeadores: qualificação de estabelecimento (cnes_validity), nascimento (sinasc_birth), óbito (sim_death, com a classe nova Death/CauseOfDeath) e atividade agregada de agravo (sinan_aggregate_activity) — além de generalizar sinan_case de um único agravo de teste (tuberculose) para as 48 bases de agravo publicadas pelo SINAN. Com tudo isso carregado no mesmo armazenamento persistente, a contagem real atual, consultada diretamente contra o triple store em produção, é de 172.477.458 triplas — mais de vinte vezes o total documentado acima — com 17.157.462 indivíduos de Person, 7.321.728 de NotifiableCaseInvestigation, 6.160.154 de Affiliation, 4.654.273 de CaseOutcome, 1.161.856 de Hospitalization, 525.239 de Birth, 448.909 de HealthFacility, 373.187 de CauseOfDeath e 354.056 de Death. Nenhum axioma foi alterado por esta expansão de escala — é o mesmo esquema formal, com muito mais dado real carregado sob ele.
Endpoint de consulta: owl/sparql_endpoint.py
A terceira extensão natural — expor o armazenamento persistente (§11) para consulta por terceiros, usando um protocolo padrão em vez de exigir que qualquer consumidor escreva Python contra a biblioteca de triple store diretamente — foi construída em seguida. owl/sparql_endpoint.py implementa o SPARQL 1.1 Protocol (o padrão W3C que define como enviar uma consulta SPARQL por HTTP e como o formato da resposta é negociado), usando só a biblioteca padrão do Python (http.server), sem nenhuma dependência de framework web nova:
# GET curl 'http://127.0.0.1:8890/sparql?query=SELECT+(COUNT(*)+AS+?n)+WHERE+{?s+?p+?o}' # POST curl -X POST -H 'Content-Type: application/sparql-query' --data 'ASK { ?s ?p ?o }' http://127.0.0.1:8890/sparql
O formato da resposta é negociado pelo cabeçalho HTTP Accept: para SELECT/ASK, JSON por padrão, com XML, CSV ou TSV disponíveis; para CONSTRUCT/DESCRIBE, Turtle por padrão, com N-Triples, RDF/XML ou JSON-LD disponíveis. Uma página inicial lista consultas de exemplo já prontas para rodar.
Só leitura, garantida em duas camadas independentes — não por uma checagem manual de "que tipo de consulta é essa". Primeiro, o armazenamento é aberto em modo somente-leitura. Segundo, e mais importante: o servidor só chama a função de CONSULTA da biblioteca de triple store, nunca a de ATUALIZAÇÃO — e a função de consulta reconhece, na sua gramática, só o SPARQL de leitura, não o de escrita. Verificado diretamente: submeter uma operação de escrita pela consulta produz um erro de sintaxe, rejeitado antes de qualquer verificação de permissão de aplicação. Uma garantia de segurança estrutural, mais forte que uma lista de verificação escrita à mão — a categoria inteira de bug "esqueci de checar um caso" fica impossível quando a restrição vem da ferramenta de baixo nível, não do código de aplicação.
Testado de ponta a ponta, servidor real rodando localmente contra o armazenamento de §11: consulta de contagem total devolveu o mesmo total de triplas relatado em §11, em JSON e em CSV; ASK devolveu o booleano correto por GET e por POST; CONSTRUCT devolveu Turtle válido com dado real; e uma tentativa de operação de escrita foi rejeitada com erro HTTP claro. Liga por padrão só no endereço local da máquina — expor além disso é decisão de hospedagem/segurança fora do escopo deste módulo. Nenhum axioma alterado, nenhum dado novo carregado.
Monitor de qualidade contínuo: owl/quality_monitor.py
A quarta e última extensão — reaproveitar as verificações de axioma já validadas como um monitor operacional recorrente, em vez de um teste que roda uma vez e é descartado — fecha o conjunto de formas de conexão com o DATASUS. owl/quality_monitor.py reaproveita as cinco consultas de axioma (15, 19, 21, 22, 23) já escritas e validadas contra dado real ao longo deste documento, importadas diretamente do módulo onde foram originalmente escritas — não copiadas de novo, precisamente para não repetir o erro descrito em §7 (a mesma lógica duplicada em múltiplos arquivos, cada cópia precisando de correção separada quando um bug aparece).
A diferença central em relação às verificações anteriores: elas construíam um grafo de teste do zero a cada execução, só para provar que os axiomas seguram contra um recorte fixo de dado. O monitor não constrói nada — consulta o armazenamento persistente (§11) no estado em que ele estiver, então a mesma execução reflete sempre o dado mais recente carregado, sem precisar saber quais arquivos foram adicionados desde a última vez.
Cada checagem recebe uma classificação declarada — OK, ALERTA ou INFO — em vez de um "zero violações ou pânico" genérico. Isto importa porque uma das cinco checagens (a metade obrigatória do axioma 23) sempre retorna um número grande de "violações" contra o dado real de tuberculose carregado, por um motivo estrutural já documentado (§7/§9): o extrato público correspondente nunca carrega, por desenho de proteção de privacidade, a chave que ligaria duas notificações do mesmo paciente ao longo do tempo. Tratar isso como alerta produziria um alarme falso a cada execução, para sempre — a checagem é classificada como informativa, a contagem é sempre reportada, mas nunca conta como falha.
Testado contra o armazenamento persistente real (§11): as quatro checagens com expectativa de zero violações retornaram OK; a checagem informativa retornou o mesmo número já documentado anteriormente neste projeto para essa limitação estrutural, classificada corretamente como INFO, não ALERTA. Testado também, e de forma igualmente importante, que o monitor detecta um problema real e não apenas confirma o esperado: contra um grafo sintético com uma violação deliberada (uma alta registrada antes da admissão correspondente), a checagem correspondente retornou ALERTA com exatamente uma violação, enquanto as outras quatro continuaram normais — a mesma disciplina de teste sintético negativo usada em toda formalização lógica deste documento, aplicada agora ao próprio monitor.
Cada execução grava um relatório com data e hora num arquivo de registro cumulativo, consultável posteriormente para ver o histórico de quando cada checagem rodou e o que encontrou. O processo termina com um código de saída que distingue "tudo normal" de "alguma checagem alertou", pronto para uso direto por uma ferramenta de agendamento externa.
Quando qualquer checagem alerta, uma notificação é disparada em duas camadas de confiabilidade deliberadamente diferentes: uma linha sempre gravada num arquivo de registro de alertas dedicado (funciona mesmo sem nenhuma sessão de usuário interativa aberta — o mecanismo real), mais uma tentativa best-effort de notificação de área de trabalho, que simplesmente não faz nada, sem interromper a execução, quando não há mecanismo de notificação gráfica disponível. Testado nos dois sentidos: uma violação sintética deliberada gerou a linha de alerta esperada; uma execução limpa não gravou nenhuma linha — a ausência do registro é, ela mesma, uma confirmação.
Agendar a execução automática em si foi deixado como decisão explícita de quem opera o sistema, não construído aqui. Um limite honesto de escopo, também: o monitor verifica o estado atual do armazenamento persistente — não busca, por conta própria, novas competências recém-publicadas para carregar antes de checar. Agendar sua execução sozinha, sem que outro processo também carregue dado novo, reconfirma repetidamente o mesmo estado — valor real, mas mais restrito do que verificar automaticamente cada nova publicação, o que exigiria também automatizar a busca de dado novo, uma extensão natural não construída aqui.
Com isto, as quatro formas de conexão com o DATASUS — biblioteca de mapeamento, armazenamento persistente, protocolo de consulta padrão, e monitor de qualidade contínuo — estão todas construídas, cada uma sobre a anterior, sem que nenhuma tenha exigido retrabalho nas que vieram antes.
Um protótipo de IA sobre o próprio limite permanente do modelo
Entre as limitações listadas em §9, uma se destaca por ser a única marcada como estruturalmente insolúvel, não apenas ainda não resolvida: a metade obrigatória do axioma que exige um episódio anterior para casos de recidiva ou reingresso após abandono de tratamento de tuberculose. O extrato público desse agravo, por desenho de proteção de privacidade, nunca carrega uma chave que ligue duas notificações do mesmo paciente ao longo do tempo — não é uma lacuna que uma busca mais ampla resolveria, é uma ausência estrutural e permanente do próprio desenho do sistema de notificação.
Essa limitação é também o candidato mais bem motivado para uma aplicação de inteligência artificial sobre este projeto: um método probabilístico de vínculo de registros (record linkage), estimando por semelhança demográfica quais notificações provavelmente pertencem à mesma pessoa ao longo do tempo — não recuperando um identificador que não existe, mas estimando uma correspondência plausível. Um segundo candidato — classificar por processamento de linguagem o texto livre da causa de óbito no sistema de mortalidade — foi descartado assim que verificado: esse campo já é um código estruturado de classificação internacional de doenças, não texto livre.
Um protótipo de pesquisa foi construído e testado contra as oito competências reais de tuberculose já carregadas no armazenamento persistente (797.985 linhas). Antes de qualquer suposição, um campo cujo nome sugeria ser exatamente o vínculo que falta foi verificado contra dado real e descartado: está em branco em 91% das linhas, sem nenhuma correlação real com quais casos são efetivamente de recidiva ou reingresso.
O método usa os únicos identificadores fracos disponíveis, todos plenamente preenchidos: ano de nascimento, sexo, raça/cor, escolaridade, e município/estado de residência — nenhum nome, CPF ou cartão do sistema de saúde, confirmando que a proteção de privacidade funciona como desenhada. Casos de recidiva/reingresso são agrupados com casos anteriores de primeira ocorrência que compartilham ano de nascimento, sexo e município exatos; dentro do grupo, uma pontuação favorece consistência de idade entre os anos, concordância de raça/escolaridade, e leve preferência por proximidade temporal. A decisão compara a pontuação do melhor candidato com a do segundo colocado — sem margem clara, o método se recusa a apontar um vínculo em vez de arriscar um errado.
Resultado medido, não presumido: dos 148.820 casos reais precisando de episódio anterior, 80,1% têm pelo menos um candidato plausível no grupo — sinal de viabilidade real. Mas a média de candidatos por caso é de ~87 (grupos densos chegam a mais de 1.600) — ambíguo demais para decidir só por agrupamento. Da pontuação completa, só 11,9% dos casos receberam decisão "confiável" pela margem escolhida — e, verificado antes de aceitar o número, mais de dois terços dessas eram só casos com um único candidato no grupo (ausência de concorrência, não confirmação por separação real). A fração genuinamente distinguida de uma alternativa competente é de apenas 3,4% do total.
Este número baixo não é um resultado preliminar a melhorar com mais tempo — é o teto real do método, e é o resultado esperado: o extrato foi desenhado deliberadamente para impedir esse tipo de reidentificação. O valor de levar esta ideia adiante seria expor o resultado como uma nova instância de alegação de vínculo entre registros — conceito que este modelo já tem desde sua concepção, desenhado para carregar um grau de confiança e nunca presumir certeza — nunca como se essa metade do axioma estivesse satisfeita: ela continua sem solução disponível a partir só do dado público, por desenho, não por falta de tentativa.
Sobre este documento
Este documento descreve o modelo tal como ele é hoje — 28 axiomas, todos formalizados, nenhum revertido, todos verificados por pelo menos uma das técnicas descritas em §7. Foi construído e testado de forma iterativa e crítica, com uma disciplina consistente: toda decisão de design foi, sempre que possível, testada contra dado real (não hipotético) do próprio SUS, em ordem crescente de confiabilidade — desde estrutura de arquivo documentada até dado de linha individual em escala completa — e toda formalização lógica foi verificada por ferramentas automatizadas (reasoners de lógica de descrição, validadores SHACL, consultas agregadas contra triple stores) contra casos sintéticos conhecidos antes de rodar contra dado real, para que "zero violações" tivesse sempre um significado verificável, nunca presumido.