feat(bloco-a): higiene alembic heads e inventario dos 9 datasets no registry
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
"""Missão 6 / Bloco A.2: Inventário dos 9 datasets existentes no Hub e migração para o Reference Data Registry.
|
||||
|
||||
Datasets catalogados:
|
||||
1. NCM (Siscomex)
|
||||
2. TIPI_IPI (Receita Federal)
|
||||
3. CEST (CONFAZ)
|
||||
4. BA_ST_MVA (SEFAZ-BA)
|
||||
5. NFE_MOC_TABLES (Receita Federal / ENCAT)
|
||||
6. CFOP (Portal Nacional NF-e)
|
||||
7. FCP (Portal Nacional NF-e)
|
||||
8. ANP (Portal Nacional NF-e)
|
||||
9. NCM_UTRIB (Portal Nacional NF-e)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models import Dataset
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DatasetInventoryItem:
|
||||
kind: str
|
||||
name: str
|
||||
provider: str
|
||||
source_url: str
|
||||
version_label: str
|
||||
expected_rows_approx: int
|
||||
act: str
|
||||
validity_info: str
|
||||
coverage: str
|
||||
licence_terms: str
|
||||
raw_file_name: Optional[str] = None
|
||||
|
||||
|
||||
DATASET_INVENTORY_9: tuple[DatasetInventoryItem, ...] = (
|
||||
DatasetInventoryItem(
|
||||
kind="NCM",
|
||||
name="Nomenclatura Comum do Mercosul (NCM)",
|
||||
provider="Siscomex",
|
||||
source_url="https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json",
|
||||
version_label="2026-09-28",
|
||||
expected_rows_approx=15156,
|
||||
act="Resolução Gecex / MDIC",
|
||||
validity_info="Vigente em 2026-09-28",
|
||||
coverage="Códigos NCM de 8 dígitos, descrições oficiais, datas de início e fim de vigência nacional.",
|
||||
licence_terms="Dado público oficial (Governo Federal / Siscomex); sem termos de uso restritivos conhecidos.",
|
||||
raw_file_name="ncm_vigente_20260928.json",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="TIPI_IPI",
|
||||
name="Tabela de Incidência do IPI (TIPI)",
|
||||
provider="Receita Federal",
|
||||
source_url="https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/tributos-e-receitas/tipi",
|
||||
version_label="tipi-xlsx-v1",
|
||||
expected_rows_approx=11028,
|
||||
act="Decreto nº 11.158/2022 e alterações até ADE 001-2026",
|
||||
validity_info="Vigente desde 2022 com atualizações até 2026",
|
||||
coverage="Alíquotas de IPI (percentuais ou NT) por NCM de 8 dígitos e código de exceção EX.",
|
||||
licence_terms="Dado público oficial (Receita Federal do Brasil); dados abertos governamentais.",
|
||||
raw_file_name="1 TIPI 2022 - Atualizada ADE 001-2026.xlsx",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="CEST",
|
||||
name="Código Especificador da Substituição Tributária (CEST)",
|
||||
provider="CONFAZ",
|
||||
source_url="https://www.confaz.fazenda.gov.br/legislacao/convenios/2018/CV142_18",
|
||||
version_label="CV142_18",
|
||||
expected_rows_approx=1096,
|
||||
act="Convênio ICMS 142/18",
|
||||
validity_info="Vigente com modificações sucessivas",
|
||||
coverage="Relação de CEST (7 dígitos), segmentos (Anexos II a XXVI) e códigos NCM aplicáveis à ST.",
|
||||
licence_terms="Dado público oficial (Conselho Nacional de Política Fazendária - CONFAZ).",
|
||||
raw_file_name="CV142_18.html",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="BA_ST_MVA",
|
||||
name="Pauta Fiscal e Margens de Valor Agregado ST da Bahia",
|
||||
provider="SEFAZ-BA",
|
||||
source_url="http://www.sefaz.ba.gov.br/legislacao/ricms/anexo1.pdf",
|
||||
version_label="ba-st-mva-pdf-v1",
|
||||
expected_rows_approx=757,
|
||||
act="RICMS/BA Anexo 1 (Decreto nº 13.780/2012)",
|
||||
validity_info="Vigente em 2026 na Bahia",
|
||||
coverage="Produtos sujeitos a ICMS-ST no Estado da Bahia, NCM/SH, CEST e percentuais de MVA original/ajustada.",
|
||||
licence_terms="Legislação tributária pública estadual (SEFAZ-BA).",
|
||||
raw_file_name="decreto_2012_13780_ricms_anexo_1_vigente_2026.pdf",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="NFE_MOC_TABLES",
|
||||
name="Tabelas de Domínios do MOC da NF-e / NFC-e",
|
||||
provider="Receita Federal / ENCAT",
|
||||
source_url="http://www.nfe.fazenda.gov.br/portal/principal.aspx",
|
||||
version_label="moc7-tables-v1",
|
||||
expected_rows_approx=104,
|
||||
act="MOC 7.0 Anexo I (Visão Geral de Leiaute e Regras de Validação)",
|
||||
validity_info="Vigente nacionalmente",
|
||||
coverage="Domínios oficiais de ORIGEM_MERCADORIA (9), CST_ICMS (14), CSOSN (10), CST_PIS (36) e CST_COFINS (36).",
|
||||
licence_terms="Documento técnico público oficial (ENCAT / Portal Nacional NF-e).",
|
||||
raw_file_name="moc7-anexo-i-leiaute-e-rv.pdf",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="CFOP",
|
||||
name="Tabela de Códigos Fiscais de Operações e Prestações (CFOP)",
|
||||
provider="Portal Nacional NF-e",
|
||||
source_url="https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=Iy/5QolvUsQ=",
|
||||
version_label="cfop-xlsx-v1",
|
||||
expected_rows_approx=593,
|
||||
act="Informe Técnico 2023.002 v2.10",
|
||||
validity_info="Vigente desde 2023",
|
||||
coverage="CFOP de 4 dígitos, vigências, indicadores operacionais (devolução, anulação, remessa, combustível) e descrições.",
|
||||
licence_terms="Dado público oficial (Portal Nacional da NF-e).",
|
||||
raw_file_name="IT_2023_002_v2_10_Tabela_CFOP_Descrição.xlsx",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="FCP",
|
||||
name="Tabela de Alíquotas de FCP por UF",
|
||||
provider="Portal Nacional NF-e",
|
||||
source_url="https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=Iy/5QolvUsQ=",
|
||||
version_label="fcp-xlsx-v1",
|
||||
expected_rows_approx=27,
|
||||
act="Tabela de Alíquotas FCP por UF",
|
||||
validity_info="Vigente (fevereiro/2025)",
|
||||
coverage="Alíquotas adicionais do Fundo de Combate à Pobreza para os 26 estados e Distrito Federal.",
|
||||
licence_terms="Dado público oficial (Portal Nacional da NF-e).",
|
||||
raw_file_name="Tabela_Alíquotas_FCP_por_UF_2025-02_07.xlsx",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="ANP",
|
||||
name="Tabela de Códigos de Produto ANP",
|
||||
provider="Portal Nacional NF-e",
|
||||
source_url="https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=Iy/5QolvUsQ=",
|
||||
version_label="anp-xlsx-v1",
|
||||
expected_rows_approx=184,
|
||||
act="Tabela de Código de Produto ANP",
|
||||
validity_info="Vigente (novembro/2024)",
|
||||
coverage="Códigos cProdANP e descrições oficiais de combustíveis e derivados de petróleo regulados pela ANP.",
|
||||
licence_terms="Dado público oficial (ANP / Portal Nacional da NF-e).",
|
||||
raw_file_name="Tabela_Codigo_Produto_ANP-20241118.xlsx",
|
||||
),
|
||||
DatasetInventoryItem(
|
||||
kind="NCM_UTRIB",
|
||||
name="Tabela de Correlação NCM x Unidade Tributável",
|
||||
provider="Portal Nacional NF-e",
|
||||
source_url="https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=Iy/5QolvUsQ=",
|
||||
version_label="ncm-utrib-xlsx-v1",
|
||||
expected_rows_approx=10374,
|
||||
act="Informe Técnico 2024.001 - NCM x uTrib",
|
||||
validity_info="Vigente a partir de 01.10.2026 / 2024",
|
||||
coverage="Mapeamento de cada NCM de 8 dígitos para a respectiva Unidade Tributável (uTrib) padrão nacional.",
|
||||
licence_terms="Dado público oficial (Portal Nacional da NF-e).",
|
||||
raw_file_name="IT2024.001v2.40 - Tabela NCM 2022 com Utrib_Comércio Exterior_vigência 01.10.26.xlsx",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def sync_inventory_to_registry(session: Session) -> dict[str, int]:
|
||||
"""Sincroniza os datasets existentes com as metainformações do Reference Data Registry.
|
||||
|
||||
Garante que os datasets já ativos no Hub (ACTIVE ou VIGENTE) possuam:
|
||||
- licence_terms preenchidos
|
||||
- act oficial correto
|
||||
- raw_file_path preenchido quando disponível
|
||||
- visibilidade no Registry sem criar segunda fonte ou duplicar linhas.
|
||||
"""
|
||||
inventory_map = {item.kind: item for item in DATASET_INVENTORY_9}
|
||||
updated = 0
|
||||
active_datasets = session.scalars(select(Dataset).where(Dataset.status.in_(("ACTIVE", "VIGENTE")))).all()
|
||||
for ds in active_datasets:
|
||||
inv = inventory_map.get(ds.kind)
|
||||
if inv:
|
||||
changed = False
|
||||
if not ds.licence_terms:
|
||||
ds.licence_terms = inv.licence_terms
|
||||
changed = True
|
||||
if not ds.act:
|
||||
ds.act = inv.act
|
||||
changed = True
|
||||
if inv.raw_file_name and not ds.raw_file_path:
|
||||
ds.raw_file_path = f"sources/raw/bootstrap_v1/{inv.raw_file_name}"
|
||||
changed = True
|
||||
if changed:
|
||||
updated += 1
|
||||
session.commit()
|
||||
return {"active_found": len(active_datasets), "updated": updated}
|
||||
@@ -12,7 +12,8 @@ from sqlalchemy import select, update
|
||||
|
||||
from .models import Dataset
|
||||
|
||||
STATUSES = ("DRAFT", "APPROVED", "VIGENTE", "OBSOLETO", "REJECTED")
|
||||
STATUSES = ("DRAFT", "APPROVED", "VIGENTE", "ACTIVE", "OBSOLETO", "SUPERSEDED", "REJECTED")
|
||||
VIGENTE_STATUSES = ("VIGENTE", "ACTIVE")
|
||||
|
||||
|
||||
class RegistryError(Exception):
|
||||
@@ -43,7 +44,7 @@ def diff_against_vigente(session, dataset: Dataset) -> dict:
|
||||
"""A summary an ADMIN reviews before approving: what the current VIGENTE of the same kind looks
|
||||
like vs this DRAFT - never the row-by-row content (that lives in the kind-specific entry tables),
|
||||
just the registry-level facts that matter for an approval decision."""
|
||||
current = session.scalar(select(Dataset).where(Dataset.kind == dataset.kind, Dataset.status == "VIGENTE"))
|
||||
current = session.scalar(select(Dataset).where(Dataset.kind == dataset.kind, Dataset.status.in_(VIGENTE_STATUSES)).order_by(Dataset.id.desc()))
|
||||
return {
|
||||
"kind": dataset.kind,
|
||||
"draft": {"id": dataset.id, "version_label": dataset.version_label, "sha256": dataset.sha256, "record_count": dataset.record_count,
|
||||
@@ -67,7 +68,7 @@ def approve_draft(session, dataset_id: int, *, admin_name: str) -> Dataset:
|
||||
raise RegistryError(f"only a DRAFT dataset can be approved (current status: {dataset.status})")
|
||||
if dataset.parse_errors:
|
||||
raise RegistryError("dataset has recorded parse errors - re-submit a clean version")
|
||||
session.execute(update(Dataset).where(Dataset.kind == dataset.kind, Dataset.status == "VIGENTE").values(status="OBSOLETO"))
|
||||
session.execute(update(Dataset).where(Dataset.kind == dataset.kind, Dataset.status.in_(VIGENTE_STATUSES)).values(status="OBSOLETO"))
|
||||
dataset.status = "VIGENTE"
|
||||
dataset.approved_by = admin_name
|
||||
dataset.approved_at = datetime.now(timezone.utc)
|
||||
|
||||
Reference in New Issue
Block a user