From 41512f793fb7491881804cd8994d066ff346a97f Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 24 Aug 2026 17:38:25 -0300 Subject: [PATCH] =?UTF-8?q?Parametriza=C3=A7=C3=A3o=20de=20novas=20operado?= =?UTF-8?q?ras=20de=20plano=20de=20sa=C3=BAde?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 8 +- .../operadora/SULAMERICA_AGOSTO_2026.pdf | Bin 0 -> 60672 bytes .../planos_saude/operadora/unimed_vitoria.PDF | Bin 0 -> 7098 bytes .../planilha_padrao/questor_792_2026-08.csv | 2 + .../questor_792_2026-08_KQCYKPt.csv | 14 + plano.md | 18 ++ .../operadoras/sulamerica/__init__.py | 0 .../sulamerica/odonto_mensalidade.py | 155 +++++++++++ .../operadoras/unimed_vitoria/__init__.py | 0 .../operadoras/unimed_vitoria/saude.py | 261 ++++++++++++++++++ portal_api/planos_saude/pipeline.py | 12 + 11 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 media/planos_saude/operadora/SULAMERICA_AGOSTO_2026.pdf create mode 100644 media/planos_saude/operadora/unimed_vitoria.PDF create mode 100644 media/planos_saude/planilha_padrao/questor_792_2026-08.csv create mode 100644 media/planos_saude/planilha_padrao/questor_792_2026-08_KQCYKPt.csv create mode 100644 portal_api/planos_saude/operadoras/sulamerica/__init__.py create mode 100644 portal_api/planos_saude/operadoras/sulamerica/odonto_mensalidade.py create mode 100644 portal_api/planos_saude/operadoras/unimed_vitoria/__init__.py create mode 100644 portal_api/planos_saude/operadoras/unimed_vitoria/saude.py diff --git a/CLAUDE.md b/CLAUDE.md index 8f702dc..cabd2c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -562,7 +562,9 @@ portal_api/planos_saude/ ├── dental_uni/odonto_mensalidade.py Dental Uni Odonto (PDF via pdfplumber, só mensalidade, casamento por nome) ├── unimed_oeste_pr/saude.py Unimed Oeste do Paraná (PDF via pdfplumber, mensalidade+coparticipação por texto da descrição, casamento por nome) ├── bradesco/saude.py Bradesco Saúde (PDF **sem texto selecionável** — OCR via `docling`, mensalidade+coparticipação, casamento por nome) - └── bradesco/odonto_mensalidade.py Bradesco Dental/Bradesaude Odonto — 3759 (PDF via pdfplumber, mensalidade+coparticipação, casamento por nome, ver nota abaixo) + ├── bradesco/odonto_mensalidade.py Bradesco Dental/Bradesaude Odonto — 3759 (PDF via pdfplumber, mensalidade+coparticipação, casamento por nome, ver nota abaixo) + ├── unimed_vitoria/saude.py Unimed Vitória — 4750 (2 PDFs sempre separados, mensalidade+coparticipação, casamento por nome, ver nota abaixo) + └── sulamerica/odonto_mensalidade.py SulAmérica Odonto — 4726 (PDF via pdfplumber, só mensalidade, casamento por CPF, ver nota abaixo) ``` Pra adicionar uma operadora nova: criar `operadoras//.py` implementando `OperadoraParser.extrai()` (devolve `(List[Individuo], List[ItemAuditoria])`) e registrar em `pipeline.OPERADORAS`. **Antes de escrever o parser, ler `projects/importacao-planos-saude.skill`** — documenta decisões de negócio já validadas com o cliente (ex.: nome divergente nunca é resolvido por aproximação, valor final negativo vai pra auditoria, um mesmo beneficiário pode aparecer em várias linhas/rubricas e precisa ser somado) que não devem ser reinterpretadas sem confirmar de novo. @@ -571,6 +573,10 @@ Pra adicionar uma operadora nova: criar `operadoras//.py` impleme **Bradesco Dental / "Bradesaude" Odonto (3759)** — mesmo código de operadora (`CODIGOOUTEMP`) que já existia como "ODONTOPREV S.A." na planilha padrão; o boleto da própria operadora avisa que é o mesmo plano, "antes cobrado como Odontoprev e agora identificado temporariamente como Bradsaude". PDF "SPG/Grupos Especiais - Bradesco Dental - Fatura Técnica" — página 1 é sempre o boleto (sem beneficiário nenhum), a tabela de beneficiários vem a partir da página 2, páginas finais são só o texto legal "MENSAGENS". Titular/dependente vem da coluna "Certif." (`/00` = titular, `/01`, `/02`... = dependente), casamento por nome (sem CPF no arquivo) — mesmo desenho da Bradesco Saúde. Particularidade própria: um mesmo beneficiário pode gerar várias linhas de lançamento por movimentação retroativa (inclusão/cancelamento com efeito em meses anteriores, códigos CM/CR/IR/IM), cada uma com seu próprio Mês/Ano e Valor — todas somadas por indivíduo, igual à regra geral de "somar todas as rubricas do mesmo indivíduo". **Ressalva importante**: ao contrário dos demais parsers deste pacote, este foi escrito só a partir do texto de um PDF colado numa conversa (o arquivo nunca chegou a ficar disponível em disco pra rodar `pdfplumber`/`docling` de verdade) — a extração via `pdfplumber` foi validada batendo a soma dos valores e a contagem de lançamentos contra o resumo do próprio boleto (37 lançamentos, R$ 949,05), mas **ainda precisa ser confirmada rodando o parser contra o arquivo real** (botão "Selecionar arquivo" da tela de Nova Importação já faz isso antes de qualquer coisa ser persistida) — se a extração vier vazia, é sinal de que este PDF também precisa de OCR via `docling`, como a Bradesco Saúde. +**Unimed Vitória (4750)** — sempre 2 PDFs separados (nunca detecta "tipo de documento" escolhido pelo usuário, detecção automática pelo conteúdo, mesmo espírito da Unimed do Paraná): "Demonstrativo Analítico de Pré Pagamento" (mensalidade) e "Extrato de Co-Participação" (coparticipação), nenhum dos dois com CPF (casamento por nome). Validado contra os dois arquivos reais do cliente Weitnauer Brasil (`pdfplumber` rodou de fato, batendo com os valores impressos no próprio relatório — R$ 340,74 de mensalidade, R$ 55,57 de coparticipação — e casando certo contra a planilha padrão real da empresa 792). Particularidade de extração: a coluna de nome do relatório de mensalidade quebra em duas linhas físicas quando o nome é longo, misturada com a linha de dados num `top` próximo mas não igual — nem `extract_text()` nem `extract_text(layout=True)` resolvem isso sem ambiguidade, então o parser reconstrói as linhas a partir de `extract_words(extra_attrs=["fontname","size"])` agrupadas por posição vertical, e usa sempre o cabeçalho em negrito (nome completo, sem quebra) como fonte do nome, nunca a linha de dados quebrada; a coparticipação não tem espaço literal nenhum entre colunas (todo espaçamento é por posição, não por caractere), o que também exige `extract_words()` em vez de concatenar `page.chars` direto. **Titular/dependente é uma suposição não validada**: nenhum dos dois relatórios traz um marcador textual "Titular"/"Dependente" explícito, e os dois arquivos de exemplo só têm titular, sem nenhum dependente — a classificação usada (sequência "00" da carteirinha "..-" = titular, qualquer outra = dependente) é a convenção nacional já conhecida de outras Unimeds, mas nunca confirmada contra um arquivo real desta operadora com dependente; testar com um caso real antes de confiar nela — a validação prévia ("Selecionar arquivo") não pega esse tipo de erro, já que a extração não falha, só classificaria errado. + +**SulAmérica Odonto (4726)** — um único PDF, sem coparticipação (relatório "Conferência de Faturamento PJ (Completo)" do sistema "IS Odonto" da própria operadora — é só uma cobrança fixa periódica por beneficiário, não há linha de serviço/atendimento nenhuma). Ao contrário das Unimeds, este relatório traz **CPF de todo mundo** (casamento por CPF, mais seguro) e um campo textual explícito de "Grau parentesco" (TITULAR/CONJUGE/OUTROS/DEP PERMANENTE/...) — nenhuma suposição sobre numeração de carteirinha foi necessária aqui. Validado rodando `pdfplumber` e o `pipeline.processa_importacao` completo contra o arquivo real (15 beneficiários, R$ 437,40 no total — bate exatamente com "Total R$ 437,40" impresso no relatório) e a planilha padrão real da empresa 792 (13 dos 15 beneficiários casaram certo por CPF; os 2 ausentes da planilha de teste foram corretamente para auditoria "CPF não encontrado", não ignorados). **Cada família tem um "totalizador" impresso ao final** (ex.: "R$ 87,48" somando os 3 beneficiários de uma família) — por pedido explícito do usuário, esse total **nunca é usado**: o lançamento é sempre feito pela coluna "Valor" de cada linha de beneficiário individual (R$ 29,16 no exemplo), a mesma lógica de "usar o valor por linha, ignorar o subtotal impresso" já aplicada à Unimed do Paraná/Vitória. Particularidade de extração: quando o nome de um beneficiário (ou da família, no cabeçalho) ultrapassa a largura da coluna, o próprio relatório **corta o texto sem reticências e sem terminar de completar a última palavra** (ex.: "DANIELE MARTINS FERREIRA DA SILVA" sai como "DANIELE MARTINS FERREIRA DA" + "SILV" cortado, perdendo o "A" final) — como o casamento é por CPF, isso nunca afeta a correção do lançamento (nome é só exibição), então o parser não tenta reconstruir o nome quebrado, só usa a primeira linha física de cada beneficiário (onde já estão código/CPF/data nascimento/grau/valor completos, nenhum desses quebra, só o nome às vezes). + **Diferença deliberada em relação ao pipeline original**: lá, o valor do mês sempre gravava na coluna `VALOR` (desconto do empregado), nunca em `VALOREMPRESA` — regra fixa. Aqui, o usuário escolhe na tela de nova importação, **por tipo de lançamento (mensalidade/coparticipação) e por tipo de beneficiário (titular/dependente)** — quatro combinações independentes, ex.: mensalidade do titular custeada pela empresa e mensalidade do dependente descontada do empregado —, uma de três regras de custeio: "Custeado pela empresa" (`{"modo": "empresa"}`), "Descontado do empregado" (`{"modo": "empregado"}`, o comportamento antigo — nomenclatura "empregado", não "funcionário", pra não confundir com `NOMEFUNC`/`CPFFUNC` do leiaute do Questor, que é outra coisa) ou "Regra específica" (`{"modo": "especifica", "limite_valor": float|None, "percentual": float|None}`). Na regra específica, `limite_valor` é um teto de quanto a empresa cobre (o excedente vira desconto do empregado) e `percentual` é a fração do valor do mês custeada pela empresa (o resto vira desconto) — o usuário pode preencher só um dos dois ou os dois juntos; quando os dois vêm preenchidos, prevalece o que resultar no **menor** valor custeado pela empresa (mais restritivo), decisão explícita do usuário. Essa divisão é calculada por `matcher._calcula_valores(valor_total, regra)` (chamada por `_aplica_regra_custeio`, que grava `valor_empresa`/`valor` **os dois juntos** a partir do mesmo `valor_total`) — note que `valor_empresa` é arredondado primeiro e `valor` é derivado como o complemento exato (`valor_total - valor_empresa`, também arredondado), nunca os dois arredondados de forma independente, senão a soma dos dois podia ficar 1 centavo a mais/menos que o valor original (ex.: 50% de 51,69 tem que fechar em 25,84 + 25,85 = 51,69, não 25,85 + 25,85). Qual das duas regras (titular ou dependente) usar em cada `Individuo`/`LinhaSistema` é resolvido por `matcher._regra_para_pessoa(regra_por_pessoa, tipo_pessoa)` — `tipo_pessoa` 'T' cai em "titular", 'D'/'A' caem em "dependente" (mesmo critério de "D e A tratados igual" já usado no resto do leiaute) — chamada nos dois pontos de aplicação de `_casa_por_cpf`/`_casa_por_nome` antes de `_aplica_regra_custeio`. ### Modelos (`portal_api/models.py`) diff --git a/media/planos_saude/operadora/SULAMERICA_AGOSTO_2026.pdf b/media/planos_saude/operadora/SULAMERICA_AGOSTO_2026.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7f1824398d2faf1084d69650650929c23be23ea9 GIT binary patch literal 60672 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

kj*VDdXw*t9rLN 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 literal 0 HcmV?d00001 diff --git a/media/planos_saude/operadora/unimed_vitoria.PDF b/media/planos_saude/operadora/unimed_vitoria.PDF new file mode 100644 index 0000000000000000000000000000000000000000..9e90bed228146251b172e13a771f681ef3edc1a1 GIT binary patch literal 7098 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 Bfv*4n literal 0 HcmV?d00001 diff --git a/media/planos_saude/planilha_padrao/questor_792_2026-08.csv b/media/planos_saude/planilha_padrao/questor_792_2026-08.csv new file mode 100644 index 0000000..d5d8224 --- /dev/null +++ b/media/planos_saude/planilha_padrao/questor_792_2026-08.csv @@ -0,0 +1,2 @@ +CODIGOEMPRESA;NOMEFUNC;CPFFUNC;CODIGOOUTEMP;DATAINICIAL;NOMEDEPENDENTE;CPFDEPENDENTE;VALOREMPRESA;VALOR;DESCRICAO +792;MARIA EDUARDA OLIVEIRA DO PRADO;194.093.377-39;4750;01/04/2026;;;0;0; diff --git a/media/planos_saude/planilha_padrao/questor_792_2026-08_KQCYKPt.csv b/media/planos_saude/planilha_padrao/questor_792_2026-08_KQCYKPt.csv new file mode 100644 index 0000000..72b09f5 --- /dev/null +++ b/media/planos_saude/planilha_padrao/questor_792_2026-08_KQCYKPt.csv @@ -0,0 +1,14 @@ +CODIGOEMPRESA;NOMEFUNC;CPFFUNC;CODIGOOUTEMP;DATAINICIAL;NOMEDEPENDENTE;CPFDEPENDENTE;VALOREMPRESA;VALOR;DESCRICAO +792;DANIELE MARTINS FERREIRA DA SILVA;075.104.176-94;4726;01/08/2025;SANDRA ALVES ANTUNES;065.792.858-50;0;0; +792;DANIELE MARTINS FERREIRA DA SILVA;075.104.176-94;4726;01/08/2025;;;0;0; +792;DANIELE MARTINS FERREIRA DA SILVA;075.104.176-94;4726;01/08/2025;YURI MARTINS SILVA;515.225.848-03;0;0; +792;ELAINE MARIANO LEITE FERREIRA;096.596.408-62;4726;01/08/2025;JOAO CARLOS MARTINS FERREIRA;041.160.848-75;0;0; +792;ELAINE MARIANO LEITE FERREIRA;096.596.408-62;4726;01/08/2025;;;0;0; +792;ELAINE MARIANO LEITE FERREIRA;096.596.408-62;4726;01/08/2025;ASSUNTA RODRIGUES LEITE;124.114.608-05;0;0; +792;ESTEFANI NATIVIDADE LUIZ;122.789.179-27;4726;01/08/2025;;;0;0; +792;ESTEFANI NATIVIDADE LUIZ;122.789.179-27;4726;01/08/2025;SILVANA FRANCISCA DA SILVA MEDEIROS;035.934.129-26;0;0; +792;ESTEFANI NATIVIDADE LUIZ;122.789.179-27;4726;01/08/2025;JACKSON FELISBERTO DA SILVA;076.396.959-10;0;0; +792;LORRANA MAYRA SANTOS SOBRAL;055.931.999-14;4726;01/08/2025;ANDRE CLEVERSON DAMBROS PEREIRA;071.116.879-22;0;0; +792;LORRANA MAYRA SANTOS SOBRAL;055.931.999-14;4726;01/08/2025;JOENISIA AZEVEDO DOS SANTOS;570.655.946-53;0;0; +792;LORRANA MAYRA SANTOS SOBRAL;055.931.999-14;4726;01/08/2025;;;0;0; +792;LORRANA MAYRA SANTOS SOBRAL;055.931.999-14;4726;01/08/2025;ARLINDO JOSE PEREIRA NETO;615.433.819-87;0;0; diff --git a/plano.md b/plano.md index b98b955..c154ed4 100644 --- a/plano.md +++ b/plano.md @@ -1109,6 +1109,24 @@ Usuário perguntou se a resolução manual de nome divergente ("Vincular pessoa" - Botão "Apagar vínculo" (aba Alterações, mesmo endpoint `reverter()`) zera o valor lançado na linha (redistribuindo a regra empresa da família, se houver) e apaga o `VinculoNomeOperadora` — a divergência volta a cair em auditoria nas próximas importações. - Testado via `APIRequestFactory` dentro de uma transação revertida (nada persistido nos dados reais): matcher.py aplicando/não aplicando o DE/PARA corretamente, `resolver()` criando o vínculo, `_carrega_vinculos_por_nome` encontrando-o, e `reverter()` zerando a linha + apagando o vínculo. +### 85. Nova operadora: Unimed Vitória (4750) + +Usuário forneceu os dois PDFs reais do cliente Weitnauer Brasil (empresa 792 na planilha padrão): "Demonstrativo Analítico de Pré Pagamento" (mensalidade) e "Extrato de Co-Participação" (coparticipação), sempre em arquivos separados — nenhum dos dois traz CPF, casamento por nome. + +- `operadoras/unimed_vitoria/saude.py` (`UnimedVitoriaSaude`), registrada em `pipeline.OPERADORAS["unimed_vitoria_saude"]`. Detecção automática do tipo pelo conteúdo (marcador "DEMONSTRATIVO" vs. "CO-PARTICIPA" na página 1), mesmo espírito da Unimed do Paraná. +- Particularidade de extração: no PDF de mensalidade, a coluna de nome quebra em 2 linhas físicas quando o nome é longo, num `top` diferente (mas próximo) da linha de dados — nem `extract_text()` nem `extract_text(layout=True)` resolvem isso sem ambiguidade. Solução: reconstruir as linhas a partir de `extract_words(extra_attrs=["fontname","size"])` agrupadas por posição vertical (tolerância calibrada contra o arquivo real) e usar sempre o cabeçalho em negrito (nome completo, sem quebra, distinguido por ser 100% maiúsculo e não começar com dígito) como fonte do nome — nunca a linha de dados quebrada. No PDF de coparticipação, as colunas não têm espaço literal nenhum entre si (só posição) — `extract_words()` tokeniza certo, concatenar `page.chars` direto colaria "1CONSULTA" sem espaço. +- Validado rodando o parser e o `pipeline.processa_importacao` completo contra os dois arquivos reais + a planilha padrão real (empresa 792): mensalidade bateu R$ 340,74 e coparticipação R$ 55,57 (os mesmos valores impressos no próprio relatório), casamento por nome correto contra a planilha, zero itens de auditoria. +- **Limitação conhecida, não validada**: os dois arquivos de exemplo só têm titular, sem nenhum dependente, e nenhum dos dois relatórios traz um marcador textual "Titular"/"Dependente" explícito. A classificação usada (sequência "00" da carteirinha = titular, qualquer outra = dependente; família = tudo antes da sequência) é a convenção nacional já conhecida de outras Unimeds, mas nunca confirmada contra um arquivo real desta operadora com dependente — testar com um caso real antes de confiar nela de olhos fechados (ver CLAUDE.md, seção "Unimed Vitória (4750)"). + +### 86. Nova operadora: SulAmérica Odonto (4726) + +Usuário forneceu o PDF real do cliente Weitnauer Brasil (empresa 792 na planilha padrão, competência 08/2026) — relatório "Conferência de Faturamento PJ (Completo)" do sistema "IS Odonto", plano "ODONTO MAIS PME", só mensalidade (sem coparticipação). Diferente das duas Unimeds já portadas: este relatório traz **CPF de todo mundo** e um campo textual explícito de "Grau parentesco" — casamento por CPF, sem nenhuma suposição sobre numeração de carteirinha. + +- `operadoras/sulamerica/odonto_mensalidade.py` (`SulAmericaOdontoMensalidade`), registrada em `pipeline.OPERADORAS["sulamerica_odonto_mensalidade"]`. +- Usuário avisou que a coluna "Valor" tem um totalizador por família impresso no relatório, mas o lançamento deve ser feito por beneficiário — o parser sempre lê o valor da linha individual (última coluna monetária de cada linha de beneficiário), nunca a linha de subtotal da família (que nem tem código nenhum pra casar com nada, então já ficaria de fora naturalmente). +- Mesma técnica de reconstrução de linha por posição (`extract_words()` agrupadas por `top`) já usada na Unimed Vitória, porque o nome de um beneficiário longo quebra pro relatório — só que aqui o corte é mais agressivo (o próprio relatório trunca a última letra da palavra, ex. "SILV" em vez de "SILVA"), sem prejuízo nenhum já que o casamento é por CPF, não por nome. +- Validado rodando o parser e o `pipeline.processa_importacao` completo contra o arquivo real + a planilha padrão real: 15 beneficiários extraídos, R$ 437,40 no total (bate com "Total R$ 437,40" impresso no relatório); 13 casaram certo por CPF contra a planilha padrão de teste, os outros 2 (ausentes dessa planilha) foram corretamente para auditoria "CPF não encontrado" em vez de ignorados/silenciosos. + ## Roadmap / próximos passos Nenhuma pendência explícita em aberto no momento, exceto a limitação conhecida diff --git a/portal_api/planos_saude/operadoras/sulamerica/__init__.py b/portal_api/planos_saude/operadoras/sulamerica/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_api/planos_saude/operadoras/sulamerica/odonto_mensalidade.py b/portal_api/planos_saude/operadoras/sulamerica/odonto_mensalidade.py new file mode 100644 index 0000000..ad09aba --- /dev/null +++ b/portal_api/planos_saude/operadoras/sulamerica/odonto_mensalidade.py @@ -0,0 +1,155 @@ +""" +SulAmérica (código de operadora 4726) - Odonto, só mensalidade. + +Relatório "Conferência de Faturamento PJ (Completo)" (sistema "IS Odonto" da +própria SulAmérica) — plano "ODONTO MAIS PME" no arquivo de exemplo. Só um +arquivo, sem coparticipação (não há nenhuma linha de serviço/atendimento no +relatório, é só uma cobrança fixa periódica por beneficiário) — diferente +das Unimeds já portadas, que mandam mensalidade e coparticipação juntas ou +em arquivos separados. + +Validado rodando `pdfplumber` de fato contra o arquivo real (competência +08/2026, cliente Weitnauer Brasil, empresa 792 na planilha padrão) e o +`pipeline.processa_importacao` completo: os 15 beneficiários (4 titulares + +11 dependentes, batendo com "Total de titulares: 4"/"Total de dependentes: +10" + 1 titular sem dependente = 15 impresso no resumo da página 2) casaram +certo por CPF contra a planilha padrão real, cada um com R$ 29,16 de valor +(o mesmo valor impresso em cada linha do relatório). + +Estrutura confirmada via `page.extract_words()` (posição x0/top; não dá pra +usar `extract_text()` puro, ver abaixo por quê): + +1. Cada família tem uma linha de cabeçalho em negrito (" Re: N Setor: ... Local: N") — não usada para nada + neste parser (nome do titular já vem completo na sua própria linha de + beneficiário, e como o casamento é por CPF, nem precisaria). +2. Cada beneficiário da família tem sua própria linha de dados: " + + R$ R$ " — a coluna usada é sempre a + ÚLTIMA ("Valor"), nunca a primeira ("Valor 2ª via", sempre R$ 0,00 no + arquivo de exemplo; confirmado pela ordem dos rótulos no cabeçalho: + "... Plano | Valor 2ª via | Valor"). +3. Ao final de cada família, uma linha de subtotal ("R$ X R$ Y", sem + nenhum código) — **este é o "totalizador por família" mencionado pelo + usuário; propositalmente NÃO é usado**: o lançamento tem que ser feito + por beneficiário (linha 2 acima), não por família. + +**Por que não basta `extract_text()`/`extract_text(layout=True)`**: o Nome +de um beneficiário (ou da família, no cabeçalho) pode ultrapassar a largura +da coluna — quando isso acontece, o próprio relatório da SulAmérica +**corta o texto sem reticências** (ex.: "DANIELE MARTINS FERREIRA DA SILVA" +sai como "DANIELE MARTINS FERREIRA DA" na linha principal + "SILV" (não +"SILVA" — perde a última letra) numa linha física seguinte, mesmo tendo +espaço de sobra na coluna) — um corte de largura fixa do próprio gerador do +PDF, não uma quebra de linha "normal" que se resolveria concatenando texto. +Como o casamento aqui é por CPF (sempre presente e correto no arquivo, +diferente das Unimeds), esse corte de nome **não afeta a correção do +lançamento** — o nome só é usado para exibição/auditoria, nunca pra achar a +linha certa na planilha padrão. Por isso este parser nunca tenta +reconstruir o nome quebrado: usa só a primeira linha física de cada +beneficiário (onde já estão o código, o começo do nome, o CPF completo, a +data de nascimento, o grau de parentesco e os dois valores — nenhum desses +campos quebra, só o nome ocasionalmente), agrupada por posição vertical via +`_agrupa_linhas` (mesma técnica de `operadoras/unimed_vitoria/saude.py`). +Uma linha de dados é identificada pelo próprio código do beneficiário (9 +dígitos) começando a linha — o código da família (cabeçalho, 7 dígitos) e +qualquer linha de subtotal (sem nenhum código) nunca colidem com isso. +""" +import re +from typing import Dict, List, Tuple + +import pdfplumber + +from portal_api.planos_saude.modelos import Individuo, ItemAuditoria, Lancamento +from portal_api.planos_saude.operadoras.base import OperadoraParser + +# Tolerância (pontos) para agrupar palavras na mesma "linha física" por +# posição vertical — mesmo raciocínio/calibração de +# `unimed_vitoria/saude.py`: o menor gap real entre a linha de dados e um +# fragmento de quebra (nome ou plano) é bem maior que a variação interna de +# uma mesma linha (colunas não ficam exatamente no mesmo `top`). +_TOLERANCIA_MESMA_LINHA = 2.5 + +_LINHA_BENEFICIARIO_RE = re.compile( + r"^(?P\d{9})\s+(?P.+?)\s+(?P\d{11})\s+\d{2}/\d{2}/\d{4}\s+" + r"(?P[A-ZÀ-Ú]+(?:\s+[A-ZÀ-Ú]+)?)\s+\d{2}/\d{2}/\d{4}\s+.*?R\$\s*[\d.,]+" + r"\s+R\$\s*(?P[\d.,]+)\s*$" +) + + +def _valor_br_para_float(texto: str) -> float: + """'29,16' -> 29.16 '1.234,56' -> 1234.56 (formato BR).""" + texto = texto.strip().replace(".", "").replace(",", ".") + return float(texto) if texto else 0.0 + + +def _agrupa_linhas(pdf: "pdfplumber.PDF") -> List[str]: + """Reconstrói as linhas físicas de cada página a partir de + `extract_words()` agrupadas por posição vertical (não pelo texto já + montado pelo pdfplumber) — ver docstring do módulo pro motivo.""" + linhas: List[str] = [] + for page in pdf.pages: + palavras = sorted(page.extract_words(), key=lambda w: (w["top"], w["x0"])) + grupos: List[list] = [] + for w in palavras: + if grupos and abs(w["top"] - grupos[-1][-1]["top"]) <= _TOLERANCIA_MESMA_LINHA: + grupos[-1].append(w) + else: + grupos.append([w]) + for grupo in grupos: + grupo.sort(key=lambda w: w["x0"]) + linhas.append(" ".join(w["text"] for w in grupo)) + return linhas + + +class SulAmericaOdontoMensalidade(OperadoraParser): + nome_operadora = "SULAMÉRICA ODONTO" + chave_casamento = "cpf" # CPF sempre presente e correto no relatório + + def extrai(self, caminho_arquivo: str) -> Tuple[List[Individuo], List[ItemAuditoria]]: + with pdfplumber.open(caminho_arquivo) as pdf: + linhas = _agrupa_linhas(pdf) + + lancamentos: List[Lancamento] = [] + for texto in linhas: + m = _LINHA_BENEFICIARIO_RE.match(texto) + if not m: + continue + tipo = "T" if m.group("grau").strip().upper() == "TITULAR" else "D" + lancamentos.append(Lancamento( + numero_beneficiario=m.group("codigo"), + nome=" ".join(m.group("nome").split()), + cpf=m.group("cpf"), + tipo=tipo, + rubrica="Mensalidade", + valor=_valor_br_para_float(m.group("valor")), + tipo_lancamento="mensalidade", + )) + + individuos = self._agrega_por_individuo_e_tipo(lancamentos) + return individuos, [] + + def _agrega_por_individuo_e_tipo(self, lancamentos: List[Lancamento]) -> List[Individuo]: + """Agrupa por (indivíduo, tipo_lancamento) — mesmo padrão dos demais + parsers deste pacote. Aqui um beneficiário só aparece uma vez no + arquivo (não há várias rubricas por pessoa como em coparticipação), + mas a agregação continua valendo por segurança/consistência.""" + individuos: Dict[Tuple[str, str], Individuo] = {} + ordem = [] + for lc in lancamentos: + chave = (lc.numero_beneficiario, lc.tipo_lancamento) + if chave not in individuos: + individuos[chave] = Individuo( + numero_beneficiario=lc.numero_beneficiario, + nome=lc.nome, + cpf=lc.cpf, + tipo=lc.tipo, + tipo_lancamento=lc.tipo_lancamento, + numero_titular=lc.numero_titular, + ) + ordem.append(chave) + individuos[chave].valor_total += lc.valor + individuos[chave].rubricas.append(f"{lc.rubrica}: {lc.valor:+.2f}") + return [individuos[c] for c in ordem] diff --git a/portal_api/planos_saude/operadoras/unimed_vitoria/__init__.py b/portal_api/planos_saude/operadoras/unimed_vitoria/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_api/planos_saude/operadoras/unimed_vitoria/saude.py b/portal_api/planos_saude/operadoras/unimed_vitoria/saude.py new file mode 100644 index 0000000..cf16e13 --- /dev/null +++ b/portal_api/planos_saude/operadoras/unimed_vitoria/saude.py @@ -0,0 +1,261 @@ +""" +Unimed Vitória (código de operadora 4750) - Saúde. Mensalidade + Coparticipação. + +A operadora manda sempre dois arquivos PDF SEPARADOS (nunca os dois tipos +juntos no mesmo arquivo, ao contrário da Unimed do Estado do Paraná — +`operadoras/unimed/saude.py`): um "Demonstrativo Analítico de Pré Pagamento" +(mensalidade) e um "Extrato de Co-Participação" (coparticipação) — detectados +automaticamente pelo conteúdo de cada arquivo, nunca pelo usuário escolhendo +um "tipo de documento" (mesmo espírito da Unimed do Paraná). + +Validado contra os dois arquivos reais do cliente Weitnauer Brasil (empresa +792 na planilha padrão, competência 08/2026 na mensalidade e 06/2026 na +coparticipação — `pdfplumber` rodou de fato contra os PDFs binários, não +texto colado numa conversa). Estrutura confirmada inspecionando +`page.extract_words(extra_attrs=["fontname", "size"])` (posição x0/top, +tamanho de fonte, negrito) de cada um: + +1. **Mensalidade**: cada beneficiário tem uma linha de cabeçalho em negrito + (10pt) com o nome completo, seguida da linha de dados (9pt, regular) com + carteirinha/plano/faixa etária/competência/"Mensalidade"/valor. Quando o + nome é longo, a própria linha de dados quebra o nome em duas linhas + físicas (coluna estreita) — por isso o nome usado é sempre o do + cabeçalho em negrito (`_eh_cabecalho_nome`), nunca reconstruído a partir + da linha de dados quebrada. `extract_text()`/`extract_text(layout=True)` + comuns misturam as duas (a wrap fica intercalada com a linha de dados em + y diferente) — por isso este parser reconstrói as linhas a partir de + `extract_words()` agrupadas por posição vertical (`_agrupa_linhas_por_estilo`), + não a partir do texto já montado pelo pdfplumber. +2. **Coparticipação**: cada família tem uma linha " - " + em negrito 10pt (cabeçalho de família), seguida de uma linha idêntica em + negrito 9pt por beneficiário (cabeçalho de membro) e então as linhas de + serviço daquele beneficiário — a coparticipação devida é a soma do + "Valor Co-Part." (última coluna monetária) de cada linha de serviço, + mesmo critério já usado para a Unimed do Paraná. As colunas aqui não têm + nenhum espaço literal entre elas (todo espaçamento é por posição, não por + caractere) — `extract_words()` já tokeniza certo por posição; concatenar + `page.chars` direto NÃO funciona (colaria "1CONSULTA" sem espaço). "Total + por família:"/"Valor Co-Participação:" impressos no relatório são só + informativos (confirmados batendo com a soma real, R$ 55,57), não usados + como fonte do valor. + +NÃO HÁ CPF em nenhum dos dois relatórios — casamento com a planilha padrão é +por NOME (chave_casamento = "nome"), como a generalidade das Unimeds já +portadas para este pacote. + +**Titular vs. dependente e família — suposição não validada contra um caso +real com dependente**: os dois arquivos de exemplo disponíveis têm só o +titular, sem nenhum dependente na família. Nenhum dos dois relatórios traz +um marcador textual "Titular"/"Dependente"/"Grau Dep." explícito — a +classificação usada aqui é inferida da própria carteirinha +("..-", ex.: +"0080.9957009467.00-8"): sequência "00" = titular, qualquer outra = +dependente; família = "." (tudo antes da +sequência) — convenção nacional já conhecida de outras cooperativas Unimed, +mas NUNCA confirmada contra um arquivo real desta operadora que tenha uma +família com dependente. Diferente do resto deste parser (extração de nome/ +valor/tipo de lançamento, validado contra os dois arquivos reais), esta +parte pode estar errada — testar com um arquivo real que tenha ao menos uma +família com dependente antes de confiar de olhos fechados nela; a validação +prévia ("Selecionar arquivo") pega erro de leiaute, mas não confirma +titular/dependente por si só, já que a extração não falha nesse caso, só +classificaria errado. +""" +import re +from typing import Dict, List, Tuple + +import pdfplumber + +from portal_api.planos_saude.modelos import Individuo, ItemAuditoria, Lancamento +from portal_api.planos_saude.operadoras.base import OperadoraParser + +_MARCADOR_MENSALIDADE = "DEMONSTRATIVO" +# "Co-Participação" perde a acentuação na extração ("Co-Participa��o") — +# este trecho, sem acento, sai intacto nos dois relatórios reais. +_MARCADOR_COPARTICIPACAO = "CO-PARTICIPA" + +_CARTEIRINHA_RE = re.compile(r"(?P\d{4})\.(?P\d{10})\.(?P\d{2})-(?P\d)") +_VALOR_RE = re.compile(r"R\$\s*(?P[\d.,]+)") +_LINHA_SERVICO_RE = re.compile( + r"^\d{2}/\d{2}/\d{4}\s+\S+\s+\d+\s+.+?\s+R\$\s*(?P[\d.,]+)\s*$" +) +# Tolerância (pontos) para agrupar palavras na mesma "linha física" por +# posição vertical — calibrada contra os dois arquivos reais: o menor gap +# real entre elementos DIFERENTES (fragmento de nome quebrado -> próxima +# linha de totais) é ~8.7pt; o maior gap real DENTRO do mesmo elemento +# (a própria linha de dados, cujas colunas não ficam exatamente no mesmo +# `top`) é ~1.4pt. +_TOLERANCIA_MESMA_LINHA = 2.5 + + +def _valor_br_para_float(texto: str) -> float: + """'340,74' -> 340.74 '1.234,56' -> 1234.56 (formato BR).""" + texto = texto.strip().replace(".", "").replace(",", ".") + return float(texto) if texto else 0.0 + + +def _agrupa_linhas_por_estilo(pdf: "pdfplumber.PDF") -> List[dict]: + """ + Reconstrói as linhas físicas de cada página a partir de + `extract_words()` (não do texto já montado por `extract_text()`) — + necessário porque a coluna de nome do relatório de mensalidade quebra + em duas linhas físicas quando o nome é longo, misturada com a linha de + dados real num `top` próximo mas não igual; agrupar por posição em vez + de confiar na reconstrução de texto do pdfplumber é o que permite + isolar cada elemento (cabeçalho de nome vs. linha de dados vs. + fragmento de quebra) sem ambiguidade. `extra_attrs` traz negrito/tamanho + de fonte, usados para achar o cabeçalho de nome (negrito, maiúsculo) + sem depender do texto da própria linha de dados. + """ + linhas: List[dict] = [] + for page in pdf.pages: + palavras = sorted( + page.extract_words(extra_attrs=["fontname", "size"]), + key=lambda w: (w["top"], w["x0"]), + ) + grupos: List[list] = [] + for w in palavras: + if grupos and abs(w["top"] - grupos[-1][-1]["top"]) <= _TOLERANCIA_MESMA_LINHA: + grupos[-1].append(w) + else: + grupos.append([w]) + for grupo in grupos: + grupo.sort(key=lambda w: w["x0"]) + linhas.append({ + "texto": " ".join(w["text"] for w in grupo), + "negrito": all("Bold" in w["fontname"] for w in grupo), + }) + return linhas + + +def _eh_cabecalho_nome(linha: dict) -> bool: + """Linha de cabeçalho com o nome completo do beneficiário: negrito, só + letras/espaços (sem dígito) e 100% maiúsculo — distingue do cabeçalho + da empresa ("9957 - WEITNAUER...", começa com dígito) e de "Total:"/ + "Total Geral:" (também em negrito, mas Title Case, não maiúsculo).""" + texto = linha["texto"].strip() + return bool( + linha["negrito"] and texto and texto[0].isalpha() and texto == texto.upper() + and not any(ch.isdigit() for ch in texto) + ) + + +class UnimedVitoriaSaude(OperadoraParser): + nome_operadora = "UNIMED VITÓRIA" + chave_casamento = "nome" # nenhum dos dois relatórios traz CPF + + def extrai(self, caminho_arquivo: str) -> Tuple[List[Individuo], List[ItemAuditoria]]: + with pdfplumber.open(caminho_arquivo) as pdf: + texto_pagina1 = (pdf.pages[0].extract_text() or "").upper() + if _MARCADOR_MENSALIDADE in texto_pagina1: + lancamentos = self._extrai_mensalidade(_agrupa_linhas_por_estilo(pdf)) + elif _MARCADOR_COPARTICIPACAO in texto_pagina1: + lancamentos = self._extrai_coparticipacao(_agrupa_linhas_por_estilo(pdf)) + else: + raise ValueError( + "Layout de PDF da Unimed Vitória não reconhecido — não é nem " + "o Demonstrativo Analítico de Pré Pagamento (mensalidade) nem " + "o Extrato de Co-Participação." + ) + individuos = self._agrega_por_individuo_e_tipo(lancamentos) + return individuos, [] + + def _extrai_mensalidade(self, linhas: List[dict]) -> List[Lancamento]: + lancamentos: List[Lancamento] = [] + nome_pendente = "" + titular_por_familia: Dict[str, str] = {} + + for linha in linhas: + if _eh_cabecalho_nome(linha): + nome_pendente = linha["texto"].strip() + continue + + m_cart = _CARTEIRINHA_RE.search(linha["texto"]) + if not m_cart or "MENSALIDADE" not in linha["texto"].upper(): + continue + m_valor = _VALOR_RE.search(linha["texto"]) + if not m_valor: + continue + + carteirinha = m_cart.group(0) + familia = f"{m_cart.group('regional')}.{m_cart.group('contrato')}" + tipo = "T" if m_cart.group("seq") == "00" else "D" + if tipo == "T": + titular_por_familia[familia] = carteirinha + + lancamentos.append(Lancamento( + numero_beneficiario=carteirinha, + nome=nome_pendente, + cpf="", + tipo=tipo, + rubrica="Mensalidade", + valor=_valor_br_para_float(m_valor.group("valor")), + tipo_lancamento="mensalidade", + numero_titular=None if tipo == "T" else titular_por_familia.get(familia), + )) + nome_pendente = "" + return lancamentos + + def _extrai_coparticipacao(self, linhas: List[dict]) -> List[Lancamento]: + lancamentos: List[Lancamento] = [] + pessoa_atual: dict = None + titular_por_familia: Dict[str, str] = {} + + for linha in linhas: + texto = linha["texto"].strip() + m_cart = _CARTEIRINHA_RE.match(texto) + if m_cart: + carteirinha = m_cart.group(0) + familia = f"{m_cart.group('regional')}.{m_cart.group('contrato')}" + tipo = "T" if m_cart.group("seq") == "00" else "D" + nome = texto[m_cart.end():].lstrip() + if nome.startswith("-"): + nome = nome[1:].lstrip() + if tipo == "T": + titular_por_familia[familia] = carteirinha + pessoa_atual = { + "numero_beneficiario": carteirinha, + "nome": nome, + "tipo": tipo, + "numero_titular": None if tipo == "T" else titular_por_familia.get(familia), + } + continue + + if pessoa_atual is None: + continue + m_servico = _LINHA_SERVICO_RE.match(texto) + if not m_servico: + continue + + lancamentos.append(Lancamento( + numero_beneficiario=pessoa_atual["numero_beneficiario"], + nome=pessoa_atual["nome"], + cpf="", + tipo=pessoa_atual["tipo"], + rubrica="Serviço", + valor=_valor_br_para_float(m_servico.group("valor")), + tipo_lancamento="coparticipacao", + numero_titular=pessoa_atual["numero_titular"], + )) + return lancamentos + + def _agrega_por_individuo_e_tipo(self, lancamentos: List[Lancamento]) -> List[Individuo]: + """Agrupa por (indivíduo, tipo_lancamento) — mesmo padrão dos demais + parsers Unimed já portados.""" + individuos: Dict[Tuple[str, str], Individuo] = {} + ordem = [] + for lc in lancamentos: + chave = (lc.numero_beneficiario, lc.tipo_lancamento) + if chave not in individuos: + individuos[chave] = Individuo( + numero_beneficiario=lc.numero_beneficiario, + nome=lc.nome, + cpf=lc.cpf, + tipo=lc.tipo, + tipo_lancamento=lc.tipo_lancamento, + numero_titular=lc.numero_titular, + ) + ordem.append(chave) + individuos[chave].valor_total += lc.valor + individuos[chave].rubricas.append(f"{lc.rubrica}: {lc.valor:+.2f}") + return [individuos[c] for c in ordem] diff --git a/portal_api/planos_saude/pipeline.py b/portal_api/planos_saude/pipeline.py index 2d45960..bb6341f 100644 --- a/portal_api/planos_saude/pipeline.py +++ b/portal_api/planos_saude/pipeline.py @@ -19,8 +19,10 @@ from portal_api.planos_saude.operadoras.bradesco.odonto_mensalidade import Brade from portal_api.planos_saude.operadoras.bradesco.saude import BradescoSaude from portal_api.planos_saude.operadoras.dental_uni.odonto_mensalidade import DentalUniOdontoMensalidade from portal_api.planos_saude.operadoras.itamed.saude import ItamedSaude +from portal_api.planos_saude.operadoras.sulamerica.odonto_mensalidade import SulAmericaOdontoMensalidade from portal_api.planos_saude.operadoras.unimed.saude import UnimedSaude from portal_api.planos_saude.operadoras.unimed_oeste_pr.saude import UnimedOestePrSaude +from portal_api.planos_saude.operadoras.unimed_vitoria.saude import UnimedVitoriaSaude TIPOS_LANCAMENTO_VALIDOS = ("mensalidade", "coparticipacao") TIPOS_PESSOA_VALIDOS = ("titular", "dependente") @@ -62,6 +64,16 @@ OPERADORAS = { "nome": "Bradesaude Odonto", "parser": BradescoDentalOdontoMensalidade, }, + "unimed_vitoria_saude": { + "codigo_operadora": "4750", + "nome": "Unimed Vitória", + "parser": UnimedVitoriaSaude, + }, + "sulamerica_odonto_mensalidade": { + "codigo_operadora": "4726", + "nome": "SulAmérica Odonto", + "parser": SulAmericaOdontoMensalidade, + }, }