Correção validação 2 arquivos Unimed PR

This commit is contained in:
Gabriel 2026-08-27 15:03:23 -03:00
parent c2b78f6667
commit f826d73d2a
4 changed files with 50 additions and 2 deletions

View File

@ -35,6 +35,8 @@ A ferramenta atende hoje várias empresas/operadoras reais, nenhuma com tratamen
**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 (898)") 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.
**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

View File

@ -297,3 +297,11 @@ Pedido do usuário: o escritório trabalha com mais de um sistema contábil (Que
- Uma terceira skill pro Contabit ainda não existe — a skill geral só documenta o gap e avisa pra não inventar leiaute Contabit sem confirmação, e já registra uma ressalva: o "casamento" hoje (`matcher.py`/`LinhaSistema`) usa um formato de registro moldado no leiaute do Questor, então o Contabit provavelmente vai exigir mais que só uma geração de arquivo diferente — um `LinhaSistema`/matcher próprios também, quando essa frente for aberta.
- Referências cruzadas atualizadas em `portal_api/planos_saude/CLAUDE.md` e `README.md` (skill única → as duas); aproveitado pra corrigir a contagem de parsers no `README.md`, que ainda dizia "9" (desatualizada desde as rodadas da Humana/Unimed Cascavel).
- **Redirecionamento reforçado, mesmo dia**: usuário perguntou o que aconteceria se a skill `importacao-questor-plano-saude` fosse chamada direto pra cadastrar uma operadora nova — o aviso original (uma frase solta na seção 0, tipo "ver skill X, sempre o ponto de partida") dependia de inferência, não era uma trava amarrada ao cenário específico. Trocado pelos dois lados por um "pare aqui" explícito logo no topo: a skill geral (`importacao-plano-saude`) avisa pra quem for mexer em Cadastro de Regras/planilha padrão/leiaute/geração de arquivo ir pra skill do Questor; a skill do Questor avisa pra quem for adicionar/ajustar parser de operadora ir pra skill geral (seção 4) **antes** de escrever qualquer código ou fazer qualquer pergunta ao usuário. Aproveitado pra corrigir uma referência cruzada errada (`importacao-plano-saude` apontava "gap 4" quando o gap do Contabit é o item 3 da seção 3).
### Bug real — Unimed Saúde (5060, PDF de coparticipação): código de "Tipo Serviço" colado ao Prestador não reconhecido
Usuário reportou (empresa Questor 1970, "Rede Brasil de Mídia OOH LTDA", competência 08/2026) "Nenhum beneficiário foi encontrado neste arquivo" ao anexar o arquivo de coparticipação, enquanto a mensalidade da mesma competência processou normalmente (3 lançamentos). Investigado rodando `pdfplumber`/`extrai()` de ponta a ponta contra o arquivo real: a detecção de layout e o casamento da linha de pessoa (`_PESSOA_COPARTICIPACAO_RE`) funcionavam normalmente, mas nenhuma linha de item de serviço casava com `_ITEM_COPARTICIPACAO_RE` — o resultado final era sempre zero indivíduos.
Causa: `_ITEM_COPARTICIPACAO_RE` exigia fronteira de palavra (`\b`) dos dois lados do código de "Tipo Serviço" (CON/EXA/HOS/CLI/ODO/MED). Neste arquivo, esse código vem colado sem espaço nenhum ao final do nome do Prestador (ex.: "...MARCELO FABRICCON 10101012...", "...LUCIANO GUSTAVEXA 40316572..." — mesmo estilo de coluna colada já visto no "Beneficiario" desde a rodada 83, só que numa coluna diferente), então a fronteira à esquerda nunca era satisfeita. O mesmo arquivo revelou, de quebra, mais dois valores não previstos: o grau de dependência "OUTROS DEP" (ausente de `_GRAUS_DEPENDENCIA`) e o código de tipo de serviço "CIR" (cirurgia — "Implante de dispositivo").
Corrigido em `operadoras/unimed/saude.py`: `_ITEM_COPARTICIPACAO_RE` perdeu a fronteira de palavra à esquerda (mantida só à direita, pra não casar um código no meio de outra palavra) e ganhou "CIR"; "OUTROS DEP" foi acrescentado a `_GRAUS_DEPENDENCIA` — sem esse segundo ajuste, mesmo com o regex do item corrigido, a coparticipação dessa dependente cairia por engano na pessoa anterior do bloco (mesmo bug do "COMPANHEIRO" truncado, ver acima). Validado rodando `extrai()` de ponta a ponta contra o arquivo real: 2 beneficiários (KARLA VANESSA R$247,74 + RAPHAELA SOUZ R$183,28), somando R$431,02 — bate exatamente com "Total da Familia: 431,02" impresso no relatório; reconfirmado, sem regressão, que a mensalidade da mesma competência continua extraindo os mesmos 3 beneficiários de antes.

View File

@ -107,6 +107,7 @@ Até uma rodada anterior, "Arquivo da operadora" (passo 2 de "Nova Importação"
- **`OperadoraParser` ganhou `chave_casamento_para_tipo(tipo_lancamento)`** (default: devolve `chave_casamento`, mesmo valor de sempre — método novo, backward-compatible pra todo outro parser) porque a coparticipação analítica da Unimed em PDF precisou de uma estratégia de casamento **diferente da mensalidade dentro da mesma operadora**: o "Beneficiario" desse relatório vem colado sem espaço com o nome e o grau de dependência (ex.: "0975.0167003824292ANDREIA STORMTITULAR") e o **nome sai truncado em ~13 caracteres** por largura de coluna ("ANDREIA STORMOSKI LARA" → "ANDREIA STORM") — inviabilizando casamento por nome. Como esse relatório traz CPF completo e confiável, `UnimedSaude` usa `"cpf"` só pra `tipo_lancamento="coparticipacao"` quando a origem foi esse PDF (rastreado numa flag de instância, `self._veio_de_pdf_coparticipacao`, setada em `extrai()`); mensalidade (sem CPF em nenhum dos dois formatos) continua em `"nome"`. `pipeline.processa_importacao` chama `chave_casamento_para_tipo(tipo_lancamento)` em vez do atributo fixo.
- **Validado contra os dois arquivos reais** (não só texto colado numa conversa — o texto que sai de um PDF colado no chat **não é** o que `pdfplumber.extract_text()` de fato produz, então não serve pra desenhar regex com confiança; só o arquivo real confirma). Bate exatamente com "Total da Familia"/"Total da Sequencia" impresso no próprio relatório (1.372,08 de coparticipação, 6.061,74 de mensalidade) e com o casamento por CPF contra a planilha padrão real da empresa — toda família presente na planilha bateu centavo a centavo; a família ausente da planilha de teste foi corretamente pra auditoria, não ignorada silenciosamente.
- **Bug real corrigido (competência 09/2026, empresa Questor 604)**: `_GRAUS_DEPENDENCIA` não previa "COMPANHEIRO"/"COMPANHEIRA" — a coluna "Grau Dep." tem largura fixa de 10 caracteres, então esse grau (11 caracteres) sai truncado no relatório real como "COMPANHEIR". Sem essa entrada, a linha desse dependente não casava com `_PESSOA_COPARTICIPACAO_RE`, e o item de serviço dele (que ainda batia em `_ITEM_COPARTICIPACAO_RE`) era somado por engano no `pessoa_atual` anterior — na prática, no titular da mesma família (primeiro caso real de família com coparticipação em titular **e** dependente ao mesmo tempo; até então só se via titular sozinho). Corrigido acrescentando "COMPANHEIRO"/"COMPANHEIRA"/"COMPANHEIR" (a forma truncada, a que de fato aparece) a `_GRAUS_DEPENDENCIA`; validado rodando `extrai()` de ponta a ponta contra o arquivo real (família R$144,35 → titular R$134,33 + dependente R$10,02, batendo com "Total da Familia" impresso, e total geral do arquivo R$606,61 batendo com a soma dos "Total da Familia" das 3 famílias do documento). A importação já existente no banco (id 88, competência 09/2026) tinha sido corrigida manualmente na tela de Revisão antes deste fix (`ImportacaoPlanoSaudeAlteracao` ids 29/30) — não precisou de correção retroativa, só as importações futuras dependiam deste ajuste no parser.
- **Bug real corrigido (empresa Questor 1970, "Rede Brasil de Mídia OOH", competência 08/2026)**: `_ITEM_COPARTICIPACAO_RE` exigia fronteira de palavra (`\b`) dos dois lados do código de "Tipo Serviço" (CON/EXA/HOS/CLI/ODO/MED). Neste arquivo real, esse código vem **colado sem espaço** ao final do nome do Prestador (ex.: "...MARCELO FABRICCON 10101012...", "...LUCIANO GUSTAVEXA 40316572..." — mesmo estilo de coluna colada já visto no "Beneficiario" da nota acima, só que aqui na coluna de tipo de serviço), então nenhuma linha de item deste arquivo casava — resultado final era **zero beneficiários** ("Nenhum beneficiário foi encontrado neste arquivo"), mesmo com a detecção do layout e o casamento de linha de pessoa funcionando normalmente. O mesmo arquivo trouxe de quebra um grau de dependência ("OUTROS DEP") e um código de tipo de serviço ("CIR", cirurgia) não previstos. Corrigido: `_ITEM_COPARTICIPACAO_RE` perdeu a fronteira de palavra à esquerda (mantida só à direita) e ganhou "CIR"; "OUTROS DEP" foi acrescentado a `_GRAUS_DEPENDENCIA` (sem isso, mesmo corrigindo o regex do item, a coparticipação dessa dependente cairia por engano na pessoa anterior do bloco, mesmo bug da nota acima). Validado rodando `extrai()` de ponta a ponta contra o arquivo real: 2 beneficiários (KARLA VANESSA R$247,74 + RAPHAELA SOUZ R$183,28), somando R$431,02, batendo exatamente com "Total da Familia" impresso; o arquivo de mensalidade da mesma competência (regex própria, não afetada) continuou extraindo os mesmos 3 beneficiários de sempre.
## Planilha padrão via Questor (SQL)

View File

@ -114,6 +114,33 @@ Particularidades identificadas no PDF de coparticipação analítico (caso 2):
separados (R$ 134,33 e R$ 10,02, somando os R$ 144,35 de "Total da
Familia" impresso no relatório) e o total geral do arquivo (R$ 606,61)
bateu com a soma dos 3 "Total da Familia" do documento.
8. **Bug real corrigido (cliente Rede Brasil de Mídia OOH, competência
08/2026)**: neste relatório, a coluna "Tipo Serviço" (código de 3 letras
— CON, EXA, HOS...) vem **colada sem espaço nenhum** ao final do nome do
Prestador (ex.: "...MARCELO FABRICCON 10101012...", "...LUCIANO
GUSTAVEXA 40316572...") — mesmo estilo de coluna colada já visto em
`_PESSOA_COPARTICIPACAO_RE`, só que aqui na coluna de tipo de serviço,
que não tinha esse tratamento. Como `_ITEM_COPARTICIPACAO_RE` exigia
fronteira de palavra (`\b`) dos dois lados do código, nenhuma linha de
item deste arquivo casava — o resultado final era **zero beneficiários**
("Nenhum beneficiário foi encontrado neste arquivo"), mesmo com a
detecção de layout e o casamento das linhas de pessoa funcionando
normalmente. O mesmo arquivo também trouxe um grau de dependência não
previsto ("OUTROS DEP") e um código de tipo de serviço não previsto
("CIR", cirurgia — ex. "Implante de dispositivo"). Corrigido: (a)
`_ITEM_COPARTICIPACAO_RE` perdeu a fronteira de palavra à esquerda
(mantida só à direita, pra não casar um código no meio de outra
palavra), acrescentando "CIR" à lista; (b) "OUTROS DEP" acrescentado a
`_GRAUS_DEPENDENCIA`. Sem o ajuste em (b), mesmo corrigindo (a) os itens
desse dependente seriam somados por engano na pessoa anterior do bloco
(mesmo bug do item 7 acima). Validado rodando `extrai()` de ponta a
ponta contra o arquivo real da empresa 1970: 2 beneficiários (KARLA
VANESSA R$ 247,74 + RAPHAELA SOUZ R$ 183,28), somando R$ 431,02 — bate
exatamente com "Total da Familia: 431,02" impresso no relatório. O
arquivo de mensalidade da mesma competência não foi afetado por este bug
(usa `_LINHA_MENSALIDADE_RE`, uma regex própria) e continuou extraindo
os mesmos 3 beneficiários de antes.
"""
import csv
import re
@ -137,7 +164,7 @@ _MARCADORES_COPARTICIPACAO = ("SERVIÇOS PRESTADOS", "SERVICOS PRESTADOS", "ANAL
# caso outro relatório real não trunque.
_GRAUS_DEPENDENCIA = (
"TITULAR", "CONJUGE", "CÔNJUGE", r"FILHO\(A\)", r"PAI/M[ÃA]E", "AGREGADO",
"COMPANHEIRO", "COMPANHEIRA", "COMPANHEIR",
"COMPANHEIRO", "COMPANHEIRA", "COMPANHEIR", "OUTROS DEP",
)
_LINHA_MENSALIDADE_RE = re.compile(
@ -157,7 +184,17 @@ _PESSOA_COPARTICIPACAO_RE = re.compile(
# o casamento deste relatório usa CPF, não nome (ver
# `chave_casamento_para_tipo`).
)
_ITEM_COPARTICIPACAO_RE = re.compile(r"\b(?:EXA|CON|HOS|CLI|ODO|MED)\b")
_ITEM_COPARTICIPACAO_RE = re.compile(r"(?:EXA|CON|HOS|CLI|ODO|MED|CIR)\b")
# Sem fronteira de palavra à esquerda de propósito: confirmado contra um
# segundo arquivo real (cliente Rede Brasil de Mídia OOH, competência
# 08/2026) que esse código de "Tipo Serviço" pode vir colado sem nenhum
# espaço ao final do nome do Prestador (mesmo estilo de coluna colada já
# visto em `_PESSOA_COPARTICIPACAO_RE` — ex.: "...MARCELO FABRICCON
# 10101012...", "...LUCIANO GUSTAVEXA 40316572...") — com `\b` dos dois
# lados, nenhuma linha desse arquivo casava, e o resultado final era zero
# beneficiários ("Nenhum beneficiário foi encontrado"). "CIR" (cirurgia,
# ex. "Implante de dispositivo") também foi acrescentado à lista, código
# visto pela primeira vez neste mesmo arquivo.
# Linhas "Pct:MED"/"Pct:HOS"/"Pct:MAT" (detalhamento do valor de um item HOS
# em Medicamento/Material/Soma de Outras Taxas, cuja soma já está no valor
# do item principal — confirmado contra o arquivo real batendo com "Total