feat: unified fiscal coverage - PIS/COFINS applicability + conditional RTC in one definition, one queue, one panel

- central/app/unified_fiscal_coverage.py: pure rules (PIS/COFINS NOT_APPLICABLE/REQUIRED/OPTIONAL/NEEDS_REVIEW, RTC only when the ERP switch is on)
- agent: read-only loader (unified_fiscal.py) replaces the legacy/expanded module; findings feed the single operational state, coverage and blocker queue
- hub: BLOCKED_FISCAL_DATA blockers (PIS/COFINS/RTC) share the queue; stale ones close on the next full pass; dashboard breakdown gains CEST/PIS/COFINS/RTC
- panel: legacy/expanded coverage block removed; discreet 'Reforma Tributaria: Ativa/Desativada'
- docs: unified_fiscal_completeness, pis_cofins_applicability, rtc_pis_cofins_model + official source manifests; read-only E2E script

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 16:39:53 -03:00
co-authored by Claude Sonnet 5
parent d897cceebb
commit b15dbea213
32 changed files with 3979 additions and 517 deletions
+64
View File
@@ -0,0 +1,64 @@
# Completude fiscal unificada (UNIFIED_FISCAL_COVERAGE)
Uma definição de "completo", um painel, uma fila de blockers, uma cobertura.
## Definição
```
campos obrigatórios(produto, empresa) =
campos fiscais base aplicáveis (NCM, CEST, CFOP, ICMS/CST/CSOSN, IPI ... — decididos pelo motor de decisão do Hub)
+ PIS/COFINS aplicáveis (docs/pis_cofins_applicability.md)
+ RTC (cClassTrib → CST IBS/CBS) SOMENTE quando o ERP tem a Reforma Tributária ligada (docs/rtc_pis_cofins_model.md)
```
Campos `NOT_APPLICABLE` não contam contra a completude. Nenhum campo é exigido só porque a coluna existe.
## Onde vive (nenhuma cópia paralela)
| Peça | Arquivo |
|---|---|
| regras puras (PIS/COFINS, RTC, `evaluate_product`) | `central/app/unified_fiscal_coverage.py` |
| estado do produto (`COMPLETE`/`CORRECTED`/`BLOCKED_*`/`BLOCKED_FISCAL_DATA`/`FAILED_TECHNICAL`/`NEEDS_REVIEW`) | `central/app/operational_state.py` (`classify_product_state(..., fiscal=)`) |
| leitura do ERP (somente SELECT) | `agent/src/gti_fiscal_agent/unified_fiscal.py` |
| integração no job do Agent | `agent/scripts/run_agent_service.py` (`_unified_fiscal`, `_fiscal_blocked_products`) |
| snapshot / cobertura no Hub | `central/app/api.py` (`_apply_job_outcome` — inalterado: usa o mesmo `operational`) |
| fila única + fechamento automático | `central/app/blocked_products.py` (`resolve_stale_fiscal_blockers`) |
| painel | `central/app/panel_presentation.py`, `central/templates/{dashboard,blockers,agent_detail}.html` |
## Estados finais do produto
`COMPLETE` (+`CORRECTED`) · `SAFE_BLOCKED` (`BLOCKED_*`, incluindo `BLOCKED_FISCAL_DATA` para PIS/COFINS/RTC) · `FAILED_TECHNICAL` · `NEEDS_REVIEW` (não classificado; só incidência PIS/COFINS fora do vocabulário do SGV).
Invariante: `TOTAL = COMPLETE + SAFE_BLOCKED + FAILED + UNCLASSIFIED` (o Hub calcula "não classificados" como `total − completos − bloqueados − falhas`, então a igualdade fecha por construção e é reverificada em `coverage_invariant_pass`).
Precedência: um blocker de decisão (NCM, conflito, contexto) permanece o estado principal; blockers fiscais só rebaixam produto que seria completo. Um blocker real vale mais que `NEEDS_REVIEW`.
## Fila única e fechamento
Cada blocker fiscal é um blocker comum: `blocker_type=BLOCKED_FISCAL_DATA`, `field ∈ {PIS, COFINS, RTC}`, `reason_code` = tipo (ex.: `PIS_CST_INVALID`). A cada job de varredura completa (`fiscal_context.evaluated_all=true`) o Agent reenvia todos os blockers vigentes e o Hub fecha (`AUTO_REEVALUATION`) os blockers PIS/COFINS/RTC da instalação não reenviados (dado corrigido, ou RTC desligada). Reprocesso parcial não fecha nada de outros produtos. O fingerprint do blocker inclui os valores fiscais lidos.
## Painel
- Dashboard: um único bloco "Cobertura dos produtos" (Produtos, Completos, Bloqueados com segurança, Falhas técnicas, Não classificados, Cobertura fiscal). Breakdown de blockers: `NCM, CEST, CFOP, ST, ICMS, CSOSN, PIS, COFINS, RTC, OTHER` (categoria com 0 fica esmaecida).
- Detalhe do Agent: a mesma cobertura + uma linha discreta "Reforma Tributária: Ativa/Desativada" (do último job). RTC desativada nunca aparece como problema. O bloco "Escopo anterior/ampliado" foi removido.
- Removidos do código: `expanded_scope_summary`, `expanded_scope_view`, `LEGACY_SCOPE_COVERAGE`, `EXPANDED_SCOPE_COVERAGE`, `UNKNOWN_APPLICABILITY` (como estado em massa).
## Segurança
Somente leitura. `FIREBIRD_WRITES=0`: a avaliação abre uma conexão `READ COMMITTED READ ONLY`, executa apenas SELECT e nunca preenche `CCLASSTRIB`, PIS ou COFINS. Testes provam que só SELECT é emitido.
## Validação no banco real (somente leitura)
`scripts/unified_coverage_e2e.py` → `docs/runs/unified_coverage_e2e*.json`. TESTESOFICIAIS (521 produtos, CRT 1):
| | Antes (só decisão) | Depois — RTC desligada (estado do piloto) |
|---|---|---|
| Completos | 495 (95,01%) | 493 (94,63%) |
| Bloqueados | 26 | 28 |
| Falhas / Não classificados | 0 / 0 | 0 / 0 |
| RTC blockers | — | 0 |
| PIS / COFINS blockers | — | 2 / 2 (2 produtos) |
Aplicabilidade PIS/COFINS: 518 `NOT_APPLICABLE`, 2 `REQUIRED`, 1 `OPTIONAL`, 0 `NEEDS_REVIEW` (antes: 492 `UNKNOWN_APPLICABILITY`). Os 2 produtos bloqueados são legítimos: um com CST `2`/`2` (formato inválido) e um com incidência SUBST.TRIBUTÁRIA e CST 01 (incompatível com CST 05).
### Corpus de clientes (RTC desligada, somente PIS/COFINS; `--fiscal-only`, sem decisão do Hub)
| Banco | CRT | Produtos | NOT_APPLICABLE | OPTIONAL | Blockers PIS/COFINS | Cobertura da parte fiscal |
|---|---|---|---|---|---|---|
| CONSTRUCAO | 1 | 3.712 | 3.712 | 0 | 0 | 100% |
| MMMODAS | 1 | 20.997 | 20.995 | 1 | 0 | 100% |
| UNIAO | 1 | 8.942 | 8.170 | 772 | 2 produtos (COFINS `000`/`060`, estilo CST de ICMS) | 99,98% |
| RINCAO | 3 | 894 | 815 | 79 | 79 produtos (78 com CST `4` sem zero à esquerda + 1) | 91,16% |
| TESTESOFICIAIS | 1 | 521 | 518 | 1 | 2 produtos | 99,62% (94,63% com a decisão do Hub) |
`OPTIONAL` = valor digitado sem flag/incidência especial. RINCAO: `4` deve ser `04` (o seletor do SGV grava 2 dígitos); o motivo do blocker traz a dica `UNPADDED_USE_04`, nunca aplicada.