Guia LEORAD — organizar máscaras e autotextos com IA
Para você, médico(a): como usar
Jeito mais rápido: em leorad.com.br/organizar-mascaras ou no LEORAD (biblioteca → botão + → Organizar acervo com IA), clique na IA que você usa (ChatGPT, Claude ou Gemini): este guia vai copiado. Na conversa que abrir, cole (Ctrl+V), envie e depois anexe o seu material. Ali também dá para organizar sem sair do LEORAD, na aba Aqui no LEO.
Sem o botão:
- Abra o ChatGPT, o Claude ou o Gemini numa conversa nova.
- Anexe este arquivo e o seu material: documentos do Word, PDFs, planilhas, export de outro sistema ou texto colado.
- Escreva: "Organize minhas máscaras e autotextos seguindo o guia."
Nos dois casos, a IA vai fazer algumas perguntas, mostrar a organização proposta e, depois do seu ok, gerar um arquivo .json. No LEORAD: Biblioteca → botão + → Backup do LEO e escolha o .json (ele aceita .leo e .json).
Dicas:
- Comece com um lote pequeno, de 5 a 10 máscaras, para conferir como fica. A importação cria tudo de uma vez, e desfazer é apagar item por item.
- Não envie laudos de pacientes reais. Use só modelos, sem nome, data de nascimento ou número de exame.
- Acervo grande? Peça um arquivo por região ou modalidade (um de Crânio, outro de Abdome…) e importe um de cada vez.
Para a IA: instruções
Você vai ajudar um(a) radiologista a transformar as máscaras (modelos de laudo) e os autotextos (frases prontas) dele(a) num arquivo .json que o LEORAD importa direto. Siga este guia à risca: o formato é estrito, e alguns erros fazem itens sumirem sem aviso na importação.
Regras de ouro
- Organize, não reescreva. Você pode criar pastas, padronizar nomes de máscara, criar atalhos e apontar duplicatas. O texto do laudo fica literal: não corrija, não resuma, não "melhore" termos médicos, não troque sinônimos. Se algo parecer errado (erro de digitação, lateralidade estranha, medida incoerente), aponte ao usuário e mantenha como está. Só edite o texto se o usuário pedir explicitamente.
- Preserve campos de preenchimento. Marcadores como
___,***,[ ],( ),xx mmou<<medida>>ficam exatamente como vieram. - Nada de dados de paciente. Se o material tiver nome de paciente, data de nascimento, número de prontuário/atendimento ou assinatura com CRM de laudo real, pare e avise. Pergunte se o usuário quer que você remova esses trechos ou se ele prefere mandar só os modelos.
- Mostre antes de gerar. Nunca gere o arquivo final sem o usuário aprovar a estrutura.
- Não peça senha, token, login nem chave. Nada disso é necessário.
Fluxo da conversa
Etapa 0 — Se ainda não há material. Muitas vezes o usuário chega por um link, e você lê este guia antes de ele anexar qualquer coisa. Nesse caso, não resuma o guia nem explique o formato. Responda em no máximo 4 linhas, em português e com tom cordial:
Oi! Vou organizar suas máscaras e autotextos para importar no LEORAD. Anexe aqui o seu material (Word, PDF, planilha ou texto colado). Pode ser tudo de uma vez. Só peço que sejam modelos, sem dados de pacientes.
E espere o material.
Etapa 1 — Entender o material. Leia tudo o que foi anexado e responda com um resumo curto: quantos textos encontrou, de que tipo e como parecem organizados. Pergunte só o que não der para deduzir:
- O que é máscara (laudo completo) e o que é autotexto/frase (trecho para inserir no meio do laudo)? Se a separação estiver óbvia, proponha em vez de perguntar.
- Como ele prefere as pastas: por região (Crânio, Tórax, Abdome…), por modalidade (TC, RM, US…) ou pela organização que já existe no material?
- Qual nome dar à pasta principal (ex.: "Minhas máscaras", "Clínica X")?
Etapa 2 — Propor a estrutura. Mostre a árvore em texto, com o nome e o atalho de cada item, assim:
Máscaras — Dra. Ana
├── Crânio
│ ├── TC CRÂNIO NORMAL (tccranio)
│ └── RM CRÂNIO NORMAL (rmcranio)
└── Abdome
└── US ABDOME TOTAL NORMAL (usabdtotal)
Frases — Dra. Ana
└── Fígado
└── ESTEATOSE LEVE (esteatoseleve)
Liste também: duplicatas ou quase-duplicatas encontradas, textos que você não soube classificar e qualquer coisa que pareça errada no conteúdo. Espere o ok.
Etapa 3 — Gerar o arquivo. Monte o .json no formato abaixo, rode o validador (ou o checklist manual, se você não puder executar código) e corrija tudo o que ele apontar. Entregue:
- o arquivo para download, com um nome como
biblioteca-cranio.json; - se você não conseguir gerar arquivo, o JSON completo num único bloco de código, com a instrução: "copie, cole no Bloco de Notas e salve como
biblioteca.json". No Bloco de Notas, o usuário deve escolher "Todos os arquivos" em Tipo e a codificação UTF-8; - um resumo de uma linha: quantas pastas, quantas máscaras e quantas frases.
Se o acervo for grande (mais de ~40 textos), gere um arquivo por pasta principal em vez de um arquivo gigante, para não cortar o conteúdo no meio.
Formato do arquivo
Um array JSON plano, sem aninhamento. A hierarquia é dada pelo campo parent, que aponta para o uuid de outro item do mesmo arquivo.
Pasta:
{ "uuid": "p-cranio", "name": "Crânio", "type": "mask", "order": 0, "parent": "raiz-mascaras" }
A pasta principal (raiz) não tem o campo parent.
Documento (máscara ou frase):
{
"uuid": "d-tc-cranio",
"name": "TC CRÂNIO NORMAL",
"type": "mask",
"order": 0,
"parent": "p-cranio",
"isLeaf": true,
"text": "<p><strong>TOMOGRAFIA COMPUTADORIZADA DO CRÂNIO</strong></p><p><strong>Técnica:</strong></p><p>Aquisição volumétrica sem contraste.</p><p><strong>Análise:</strong></p><p>Parênquima encefálico com atenuação preservada.</p><p><strong>Impressão:</strong></p><p>Exame dentro dos padrões da normalidade.</p>",
"instructions": "",
"speechText": "tomografia de crânio normal",
"shortcut": "tccranio"
}
| campo | regra |
|---|---|
uuid | Identificador único dentro do arquivo. Qualquer texto serve (p1, d-tc-cranio…). O LEORAD gera os definitivos na importação. |
name | Nome que aparece na biblioteca. Máscaras em MAIÚSCULAS no padrão MODALIDADE REGIÃO ACHADO costumam ficar mais fáceis de achar. Não pode repetir dentro da mesma pasta, nem mudando só maiúscula/minúscula. |
type | "mask" para máscara, "phrase" para autotexto/frase. Define em qual aba o item entra. |
order | Posição dentro da pasta: 0, 1, 2… |
parent | uuid da pasta onde o item fica. Obrigatório em tudo, menos na raiz. |
isLeaf | true em documentos. Pastas não têm esse campo. |
text | Corpo do texto em HTML: um <p>…</p> por parágrafo e <strong> nos títulos de seção. Pode usar <em> e <u>. Não use \n, porque quebra de linha não aparece. Não use tabelas, estilos, classes nem cores. Nunca vazio. |
shortcut | Atalho para chamar o texto: minúsculo, sem espaço, sem acento, até 30 caracteres, e sem repetir no arquivo. |
speechText | Como o médico falaria o nome ao ditar ("tomografia de crânio normal"). Pode ser "". |
instructions | Orientação para a IA do LEORAD que não sai no laudo (ex.: "sempre citar a medida do maior nódulo"). HTML ou "". Só preencha se o material trouxer instruções explícitas. |
Máscaras e frases ficam em árvores separadas. Um documento precisa ter o mesmo type da pasta onde está, e uma pasta o mesmo type da pasta-mãe. Por isso, crie uma raiz para máscaras ("type": "mask") e outra para frases ("type": "phrase").
O que faz item sumir sem aviso
A importação não mostra erro nestes casos: o item simplesmente não aparece.
parentapontando para umuuidque não existe no arquivo.- Documento sem
parent(solto na raiz). - Documento ou pasta com
typediferente da pasta onde está. - Dois itens com o mesmo
namena mesma pasta, inclusive quando a diferença é só maiúscula/minúscula. textvazio.shortcutcom mais de 30 caracteres.- Mais de 8 níveis de pasta abaixo da raiz.
Validador (rode antes de entregar)
Se você pode executar Python, salve o JSON como biblioteca.json e rode este script. Se ele terminar com "Revise antes de importar", corrija e rode de novo.
import json, re, sys
from collections import Counter, defaultdict
caminho = sys.argv[1] if len(sys.argv) > 1 else "biblioteca.json"
with open(caminho, encoding="utf-8") as f:
dados = json.load(f)
nos = dados["jsonContent"] if isinstance(dados, dict) else dados
erros, avisos = [], []
if not isinstance(nos, list):
sys.exit("O arquivo precisa ser um array JSON [ ... ].")
por_uuid = {}
for n in nos:
u = n.get("uuid")
if not u:
erros.append(f"item sem uuid: {n.get('name')!r}")
elif u in por_uuid:
erros.append(f"uuid repetido: {u}")
por_uuid[u] = n
docs = [n for n in nos if n.get("isLeaf")]
pastas = [n for n in nos if not n.get("isLeaf")]
for n in nos:
nome = n.get("name") or "(sem nome)"
if not n.get("name"):
erros.append(f"item sem name (uuid {n.get('uuid')})")
if n.get("type") not in ("mask", "phrase"):
erros.append(f"{nome}: type deve ser 'mask' ou 'phrase'")
pai = n.get("parent")
if pai and pai not in por_uuid:
erros.append(f"{nome}: parent '{pai}' não existe no arquivo")
elif pai and por_uuid[pai].get("isLeaf"):
erros.append(f"{nome}: parent aponta para um documento, não para uma pasta")
elif pai and por_uuid[pai].get("type") != n.get("type"):
erros.append(f"{nome}: type '{n.get('type')}' diferente da pasta '{por_uuid[pai].get('name')}'")
for d in docs:
nome = d.get("name")
if not d.get("parent"):
erros.append(f"{nome}: documento sem parent (precisa estar dentro de uma pasta)")
texto = (d.get("text") or "").strip()
if not texto:
erros.append(f"{nome}: text vazio")
elif not re.search(r"<[a-z]", texto, re.I):
erros.append(f"{nome}: text sem HTML — use <p>…</p> por parágrafo")
elif "\n" in texto:
avisos.append(f"{nome}: text tem \\n — quebras de linha devem ser <p>")
atalho = d.get("shortcut") or ""
if len(atalho) > 30:
erros.append(f"{nome}: shortcut com {len(atalho)} caracteres (máx. 30)")
if atalho and not re.fullmatch(r"[a-z0-9._-]+", atalho):
avisos.append(f"{nome}: shortcut '{atalho}' tem maiúscula, espaço ou acento")
if re.search(r"\b(paciente|prontu[aá]rio|atendimento|nascimento|DN)\s*[:\-]", texto, re.I):
avisos.append(f"{nome}: pode conter dado de paciente — confira")
repetidos = [s for s, c in Counter(d.get("shortcut") for d in docs if d.get("shortcut")).items() if c > 1]
for s in repetidos:
erros.append(f"shortcut repetido: {s}")
irmaos = defaultdict(list)
for n in nos:
irmaos[(n.get("parent"), n.get("type"), bool(n.get("isLeaf")))].append((n.get("name") or "").lower())
for (pai, _, _), nomes in irmaos.items():
for nome, c in Counter(nomes).items():
if c > 1:
onde = por_uuid.get(pai, {}).get("name", "raiz")
erros.append(f"nome repetido na pasta '{onde}': {nome}")
def profundidade(n):
d, atual = 0, n
while atual.get("parent") in por_uuid and d < 50:
atual = por_uuid[atual["parent"]]
d += 1
return d
mais_funda = max([profundidade(p) for p in pastas] or [0])
if mais_funda > 8:
erros.append(f"{mais_funda} níveis de pasta abaixo da raiz (máx. 8)")
print(f"pastas: {len(pastas)}")
print(f"máscaras: {sum(d.get('type') == 'mask' for d in docs)}")
print(f"frases: {sum(d.get('type') == 'phrase' for d in docs)}")
print(f"profundidade máxima: {mais_funda}")
for e in erros:
print("ERRO ", e)
for a in avisos:
print("AVISO ", a)
print("\nRevise antes de importar." if erros else "\nPronto para importar.")
Checklist manual (se não puder rodar código)
Confira item por item antes de entregar:
- O arquivo começa com
[e termina com], e é JSON válido (aspas duplas, sem vírgula sobrando no fim). - Todo
uuidé diferente dos outros. - Todo
parentaponta para ouuidde uma pasta que está no arquivo. - Só as pastas raiz não têm
parent, e nenhum documento fica sem pasta. - Máscaras estão só em pastas
mask, e frases só em pastasphrase. - Nenhum nome se repete dentro da mesma pasta.
- Todo
textestá em<p>…</p>, sem\ne nunca vazio. - Todo
shortcuté minúsculo, sem espaço nem acento, tem até 30 caracteres e não se repete. - No máximo 8 níveis de pasta abaixo da raiz.
Modelo completo (importa como está)
[
{ "uuid": "raiz-m", "name": "Minhas máscaras", "type": "mask", "order": 0 },
{ "uuid": "p-cranio", "name": "Crânio", "type": "mask", "order": 0, "parent": "raiz-m" },
{
"uuid": "d-rm-cranio",
"name": "RM CRÂNIO NORMAL",
"type": "mask",
"order": 0,
"parent": "p-cranio",
"isLeaf": true,
"text": "<p><strong>RESSONÂNCIA MAGNÉTICA DO CRÂNIO</strong></p><p><strong>Análise:</strong></p><p>Parênquima encefálico com sinal e morfologia preservados.</p><p><strong>Impressão diagnóstica:</strong></p><p>Exame dentro dos padrões da normalidade.</p>",
"instructions": "",
"speechText": "ressonância de crânio normal",
"shortcut": "rmcranio"
},
{ "uuid": "raiz-f", "name": "Minhas frases", "type": "phrase", "order": 0 },
{ "uuid": "p-figado", "name": "Fígado", "type": "phrase", "order": 0, "parent": "raiz-f" },
{
"uuid": "f-esteatose",
"name": "ESTEATOSE LEVE",
"type": "phrase",
"order": 0,
"parent": "p-figado",
"isLeaf": true,
"text": "<p>Fígado com ecogenicidade discretamente aumentada, compatível com esteatose leve.</p>",
"instructions": "",
"speechText": "esteatose leve",
"shortcut": "esteatoseleve"
}
]
Lembretes ao usuário (inclua na entrega)
- Importar: Biblioteca → botão + → Backup do LEO e escolher o
.json(o importador aceita.leoe.json). - Se a pasta principal tiver o mesmo nome de uma que ele já tem, o LEORAD cria outra com um número no fim. Nada é sobrescrito.
- Depois de importar, vale abrir duas ou três máscaras e conferir a formatação antes de importar os próximos lotes.