API de plugins de mapeamento¶
Referência técnica completa do sistema de plugins de mapeamento do Anotae. Descreve o formato JSON dos arquivos de mapeamento, as classes e funções Python públicas, o processo de descoberta automática e os comandos CLI relacionados.
1. Visão geral¶
O sistema de mapeamento define como campos de um atendimento Anotae (modelo SOAP + vocabulários clínicos) são injetados nos formulários de sistemas externos como e-SUS APS PEC e SISREG III. Cada mapeamento é um arquivo JSON puro — sem código executável — interpretado pela engine de injeção na extensão de navegador.
Três fontes de mapeamentos são suportadas, em ordem crescente de prioridade:
| Fonte | Localização | Prioridade |
|---|---|---|
| Built-in | vocabularies/mappings/ (dentro do pacote instalado) |
Menor |
| User-level | ~/.anotae/mappings/ |
Média |
| Custom | Caminhos em ANOTAE_MAPPING_PATHS (variável de ambiente) |
Maior |
Quando dois arquivos definem o mesmo id, a fonte de maior prioridade
vence e a fonte substituída é registrada no DiscoveryReport.overridden.
Mapeamentos inválidos são pulados com aviso estruturado — nunca derrubam
o app.
2. Estrutura de um mapping JSON¶
2.1 Campos do objeto raiz¶
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id |
string |
Sim | Identificador único do mapeamento (slug sem espaços) |
name |
string |
Sim | Nome legível, exibido na UI e no CLI |
target_system |
string |
Sim | Sistema-alvo (ex.: "esus-aps-pec", "sisreg-iii") |
target_version |
string |
Sim | Versão do sistema-alvo testada (ex.: "5.x") |
version |
string |
Sim | Versão semântica do arquivo de mapeamento (ex.: "2.0.0") |
description |
string |
Não | Descrição para fins de auditoria e documentação |
fields |
array |
Sim | Lista de mapeamentos de campo (ver §2.2) |
_unmapped_fields |
array |
Não | Campos do sistema-alvo sem correspondência — documentação informativa, ignorado pela engine |
2.2 Estrutura de um item de fields¶
Cada elemento do array fields é um objeto com os campos abaixo.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
source |
string |
Sim | Caminho do campo no modelo Anotae (ex.: "soap.subjective", "vitals.weight_kg") |
target_selectors |
array |
Sim | Lista de seletores DOM tentados em ordem até o primeiro que encontrar o elemento (ver §2.3) |
required |
boolean |
Não (padrão false) |
Se true, a engine registra erro quando o campo não é encontrado no DOM |
transform |
string \| null |
Não | Transformação pré-injeção (ex.: "first_code" para extrair primeiro código de uma lista) |
2.3 Estrutura de um item de target_selectors¶
| Campo | Tipo | Valores válidos | Descrição |
|---|---|---|---|
type |
string |
"id", "name", "label", "aria-label", "data-testid", "css" |
Estratégia de localização do elemento DOM |
value |
string |
— | Valor do seletor para a estratégia escolhida |
A engine tenta cada seletor na ordem declarada e usa o primeiro que localizar o elemento. Isso permite robustez frente a variações de versão do sistema-alvo.
2.4 Exemplo completo¶
Mapeamento built-in para o Atendimento Individual do e-SUS APS PEC
(vocabularies/mappings/esus-aps-pec-atendimento-individual.json):
{
"id": "esus-aps-pec-atendimento-individual",
"name": "e-SUS APS PEC - Atendimento Individual",
"target_system": "esus-aps-pec",
"target_version": "5.x",
"version": "2.0.0",
"description": "Mapeamento de campos Anotae para o formulário de Atendimento Individual do e-SUS APS PEC.",
"fields": [
{
"source": "soap.subjective",
"target_selectors": [
{"type": "id", "value": "subjetivo"},
{"type": "aria-label", "value": "Subjetivo"},
{"type": "label", "value": "Subjetivo"},
{"type": "css", "value": "textarea[placeholder*='Subjetivo']"}
],
"required": false,
"transform": null
},
{
"source": "ciap2_codes",
"target_selectors": [
{"type": "data-testid", "value": "subjetivoCiap"},
{"type": "name", "value": "ciap"}
],
"required": false,
"transform": "first_code"
},
{
"source": "vitals.weight_kg",
"target_selectors": [
{"type": "name", "value": "objetivo.medicoes.peso"}
],
"required": false,
"transform": null
}
],
"_unmapped_fields": [
{
"target_name": "finalizacao.tipoAtendimento",
"reason": "Depende de contexto de agendamento, não disponível no Anotae v0.x"
}
]
}
2.5 Valores documentados de source¶
Os caminhos de source reconhecidos pela engine de injeção:
source |
Descrição |
|---|---|
soap.subjective |
Seção S do SOAP |
soap.objective |
Seção O do SOAP |
soap.assessment |
Seção A do SOAP |
soap.plan |
Seção P do SOAP |
ciap2_codes |
Lista de códigos CIAP-2 selecionados |
cid10_codes |
Lista de códigos CID-10 selecionados |
sigtap.procedure_code |
Código SIGTAP do procedimento |
vitals.weight_kg |
Peso em kg |
vitals.height_cm |
Altura em cm |
vitals.systolic_bp |
PA sistólica (mmHg) |
vitals.diastolic_bp |
PA diastólica (mmHg) |
vitals.heart_rate |
Frequência cardíaca (bpm) |
vitals.respiratory_rate |
Frequência respiratória (ipm) |
vitals.temperature_celsius |
Temperatura (°C) |
vitals.oxygen_saturation |
Saturação de O₂ (%) |
vitals.blood_glucose |
Glicemia (mg/dL) |
vitals.waist_circumference_cm |
Circunferência abdominal (cm) |
vitals.head_circumference_cm |
Perímetro cefálico (cm) |
vitals.calf_circumference_cm |
Perímetro da panturrilha (cm) |
3. API Python¶
Todos os símbolos públicos estão em anotae.core.mapping_plugin e
anotae.core.mapping_engine.
3.1 MappingSource — enum¶
# anotae/core/mapping_plugin.py
class MappingSource(StrEnum):
BUILTIN = "builtin"
USER = "user"
CUSTOM = "custom"
Representa a origem de um plugin descoberto. Valores com prioridade
crescente: BUILTIN (0) < USER (1) < CUSTOM (2).
3.2 SelectorDef — dataclass¶
# anotae/core/mapping_engine.py
@dataclass(frozen=True)
class SelectorDef:
type: str # "id" | "name" | "label" | "aria-label" | "data-testid" | "css"
value: str
3.3 FieldMappingDef — dataclass¶
@dataclass(frozen=True)
class FieldMappingDef:
source: str
target_selectors: list[SelectorDef] # default: []
required: bool # default: False
transform: str | None # default: None
3.4 MappingDefinition — dataclass¶
Representa um arquivo de mapeamento completamente parseado.
@dataclass(frozen=True)
class MappingDefinition:
id: str
name: str
target_system: str
target_version: str
version: str
description: str # default: ""
fields: list[FieldMappingDef] # default: []
3.5 MappingPlugin — dataclass¶
Mapeamento descoberto associado à sua origem no sistema de arquivos.
# anotae/core/mapping_plugin.py
@dataclass(frozen=True)
class MappingPlugin:
definition: MappingDefinition
source: MappingSource
path: Path
3.6 DiscoveryReport — dataclass¶
Resultado completo de uma execução de discover().
@dataclass(frozen=True)
class DiscoveryReport:
plugins: dict[str, MappingPlugin] # id → plugin ativo
skipped: list[tuple[Path, str]] # (caminho, motivo do erro)
overridden: list[str] # ids substituídos por prioridade
3.7 discover() -> DiscoveryReport¶
Varre as três fontes (built-in, user-level, custom) na ordem correta,
resolve conflitos por prioridade e retorna um DiscoveryReport. Chamada
idempotente — cada invocação relê o disco.
3.8 load_mapping(path: Path) -> MappingDefinition¶
from anotae.core.mapping_engine import load_mapping
definition = load_mapping(Path("~/.anotae/mappings/meu-mapping.json"))
Carrega e parseia um único arquivo JSON. Lança ValidationError se o
arquivo não existir, não for JSON válido ou não puder ser parseado.
3.9 validate_mapping(mapping: MappingDefinition) -> list[str]¶
from anotae.core.mapping_engine import validate_mapping
erros = validate_mapping(definition)
if erros:
print("Mapping inválido:", erros)
Retorna lista de mensagens de erro de coerência (campos obrigatórios ausentes, tipos de seletor inválidos etc.). Lista vazia indica mapping válido.
3.10 list_by_target_system(target_system, *, report=None) -> list[MappingPlugin]¶
from anotae.core.mapping_plugin import list_by_target_system
plugins = list_by_target_system("esus-aps-pec")
Filtra plugins por target_system. Se report não for passado, chama
discover() internamente. Retorna lista ordenada por id.
3.11 MappingRegistry — classe¶
Registry injetável para testes e injeção de dependência.
from anotae.core.mapping_plugin import MappingRegistry
# A partir da descoberta real
registry = MappingRegistry.from_discovery()
# Ou com conjunto controlado (testes)
registry = MappingRegistry(plugins={})
| Método | Assinatura | Descrição |
|---|---|---|
from_discovery() |
cls -> MappingRegistry |
Cria instância com todos os plugins descobertos |
get() |
(mapping_id: str) -> MappingPlugin \| None |
Busca plugin por id |
all() |
() -> list[MappingPlugin] |
Todos os plugins ordenados por id |
by_target_system() |
(target_system: str) -> list[MappingPlugin] |
Filtra por sistema-alvo |
register() |
(plugin: MappingPlugin) -> None |
Registra ou substitui plugin |
unregister() |
(mapping_id: str) -> bool |
Remove plugin; retorna True se existia |
4. Descoberta automática¶
4.1 Ordem de varredura¶
discover() varre as fontes nesta sequência fixa:
vocabularies/mappings/(caminho relativo ao pacote instalado)~/.anotae/mappings/- Cada caminho em
ANOTAE_MAPPING_PATHS, na ordem declarada
Arquivos schema.json são ignorados em todos os diretórios.
4.2 Resolução de conflitos¶
Quando dois arquivos têm o mesmo id:
- A fonte de maior prioridade (CUSTOM > USER > BUILTIN) vence.
- O
idsubstituído é adicionado aDiscoveryReport.overridden. - Fontes de mesma prioridade em caminhos diferentes: o último caminho
declarado em
ANOTAE_MAPPING_PATHSvence (varredura sequencial).
4.3 Variável de ambiente ANOTAE_MAPPING_PATHS¶
Formato: caminhos absolutos separados pelo separador do sistema operacional
(os.pathsep — : no Linux/macOS, ; no Windows).
# Linux / macOS
export ANOTAE_MAPPING_PATHS="/opt/mappings-institucional:/tmp/mappings-dev"
# Windows
set ANOTAE_MAPPING_PATHS=C:\mappings-institucional;C:\mappings-dev
Caminhos inexistentes são silenciosamente ignorados (não causam erro).
4.4 Tratamento de erros de validação¶
Cada arquivo com problema é adicionado a DiscoveryReport.skipped como
(Path, motivo_str). Motivos possíveis:
ValidationError: ...— JSON inválido ou campo obrigatório ausenteI/O: ...— erro de leitura do arquivo (permissão, encoding)errors: ...— falha nas regras de coerência devalidate_mapping()
O app continua normalmente; apenas os arquivos problemáticos são omitidos.
5. CLI¶
anotae mappings list¶
Lista todos os mapeamentos descobertos.
Filtra por sistema-alvo com --target-system:
Exemplo de saída:
ID NOME SISTEMA VERSÃO FONTE
esus-aps-pec-atendimento-individual e-SUS APS PEC - Atendimento Individual esus-aps-pec 2.0.0 builtin
sei-relatorio-ses-df SEI SES-DF - Relatório de Atendimento sei 1.0.0 user
sisreg-iii-solicitacao SISREG III - Solicitação sisreg-iii 1.1.0 builtin
A coluna FONTE indica a origem: builtin, user ou custom.
6. Criar um mapping personalizado¶
Guia rápido para criar um mapeamento user-level em ~/.anotae/mappings/.
Passo 1 — Criar o diretório (se não existir)
Passo 2 — Criar o arquivo JSON
{
"id": "meu-sistema-local",
"name": "Meu Sistema Local - Formulário de Consulta",
"target_system": "meu-sistema-local",
"target_version": "1.0",
"version": "1.0.0",
"description": "Mapeamento privado para sistema interno da unidade.",
"fields": [
{
"source": "soap.subjective",
"target_selectors": [
{"type": "id", "value": "campoQueixaPrincipal"},
{"type": "name", "value": "queixa"}
],
"required": false,
"transform": null
},
{
"source": "soap.plan",
"target_selectors": [
{"type": "css", "value": "textarea.plano-terapeutico"}
],
"required": false,
"transform": null
}
]
}
Passo 3 — Verificar com o CLI
O novo mapping deve aparecer na coluna FONTE como user.
Passo 4 — Inspecionar erros (se o mapping não aparecer)
from anotae.core.mapping_plugin import discover
report = discover()
for path, motivo in report.skipped:
print(f"Pulado: {path} — {motivo}")
Passo 5 — Testar injeção
Abra o sistema-alvo no navegador com a extensão Anotae ativa, preencha um atendimento e acione a injeção. Verifique no painel da extensão se os campos foram mapeados corretamente.
Nota: mappings user-level têm prioridade sobre built-ins. Se você criar um arquivo com o mesmo
idde um mapeamento built-in, o seu substituirá o padrão sem alterar o pacote instalado.
7. Segurança e sandboxing¶
Dados, nunca código¶
Os arquivos de mapeamento são exclusivamente dados (JSON). Não há
suporte a expressões arbitrárias, scripts, lambdas ou qualquer forma de
código executável nos arquivos de mapeamento. A engine de injeção na
extensão interpreta os seletores DOM e os valores de source — não há
eval() nem execução dinâmica em nenhum ponto do pipeline.
Validação de seletores na carga¶
validate_mapping() verifica que cada type em target_selectors
pertence ao conjunto de estratégias reconhecidas:
id, name, label, aria-label, data-testid, css.
Seletores com tipo inválido causam rejeição do arquivo inteiro com aviso
em DiscoveryReport.skipped.
Sem acesso à rede¶
Mappings não têm mecanismo para fazer requisições de rede. Nenhum campo do schema aceita URLs, callbacks ou referências externas. A engine de injeção opera inteiramente no DOM local da aba do navegador.
Integridade dos built-ins¶
Os mapeamentos built-in em vocabularies/mappings/ fazem parte do
pacote distribuído, cobertos pela attestation Sigstore gerada em cada
release (.github/workflows/build-windows.yml). Mapeamentos user-level
e custom não passam por essa verificação — responsabilidade do operador.
Gerado em 2026-05-19. Fonte de verdade: anotae/core/mapping_plugin.py
e anotae/core/mapping_engine.py.