portal_publico/.claude/skills/importacao-plano-saude/SKILL.md

109 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: importacao-plano-saude
description: Guia de manutenção/extensão da etapa de leitura e validação da ferramenta "Importação de Plano de Saúde" do Portal De Paula (portal_api/planos_saude/, tela importacao-plano-saude.html) — extração dos relatórios de operadora (12 parsers já implementados), casamento contra a planilha padrão e regras de negócio (regra de custeio, "Regra empresa", vínculos de nome), independente do sistema contábil de destino. Documenta quais operadoras já estão validadas com dado real, os gaps conhecidos por operadora, e como adicionar uma operadora nova. Direciona pra uma skill específica de sistema contábil (Questor hoje; Contabit no futuro) pra tudo que for leiaute/geração de arquivo final. Usar ao dar manutenção nos parsers, ao investigar uma divergência de valores, ou ao decidir se uma operadora nova pode ser cadastrada com segurança.
---
# Importação de Plano de Saúde: leitura, extração e regras de negócio
## 0. O que este documento é (e o que não é)
Este SKILL.md documenta a etapa **comum a qualquer sistema contábil de destino**: ler o relatório de faturamento de uma operadora de plano de saúde/odontológico, extrair beneficiários e valores, e aplicar as regras de negócio (custeio, "Regra empresa", nome divergente) que decidem quanto cada lançamento deve valer — tudo isso antes de qualquer formatação específica de sistema. É o complemento "por quê"/"cuidado com X" da documentação técnica, já exaustivamente descrita em `CLAUDE.md` (seção "Importação de Plano de Saúde (Utilitários)"). Antes de tocar em qualquer parser ou regra de custeio, ler os dois: `CLAUDE.md` pra arquitetura (models, endpoints, formato de `custeio_por_tipo`, "Regra empresa", "Vínculos de nome salvos"), este arquivo pra contexto de negócio e pra lista de gaps ainda não confirmados como equivalentes ao processo manual que a ferramenta substitui.
**Se a tarefa é sobre Cadastro de Regras, planilha padrão, leiaute ou geração do CSV/ZIP final: pare aqui.** Esta skill não cobre isso — é escopo da skill específica do sistema contábil de destino. A ferramenta hoje só gera saída pro **Questor**: se for esse o caso (ou se não tiver sido dito o contrário), carregar `importacao-questor-plano-saude` antes de prosseguir. Se o destino for o **Contabit** (formato alternativo de rubricas 338/200/201), avisar o usuário que essa etapa ainda não foi implementada, nem a skill correspondente existe ainda — não inventar um leiaute Contabit sem confirmação explícita (ver gap 3 na seção 3).
## 1. Operadoras já parametrizadas hoje (fotografia em 25/08/2026)
A ferramenta atende hoje várias empresas/operadoras reais, nenhuma com tratamento especial no código de extração — toda operadora passa pelo mesmo `OperadoraParser`/`pipeline.processa_importacao` genérico. **É uma fotografia, não um fato permanente**: cresce todo mês, reconsultar `ImportacaoPlanoSaude` (`python manage.py shell`) antes de confiar nela pra uma decisão importante.
### 1.1 Os 12 parsers de operadora existentes (13 cadastros — Amil Odonto tem dois): uso real até agora
| Operadora (`pipeline.OPERADORAS`) | Casamento | Importações no banco | Alguma concluída? |
|---|---|---|---|
| `unimed_saude` (5060, CSV ou 2 PDFs) | nome (mensalidade), CPF (coparticipação em PDF) | 8 | Sim (empresas 1123, 221, mais testes antigos) |
| `itamed_saude` (3755) | nome | 11 | Sim (221, 197, 1684, 626) |
| `dental_uni_odonto_mensalidade` (Dental Uni) | nome | 2 | **Não**, as 2 existentes estão em "revisão". **Tem 2 layouts de relatório**: um com `[Nº Cartão]` entre colchetes e indentação distinguindo titular/dependente (validado empresa 1084), outro sem colchete (Nº Cartão solto) e mesma indentação para titular/dependente, distinguido pela presença de "Total Fam" (validado empresa 503, "TAROBA CONSTRUCOES LTDA", 27/08/2026) — ver `operadoras/dental_uni/odonto_mensalidade.py` |
| `unimed_oeste_pr_saude` (4709) | nome | 3 | Sim, mas de uma execução **anterior** à empresa 1601 hoje cadastrada (a de 1601 está em revisão) |
| `bradesco_saude` (1386) | nome | 1 | Sim (empresa 221). PDF sem texto (OCR via Docling); desde 09/2026 lê linha a linha pelas posições OCR (não pelas células da tabela, que o modelo funde) e confere contra o "(TS)TOTAIS DA SUBFATURA" do Resumo; um beneficiário pode ter vários lançamentos (inclusão retroativa), ver rodada 136 em `portal_api/planos_saude/CHANGELOG.md` |
| `bradesco_dental_odonto_mensalidade` (3759) | nome | 1 | Sim (empresa 1684). **Ver ressalva abaixo** |
| `unimed_vitoria_saude` (4750) | nome | 1 | Sim (empresa 792) |
| `sulamerica_odonto_mensalidade` (4726) | CPF | 1 | Sim (empresa 792) |
| `amil_odonto_mensalidade` (3758) | CPF | 0 até 26/08/2026 | Nunca foi rodada dentro da ferramenta até então — ver nota abaixo, primeiro teste real achou e corrigiu um bug de parsing |
| `amil_odonto_mensalidade_898` (898) | CPF | 0 até 28/08/2026 | Nunca foi rodada — segundo cadastro da mesma operadora/mesmo parser no Questor (empresas diferentes usam um código ou outro), adicionado a pedido do usuário; regras e parâmetros de extração são idênticos aos de `amil_odonto_mensalidade` (3758) |
| `humana_saude` (5064) | nome | 0 até 27/08/2026 | Nunca foi rodada dentro da ferramenta até então — parser novo, construído via o checklist da seção 4.1 direto no primeiro teste com o arquivo-modelo (empresa 1972). **Coparticipação nunca casa automaticamente** (decisão do usuário): sem CPF nem matrícula confiável na tabela "DESPESAS COBRADAS" (nome truncado por largura de coluna), cada evento vira direto um item de auditoria — ver `operadoras/humana/saude.py` |
| `unimed_cascavel_saude` (158) | nome | 0 até 27/08/2026 | Nunca foi rodada dentro da ferramenta até então — parser novo, mesma empresa-modelo da Humana (1972). Outra Unimed regional, layout de PDF sem nenhuma sobreposição com `unimed_saude` (5060). **Coparticipação pode vir de duas fontes possíveis pro mesmo mês** (tabela embutida no relatório de mensalidade OU extrato separado — "o modelo de arquivo é gerado pela operadora"), nunca somadas: `OperadoraParser.finaliza()` (hook novo, chamado só depois de ver todos os arquivos da importação) resolve qual usar, preferindo o extrato separado — ver `operadoras/unimed_cascavel/saude.py` |
| `metlife_odonto_mensalidade` (3761) | CPF | 0 até 02/09/2026 | Nunca foi rodada dentro da ferramenta até então — parser novo, construído via o checklist da seção 4.1. Só mensalidade, regra de custeio padrão (sem "Regra empresa"), confirmado com o usuário. Extração por posição de coluna (`extract_words()` bucketizado por x0), não regex de linha — nome sem largura fixa. **CPF sai sem zero(s) à esquerda quando o valor real começa com 0** (corrigido com `.zfill(11)` na extração — conferir se aparecer de novo numa operadora futura com CPF aparentemente "curto"). Validado rodando `extrai()` contra o arquivo real: 8 beneficiários, R$ 120,00, batendo com o total impresso — ainda não rodado contra uma planilha padrão real (nenhuma empresa cadastrada nesta operadora até agora) — ver `operadoras/metlife/odonto_mensalidade.py` |
**Ressalva sobre a Bradesco Dental (3759):** o `CLAUDE.md` registra que este parser foi escrito só a partir de texto colado numa conversa, nunca confirmado contra o arquivo real. O banco, porém, já tem uma importação **concluída** pra esse operador (empresa 1684), ou seja, alguém rodou um arquivo real depois daquela ressalva ser escrita. "Concluída" só significa que o pipeline processou sem erro e o CSV foi gerado, **não** que os valores foram de fato conferidos linha a linha contra a fatura. Antes de remover a ressalva do `CLAUDE.md`, confirmar com o usuário se essa conferência manual aconteceu.
**AMIL: primeiro teste real (26/08/2026) achou um bug de parsing, já corrigido.** O parser nunca tinha sido rodado contra um arquivo de verdade — no primeiro teste em produção (empresa 1751, contrato 2831804000), todo arquivo AMIL dava "Nenhum beneficiário foi encontrado" porque o regex exigia espaço entre a coluna do plano e a coluna "Tp.", mas nesse relatório real as duas vêm coladas sem espaço nenhum. Corrigido (ver `portal_api/planos_saude/CLAUDE.md`, seção "Amil Odonto (3758)") e validado rodando `extrai()` de ponta a ponta: 161 beneficiários, R$ 1.630,93, batendo com os totais do próprio relatório. Continua valendo o cuidado geral: essa foi a primeira empresa/arquivo real confirmado, então tratar qualquer resultado da AMIL como "conferir contra a fatura" até mais empresas passarem pela ferramenta. (Este parser aparecia documentado aqui e no `CLAUDE.md` com o código "898" desde a primeira versão de cada arquivo — nunca foi esse o valor em `pipeline.OPERADORAS`, sempre `3758`; corrigido em 28/08/2026, quando a Amil ganhou um segundo cadastro de verdade sob o código 898, ver linha da tabela acima.)
**Unimed Saúde (5060): novo arquivo real (empresa 1970, 27/08/2026) achou um bug de parsing na coparticipação em PDF, já corrigido.** Mesmo padrão da AMIL acima — layout com uma coluna colada sem espaço que o regex não previa (aqui, o código de "Tipo Serviço" colado ao final do nome do Prestador, ex. "...FABRICCON", "...GUSTAVEXA"), dando "Nenhum beneficiário foi encontrado" pra qualquer arquivo de coparticipação com esse estilo de coluna. Corrigido junto com dois valores novos descobertos no mesmo arquivo ("OUTROS DEP" como grau de dependência, "CIR" como tipo de serviço) — ver `portal_api/planos_saude/CLAUDE.md`, seção "Parser da Unimed Saúde", item 8. Validado batendo exatamente com "Total da Familia" impresso no relatório (R$431,02).
**Nota sobre o `codigo_operadora`**: hoje esse código vem sempre do cadastro de operadora do **Questor** (única integração existente — usado tanto pro label de exibição quanto pra filtrar a consulta SQL da planilha padrão, ver skill `importacao-questor-plano-saude`). Se o Contabit vier a ter um cadastro de operadora próprio e divergente, isso pode precisar de um campo adicional por sistema — a confirmar quando essa frente for aberta, não assumir que o mesmo código serve pros dois.
## 2. Regras de negócio que decidem o valor de cada lançamento
A ferramenta é genérica multi-empresa/multi-operadora. O pipeline, os parsers por operadora e as regras gerais (nunca aproximar nome automaticamente, valor negativo nunca vai pro lançamento final, um mesmo beneficiário pode aparecer em várias linhas/rubricas e precisa ser somado) estão descritos em detalhe no `CLAUDE.md`, não repetir aqui.
Mecanismos que substituem uma decisão manual repetida por uma configuração reaproveitável:
- **Regra de custeio configurável por empresa+operadora**: quem paga o quê (empresa/empregado/regra específica) é decidido uma vez e reaproveitado todo mês, em vez de decidir de novo a cada competência. Hoje isso é feito via "Cadastro de Regras" (`RegraCusteioPlanoSaude`) — documentado na skill `importacao-questor-plano-saude`, porque nasce ligado ao cadastro de empresa/operadora do Questor (`codigo_empresa`/`codigo_operadora` resolvidos contra o banco de lá).
- **"Regra empresa"** (`portal_api/planos_saude/regras_empresa.py`): cobre custeios negociados que não cabem no desenho padrão "por tipo de lançamento × titular/dependente" — calculados por família inteira ou por critério fixo. Dois exemplos já cadastrados: um teto de R$ 661,61/família na Unimed (`unimed_1778_tecnomyl`, prioridade dependente primeiro e titular absorve o residual) e um critério fixo na Ottimizza/SulAmérica (`sulamerica_5775_ottimizza`). Ver `CLAUDE.md` pra lista completa e como cadastrar uma regra nova. Independente do sistema de destino — a regra decide o valor, não o formato de saída.
- **Vínculos de nome salvos (DE/PARA)** (`VinculoNomeOperadora`): confirmar manualmente uma vez ("Vincular pessoa") que dois nomes divergentes são a mesma pessoa, e o sistema reaplica sozinho nas competências seguintes — nunca aproximação automática, sempre confirmação humana explícita uma vez por divergência.
## 3. Gaps conhecidos do pipeline genérico (por operadora)
As regras abaixo foram identificadas comparando o pipeline genérico com o processo manual mais detalhado já visto até hoje (ver seção 5) — são gaps de **operadora/leiaute de custeio**, não peculiaridade de uma empresa só: qualquer empresa que negocie condição parecida com AMIL ou Bradesco pode ser afetada.
1. **AMIL, tipo "A" (agregado) vs. "D" (dependente direto):** o parser genérico (`operadoras/amil/odonto_mensalidade.py`) extrai o tipo (`T`/`D`/`A`), mas o resto do pipeline trata `D` e `A` como o mesmo "dependente" pra efeito de custeio (`matcher._regra_para_pessoa`, ver `CLAUDE.md`). Em pelo menos um contrato real já visto, a regra negociada exige que **todo tipo A seja 100% descontado do empregado, independente da regra configurada pra dependente**. Cadastrar uma empresa com beneficiários tipo A na AMIL sem confirmar essa condição arrisca custear um agregado pela empresa por engano.
2. **Bradesco, dependente sem linha na planilha padrão:** já apareceu um caso em que a regra negociada era acumular o valor desse dependente na linha do **titular** (não é regra geral do leiaute, é específica daquele contrato). O `matcher.py` genérico, na ausência dessa regra, trata esse caso como "sem cadastro", indo pra auditoria — uma saída **segura** (não lança valor errado, só some do lançamento final até alguém resolver), mas pode não ser o resultado esperado pelo cliente.
3. **Sistema Contabit** (formato alternativo de rubricas 338/200/201, usado por processos manuais antigos): fora do escopo da ferramenta hoje, que só sabe estruturar pro Questor (skill `importacao-questor-plano-saude`). Quando essa frente for aberta, provavelmente vai exigir um `LinhaSistema`/casamento próprios também — o formato de "planilha padrão" contra o qual tudo casa hoje já é moldado no leiaute do Questor (`NOMEFUNC`/`CPFFUNC`/`CODIGOOUTEMP`...), não é só a geração do arquivo final que muda entre sistemas.
Nenhum desses é motivo pra não usar a ferramenta — são pontos que quem for cadastrar uma empresa com essas operadoras/condições precisa confirmar antes (ou verificar que não se aplicam, ex.: se a empresa não tiver nenhum beneficiário tipo A). Até confirmar, mais seguro conferir manualmente o resultado da tela pra essas condições antes de considerar definitivo.
## 4. Como adicionar/ajustar um parser de operadora
Ver `CLAUDE.md` (tabela de operadoras registradas, `OperadoraParser.extrai()`, `pipeline.OPERADORAS`) para o mecanismo técnico.
### 4.1 Checklist — se a pessoa só anexar o arquivo-modelo
Objetivo: perguntar tudo que falta **antes** de escrever código, pra não precisar ajustar o parser várias vezes por falta de informação de negócio (que não tem como vir do arquivo sozinho). Nem toda pergunta se beneficia do mesmo momento — perguntar o que o arquivo nunca vai responder **antes** de abrir qualquer coisa; deixar a inspeção responder sozinha o que é auto-determinável; e só formular a pergunta de negócio mais específica **depois** de ver a estrutura real (evita uma pergunta genérica demais, tipo "existe regra especial?" em vez de "como tratar a categoria X que apareceu na tabela?").
**1. Perguntar antes de abrir o arquivo** (nunca vem do conteúdo, então inspecionar primeiro só atrasa):
1. Nome comercial da operadora + o código de cadastro dela no sistema de destino (`codigo_operadora`) — sem isso não dá pra registrar em `pipeline.OPERADORAS` nem montar o label de exibição. Hoje o destino é sempre o Questor (ver nota na seção 1.1); confirmado em duas operadoras diferentes (Dental Uni, Humana) que esse código nunca aparece no arquivo em si, então não há razão pra esperar a inspeção pra perguntar.
2. Qual empresa/código de cliente esse arquivo representa — usado na trava de conferência que já existe (`ImportacaoPlanoSaudeViewSet.create()`). Mesmo quando o nome da pasta/arquivo já sugere um código (ex.: "503 - Dental Uni", "1972 - Humana"), **confirmar em vez de assumir** — é um palpite vindo do nome do arquivo, não do conteúdo.
**2. Extrair e inspecionar** (sempre com o arquivo real — nunca a partir de texto colado numa conversa, ver [[feedback_pdf_parser_precisa_arquivo_real]]) — isto é autodeterminado, não precisa perguntar:
3. Abrir com a lib certa pro formato — `pdfplumber` pra PDF com texto selecionável; se `page.chars`/`extract_text()` vier vazio, é OCR (`docling`), não `pdfplumber` (ver nota em `operadoras/bradesco/saude.py`); `openpyxl` pra `.xlsx`; `csv` com fallback de encoding pra `.csv` acentuado. Rodar de fato contra o arquivo, não confiar em inspeção visual. **Cuidado com espaçamento entre palavras**: nem sempre `extract_text()`/`extract_text(layout=True)` preservam os espaços reais do PDF (já visto na Unimed Cascavel, onde só `extract_words(x_tolerance=1)` resolveu) — se as linhas saírem com palavras coladas, inspecionar `page.chars` direto pra confirmar o espaçamento real antes de desenhar qualquer regex.
4. Olhar o texto/linhas extraídas com `repr()`, não a versão "bonita" — indentação, colchetes, colunas coladas sem espaço e quebra de nome em duas linhas só aparecem assim. A partir do texto real, identificar: **se o arquivo traz CPF de cada beneficiário ou só nome** (decide `chave_casamento` — CPF é sempre preferível quando existe; isso se responde lendo o arquivo, nunca perguntando à pessoa); como titular e dependente se distinguem (rótulo? indentação? um campo "total família" só preenchido num dos dois? uma coluna de tipo já em texto explícito?); formato do valor monetário (vírgula BR ou ponto americano); se algum nome quebra em mais de uma linha física ou é truncado por largura de coluna.
**3. Confirmar depois de ver a estrutura real** (a inspeção já deu contexto suficiente pra fazer a pergunta certa, não uma genérica):
5. Quais tipos de lançamento vêm nesse arquivo — a inspeção já mostra quais tabelas existem (só mensalidade? só coparticipação? os dois juntos no mesmo arquivo, como a Humana? ou em arquivos separados por tipo, ver "Múltiplos arquivos de operadora" no `CLAUDE.md`) — a pergunta que falta responder é se essa composição **é sempre assim** todo mês, ou se pode variar (ex.: Unimed Cascavel, onde a coparticipação pode vir embutida OU separada dependendo do mês).
6. Existe alguma regra de custeio negociada com o cliente além do padrão "empresa/empregado por tipo × titular/dependente" (teto por família, percentual fixo, um tipo sempre 100% descontado)? Usar o que a inspeção revelou pra perguntar de forma específica — ex.: se apareceu uma categoria "Agregado" na totalização (como no arquivo da Humana), perguntar diretamente como ela deve ser tratada, em vez de só "existe regra especial?" genérico. Se houver regra negociada, pedir **um exemplo numérico já calculado à mão** (ex.: "família com mensalidade R$X, empresa cobre R$Y, empregado paga R$Z") — é contra esse exemplo que a implementação é validada no passo 8 abaixo, não só "parece certo".
**Escrever e validar:**
7. Implementar `OperadoraParser.extrai()` em `operadoras/<nome>/<arquivo>.py` e registrar em `pipeline.OPERADORAS` com o `codigo_operadora` do passo 1.
8. Rodar `extrai()` de ponta a ponta contra o arquivo real e comparar total em R$ + contagem de beneficiários com os totais que o próprio relatório já imprime (nunca só "não deu erro" ou "contagem de linhas parece certa").
9. Testar pela tela (`validar-arquivo`) e, se possível, uma importação completa contra a planilha padrão real da empresa antes de considerar pronto (planilha padrão hoje é sempre a do Questor — ver skill `importacao-questor-plano-saude`).
### 4.2 Regras de negócio que nunca devem ser reinterpretadas
Válidas pra qualquer operadora nova, sem exceção — e independentes do sistema contábil de destino:
- Nome divergente nunca é resolvido por aproximação automática (fuzzy match). Sempre confirmação humana explícita, mesmo que pareça óbvio.
- Nenhum valor negativo entra no lançamento final, vira auditoria, nunca é zerado/truncado silenciosamente.
- Um mesmo beneficiário pode gerar várias linhas/rubricas no arquivo da operadora (mensalidade mais retroativo, por exemplo). Sempre somar por indivíduo antes de decidir o valor final, nunca tratar cada linha isoladamente.
## 5. Histórico (contexto, não necessário no dia a dia)
A ferramenta nasceu documentando o processo manual mensal de uma empresa específica — TECNOMYL (código Questor `1778`, operadoras AMIL/Unimed/Bradesco) —, feito à mão com scripts Python ad-hoc (protótipo em `projects/project/`) seguindo um runbook vivo por competência. Hoje TECNOMYL é só mais uma empresa entre as cadastradas em "Cadastro de Regras" (skill `importacao-questor-plano-saude`), sem tratamento especial no código — esse histórico só importa pra quem for:
- **Entender a origem das regras genéricas do pipeline** (nome nunca resolvido por aproximação, valor negativo sempre vira auditoria, somar por indivíduo): vieram de decisões validadas com esse cliente no processo manual, documentadas em detalhe no runbook dele.
- **Cadastrar TECNOMYL de vez na ferramenta**: ela ainda não tem `RegraCusteioPlanoSaude` cadastrada hoje. Existe um único registro histórico de importação (Unimed, `regra_empresa=unimed_1778_tecnomyl` já setado) parado em "revisão", de antes do "Cadastro de Regras" existir como tela separada. Antes de cadastrar, ver os gaps da seção 3 (a maioria foi identificada a partir do processo manual dela) e o aviso abaixo sobre vínculos de nome.
- **Vínculos de nome**: as cerca de 30 equivalências de nome divergente já conhecidas do runbook da TECNOMYL (nome na operadora ≠ nome no Questor) não foram pré-carregadas como `VinculoNomeOperadora` — a primeira competência dela rodada pela tela vai gerar auditoria pra cada uma de novo, até serem confirmadas uma vez cada (ver seção 2).
- **Localizar o runbook**: fora deste repositório, só na máquina de quem processa hoje — `Plano de Saúde - TM\MM-AAAA\` (por competência: `Memoria_Importacao_Questor.md`, CSVs gerados, `Faltantes_Questor_MM_AAAA.xlsx`, `Auditoria_Importacao_Questor_MM_AAAA.xlsx`). **Limitação real**: nada neste SKILL.md substitui esse runbook, só resume o que ele documenta de mais estável. Se for necessário que outra pessoa continue sem acesso a essa máquina, os arquivos precisam ser trazidos pra algum lugar compartilhado (git ou outro) — decisão que envolve dados reais de funcionários, não fazer sem o usuário confirmar onde/como.