chore: establish project baseline

This commit is contained in:
OpenCode
2026-09-21 15:51:47 -03:00
commit 9dcc263217
459 changed files with 447890 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Official fiscal dataset importers."""
+29
View File
@@ -0,0 +1,29 @@
"""Fail-closed text parser for the official RICMS/BA Anexo 1 PDF."""
import re
_ITEM = re.compile(r"^\s*(\d{1,2}(?:\.\d+)+)\s+(?:(\d{2}\.\d{3}\.\d{2})\s+)?(.*)$")
_NCM = re.compile(r"\b(?:\d{4}(?:\.\d{1,4}){0,3}|\d{2}(?:\.\d{1,4}){1,3})\b")
_MVA = re.compile(r"\d+(?:,\d+)?%")
def parse_page(text, page):
records, rejected = [], []
lines = [" ".join(line.split()) for line in text.splitlines()]
index = 0
while index < len(lines):
match = _ITEM.match(lines[index])
if not match:
index += 1; continue
item, cest, content = match.groups(); block = [content]; index += 1
while index < len(lines) and not _ITEM.match(lines[index]) and not lines[index].startswith("ITEM CEST"):
block.append(lines[index]); index += 1
joined = " ".join(block)
ncm = _NCM.findall(joined)
if not cest and not ncm:
rejected.append({"item": item, "source_page": page, "reason": "NO_CEST_OR_NCM"}); continue
mvas = _MVA.findall(joined)
description = joined
if cest: description = description.replace(cest, "")
for code in ncm: description = description.replace(code, "")
for mva in mvas: description = description.replace(mva, "")
records.append({"item": item, "cest": cest, "ncm_sh": ncm or None, "description": " ".join(description.split()) or None, "mva_original": mvas[-1] if mvas else None, "mva_adjusted": mvas[0] if mvas else None, "agreement_info": None, "source_page": page})
return records, rejected
+287
View File
@@ -0,0 +1,287 @@
"""Parsers for the official NF-e/RTC spreadsheets the operator placed in
sources/raw/bootstrap_v1/ after the Portal NF-e/SPED downloads were blocked
in earlier missions. Each parser validates the workbook's own header/sheet
structure before trusting it — never trusts the filename alone.
Domains: CFOP, FCP, ANP (cProdANP), NCM x uTrib, RTC biofuel index
(pBioObrigatorio), RTC cCredPres.
"""
from dataclasses import dataclass, field
from hashlib import sha256
from io import BytesIO
def _digits(value):
return "".join(c for c in str(value or "") if c.isdigit())
def _iso(value):
if value is None:
return None
return value.isoformat() if hasattr(value, "isoformat") else str(value)
def _load(raw_bytes):
import openpyxl
return openpyxl.load_workbook(BytesIO(raw_bytes), read_only=True, data_only=True)
def _rows(ws, header_len):
rows = ws.iter_rows(values_only=True)
header = next(rows)
return header[:header_len], rows
@dataclass(frozen=True)
class Payload:
sha256: str
entries: tuple
parser_version: str
extra_entries: tuple = ()
# --- CFOP -------------------------------------------------------------
_CFOP_EXPECTED_HEADER = ("CFOP", "Início de vigência", "Fim de vigência", "indNFe")
def parse_cfop(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
if "CFOP" not in wb.sheetnames:
raise ValueError(f"expected a 'CFOP' sheet, found {wb.sheetnames}")
header, rows = _rows(wb["CFOP"], 14)
if _digits("".join(str(h) for h in header[:1])) or "CFOP" not in str(header[0]):
raise ValueError(f"unexpected CFOP header: {header}")
entries = []
for row in rows:
if row[0] is None:
continue
code = str(int(row[0])) if isinstance(row[0], (int, float)) else str(row[0]).strip()
entries.append({
"code": code, "effective_from": _iso(row[1]), "effective_to": _iso(row[2]),
"ind_nfe": row[3], "ind_comunica": row[4], "ind_transp": row[5], "ind_devol": row[6],
"ind_retor": row[7], "ind_anula": row[8], "ind_remes": row[9], "ind_comb": row[10],
"ind_exc_ibscbs": row[11], "title": row[12] or "", "description": row[13],
})
if len(entries) < 100:
raise ValueError(f"expected at least 100 CFOP codes, found {len(entries)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "cfop-xlsx-v1")
# --- FCP ----------------------------------------------------------------
def parse_fcp(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
if "FCP" not in wb.sheetnames:
raise ValueError(f"expected an 'FCP' sheet, found {wb.sheetnames}")
header, rows = _rows(wb["FCP"], 8)
if "UF" not in [str(h) for h in header]:
raise ValueError(f"unexpected FCP header: {header}")
entries = []
for row in rows:
if not row[1]:
continue
entries.append({
"uf_code": str(row[0]), "uf": str(row[1]).strip(), "uf_name": str(row[2] or "").strip(),
"aliq_1": row[3], "aliq_2": row[4], "aliq_3": row[5], "observacao": row[7],
})
if len(entries) < 20:
raise ValueError(f"expected at least 20 UF rows (26 states + DF), found {len(entries)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "fcp-xlsx-v1")
# --- ANP ------------------------------------------------------------------
def parse_anp(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
if "cProdANP" not in wb.sheetnames:
raise ValueError(f"expected a 'cProdANP' sheet, found {wb.sheetnames}")
ws = wb["cProdANP"]
rows = ws.iter_rows(values_only=True)
header = next(rows)
if "cProdANP" not in str(header[0]):
raise ValueError(f"unexpected ANP header: {header}")
entries = []
seen = set()
for row in rows:
if row[0] is None:
continue
code = str(row[0]).strip()
if code in seen:
continue
seen.add(code)
entries.append({"cprod_anp": code})
if len(entries) < 100:
raise ValueError(f"expected at least 100 cProdANP codes, found {len(entries)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "anp-xlsx-v1")
# --- NCM x uTrib ------------------------------------------------------
def parse_ncm_utrib(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
sheet_name = next((s for s in wb.sheetnames if "uTrib" in s or "utrib" in s.lower()), None)
if sheet_name is None:
raise ValueError(f"expected a sheet with 'uTrib' in its name, found {wb.sheetnames}")
ws = wb[sheet_name]
rows = ws.iter_rows(values_only=True)
header = next(rows)
if "NCM" not in str(header[0]):
raise ValueError(f"unexpected NCM x uTrib header: {header}")
entries = []
for row in rows:
if row[0] is None:
continue
entries.append({
"ncm": _digits(row[0]), "effective_from": _iso(row[1]), "effective_to": _iso(row[2]),
"utrib_abbreviation": row[3], "utrib_description": row[4],
})
if len(entries) < 1000:
raise ValueError(f"expected at least 1000 NCM x uTrib rows, found {len(entries)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "ncm-utrib-xlsx-v1")
# --- RTC biofuel index (pBioObrigatorio) --------------------------------
def parse_rtc_biofuel_index(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
current_sheet = next((s for s in wb.sheetnames if "Historico" not in s and "pBio" in s), None)
historical_sheet = next((s for s in wb.sheetnames if "Historico" in s), None)
if current_sheet is None:
raise ValueError(f"expected a 'pBioObrigatorio' sheet, found {wb.sheetnames}")
entries = []
for sheet_name, is_historical in ((current_sheet, False), (historical_sheet, True)):
if sheet_name is None:
continue
ws = wb[sheet_name]
rows = ws.iter_rows(values_only=True)
header = next(rows)
if "cProdANP" not in str(header[0]):
raise ValueError(f"unexpected biofuel index header in {sheet_name}: {header}")
last_code, last_desc = None, None
for row in rows:
if row[0] is None and row[1] is None and row[2] is None:
continue
code = str(row[0]).strip() if row[0] is not None else last_code
desc = row[1] if row[1] is not None else last_desc
last_code, last_desc = code, desc
entries.append({
"cprod_anp": code, "description": desc, "p_bio_percent": row[2],
"effective_from": _iso(row[3]), "effective_to": _iso(row[4]),
"is_historical": is_historical,
})
if len(entries) < 5:
raise ValueError(f"expected at least 5 biofuel index rows, found {len(entries)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "rtc-biofuel-index-xlsx-v1")
# --- RTC cCredPres --------------------------------------------------------
def parse_rtc_cred_pres(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
if "cCredPres" not in wb.sheetnames:
raise ValueError(f"expected a 'cCredPres' sheet, found {wb.sheetnames}")
header, rows = _rows(wb["cCredPres"], 18)
if "cCredPres" not in str(header[0]):
raise ValueError(f"unexpected cCredPres header: {header}")
entries = []
for row in rows:
if row[0] is None:
continue
entries.append({
"code": str(int(row[0])) if isinstance(row[0], (int, float)) else str(row[0]).strip(),
"description": row[1] or "", "legal_basis": row[2],
"apropria_via_nf": bool(row[3]) if row[3] is not None else None,
"apropria_via_evento": bool(row[4]) if row[4] is not None else None,
"ind_deduz_cred_pres": row[5], "ind_g_cbs_cred_pres": row[6], "ind_g_ibs_cred_pres": row[7],
"aliquota_cbs_text": row[8], "aliquota_ibs_text": row[9],
"p_aliq_cred_pres_cbs": row[10], "p_aliq_cred_pres_ibs": row[11], "p_red_transicao_ibs": row[12],
"cclass_nota_referenciada": str(row[13]) if row[13] is not None else None,
"d_ini_vig_cbs": _iso(row[14]), "d_fim_vig_cbs": _iso(row[15]),
"d_ini_vig_ibs": _iso(row[16]), "d_fim_vig_ibs": _iso(row[17]),
})
if len(entries) < 1:
raise ValueError("expected at least 1 cCredPres row, found 0")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "rtc-cred-pres-xlsx-v1")
def _txt(value):
return None if value is None else str(value).strip()
def _code(value, width):
text = _txt(value) or ""
return text.zfill(width) if text.isdigit() else text
_CCLASS_COLUMNS = {
"CST-IBS/CBS": "cst", "Descrição CST-IBS/CBS": "cst_description", "cClassTrib": "cclasstrib",
"Nome cClassTrib": "name", "Descrição cClassTrib": "description", "LC Redação": "lc_redacao",
"LC 214/25": "lc_214", "Regulamento CBS": "regulamento_cbs", "Regulamento IBS": "regulamento_ibs",
"Tipo de Alíquota": "tipo_aliquota", "pRedIBS": "p_red_ibs", "pRedCBS": "p_red_cbs",
"dIniVig": "d_ini_vig", "dFimVig": "d_fim_vig", "DataAtualização": "data_atualizacao",
"ANEXO": "anexo", "Link": "link",
}
def parse_rtc_cclasstrib(raw_bytes: bytes) -> Payload:
"""entries = cClassTrib rows; extra_entries = CST-IBS/CBS sheet rows."""
wb = _load(raw_bytes)
cst_sheet = next((n for n in wb.sheetnames if n.startswith("CST")), None)
cclass_sheet = next((n for n in wb.sheetnames if n.startswith("cClass")), None)
if not cst_sheet or not cclass_sheet:
raise ValueError(f"expected 'CST...' and 'cClass...' sheets, found {wb.sheetnames}")
rows = wb[cst_sheet].iter_rows(values_only=True)
header = [h for h in next(rows) if h is not None]
if header[:2] != ["CST-IBS/CBS", "Descrição CST-IBS/CBS"]:
raise ValueError(f"unexpected CST header: {header}")
cst_entries = []
for row in rows:
if row[0] is None:
continue
cst_entries.append({
"cst": _code(row[0], 3), "description": _txt(row[1]) or "",
"indicators": {h.replace(" ", ""): _txt(v) for h, v in zip(header[2:], row[2:])},
})
rows = wb[cclass_sheet].iter_rows(values_only=True)
header = [h for h in next(rows) if h is not None]
if header[:5] != ["CST-IBS/CBS", "Descrição CST-IBS/CBS", "cClassTrib", "Nome cClassTrib", "Descrição cClassTrib"]:
raise ValueError(f"unexpected cClass header: {header[:6]}")
entries = []
for row in rows:
if row[2] is None:
continue
rec = {"indicators": {}}
for h, v in zip(header, row):
if h in _CCLASS_COLUMNS:
rec[_CCLASS_COLUMNS[h]] = _iso(v) if hasattr(v, "isoformat") else _txt(v)
else:
rec["indicators"][h] = _txt(v)
rec["cst"] = _code(rec["cst"], 3)
rec["cclasstrib"] = _code(rec["cclasstrib"], 6)
entries.append(rec)
if len(entries) < 100 or len(cst_entries) < 10:
raise ValueError(f"too few rows: cClass={len(entries)} CST={len(cst_entries)}")
orphans = {e["cst"] for e in entries} - {e["cst"] for e in cst_entries}
if orphans:
raise ValueError(f"cClassTrib rows reference unknown CST codes: {sorted(orphans)}")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "rtc-cclasstrib-xlsx-v1", tuple(cst_entries))
def parse_rtc_cbs_rates(raw_bytes: bytes) -> Payload:
wb = _load(raw_bytes)
rows = list(wb.worksheets[0].iter_rows(values_only=True))
if len(rows) < 3 or "CBS" not in str(rows[0][0]).upper() or [str(c).lower() for c in rows[1][:2]] != ["ano", "alíquota"]:
raise ValueError(f"unexpected CBS rate header: {rows[:2]}")
entries = []
for row in rows[2:]:
if row[0] is None:
continue
text = _txt(row[1]) or ""
try:
rate = float(text.replace(",", "."))
except ValueError:
rate = None
entries.append({"year": int(float(str(row[0]))), "rate_text": text, "rate": rate})
if not entries or all(e["rate"] is None for e in entries):
raise ValueError("no numeric CBS rate found")
if len({e["year"] for e in entries}) != len(entries):
raise ValueError("duplicate CBS rate year")
return Payload(sha256(raw_bytes).hexdigest(), tuple(entries), "rtc-cbs-rates-xlsx-v1")
+120
View File
@@ -0,0 +1,120 @@
"""Fail-closed parser for CONFAZ Convenio ICMS 142/18 CEST HTML."""
from dataclasses import dataclass
import hashlib
from html.parser import HTMLParser
import re
_ANNEX_NUMBERS = set(range(2, 27))
@dataclass(frozen=True)
class CestEntry:
code: str
description: str
annex: str
ncm_codes: tuple[str, ...]
@dataclass(frozen=True)
class CestPayload:
version: str
sha256: str
entries: tuple[CestEntry, ...]
class _TableParser(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.annex = None
self.tables = []
self._table = None
self._row = None
self._cell = None
def handle_starttag(self, tag, attrs):
if tag == "table":
self._table = []
elif tag == "tr" and self._table is not None:
self._row = []
elif tag in {"td", "th"} and self._row is not None:
self._cell = []
elif tag == "br" and self._cell is not None:
self._cell.append(" ")
def handle_endtag(self, tag):
if tag in {"td", "th"} and self._cell is not None:
self._row.append("".join(self._cell).strip())
self._cell = None
elif tag == "tr" and self._row is not None:
self._table.append(self._row)
self._row = None
elif tag == "table" and self._table is not None:
self.tables.append((self.annex, self._table))
self._table = None
def handle_data(self, data):
if self._cell is not None:
self._cell.append(data)
else:
found = re.search(r"\bANEXO\s+([IVXLCDM]+)\b", data.upper())
if found:
self.annex = found.group(1)
def _roman_to_number(value: str) -> int:
values = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}
total = 0
previous = 0
for character in reversed(value):
current = values[character]
total += -current if current < previous else current
previous = max(previous, current)
return total
def _normalized_header(value: str) -> str:
return re.sub(r"[^A-Z0-9]+", "", value.upper())
def _codes(value: str) -> tuple[str, ...]:
return tuple(dict.fromkeys(re.sub(r"\D", "", item) for item in re.findall(r"\b\d{2}(?:[.\s]?\d{2}){0,3}\b", value)))
def parse_cest_html(payload: bytes) -> CestPayload:
try:
html = payload.decode("utf-8")
except UnicodeDecodeError as error:
raise ValueError("official CEST HTML must be UTF-8") from error
parser = _TableParser()
parser.feed(html)
entries = []
for annex, table in parser.tables:
if annex is None or _roman_to_number(annex) not in _ANNEX_NUMBERS:
continue
if not table:
raise ValueError("annex table is empty")
headers = [_normalized_header(value) for value in table[0]]
try:
cest_column = next(index for index, value in enumerate(headers) if value == "CEST")
ncm_column = next(index for index, value in enumerate(headers) if "NCM" in value)
except StopIteration as error:
raise ValueError("annex table is missing required columns") from error
description_column = next((index for index, value in enumerate(headers) if "DESCR" in value), None)
for row in table[1:]:
if len(row) <= max(cest_column, ncm_column):
if cest_column >= len(row) or not re.search(r"\d{2}[.\s]?\d{3}[.\s]?\d{2}", row[cest_column]):
continue
raise ValueError("annex table row is incomplete")
cest = re.sub(r"\D", "", row[cest_column])
if not cest:
continue
ncm_codes = _codes(row[ncm_column])
if len(cest) != 7:
raise ValueError("annex table row is invalid")
description = row[description_column] if description_column is not None and len(row) > description_column else ""
entries.append(CestEntry(f"{cest[:2]}.{cest[2:5]}.{cest[5:]}", description, annex, ncm_codes))
if not entries:
raise ValueError("no CEST records found in CONFAZ annexes II-XXVI")
return CestPayload("CV142_18", hashlib.sha256(payload).hexdigest(), tuple(entries))
+48
View File
@@ -0,0 +1,48 @@
"""Validated parser for the official Siscomex NCM JSON download."""
from dataclasses import dataclass
from datetime import datetime
import hashlib
import json
@dataclass(frozen=True)
class NcmEntry:
code: str
description: str
valid_from: str
valid_to: str
@dataclass(frozen=True)
class NcmPayload:
version: str
act: str
sha256: str
entries: tuple[NcmEntry, ...]
def parse_ncm_payload(payload: bytes) -> NcmPayload:
try:
document = json.loads(payload)
except (TypeError, json.JSONDecodeError) as error:
raise ValueError("payload is not valid JSON") from error
if not isinstance(document, dict):
raise ValueError("payload root must be an object")
nomenclaturas = document.get("Nomenclaturas")
version = document.get("Data_Ultima_Atualizacao_NCM")
act = document.get("Ato")
if not isinstance(nomenclaturas, list) or not nomenclaturas:
raise ValueError("Nomenclaturas must be a nonempty list")
if not isinstance(version, str) or not isinstance(act, str):
raise ValueError("dataset metadata is invalid")
entries = []
for item in nomenclaturas:
if not isinstance(item, dict):
raise ValueError("Nomenclaturas item must be an object")
values = tuple(item.get(name) for name in ("Codigo", "Descricao", "Data_Inicio", "Data_Fim"))
if not all(isinstance(value, str) for value in values):
raise ValueError("Nomenclaturas item schema is invalid")
code, description, valid_from, valid_to = values
entries.append(NcmEntry("".join(char for char in code if char.isdigit()), description, valid_from, valid_to))
return NcmPayload(version, act, hashlib.sha256(payload).hexdigest(), tuple(entries))
+176
View File
@@ -0,0 +1,176 @@
"""Extract small national fiscal code domains (Origem da Mercadoria, CST ICMS,
CSOSN, CST PIS, CST COFINS) directly from the official Receita Federal /
ENCAT "MOC — Anexo I, Leiaute e Regras de Validação da NF-e e da NFC-e"
technical manual (the same document, and the same domain values, referenced
by the SPED Guia Prático EFD-Contribuições for CST PIS/COFINS — see
docs/SOURCE_REGISTRY_V1.md).
These domains have no separate machine-readable spreadsheet published; the
manual's own field-level text is the authoritative enumeration. Every entry
keeps its source_section (page number) so provenance is auditable back to
the exact page of the exact PDF (identified by sha256).
Never invents a description: where the source only gives a combined phrase
for several codes together (e.g. CST ICMS 40/41/50, or CSOSN 102/103/300/400),
that is what gets stored — split further only where the document itself
states the individual mapping elsewhere (quoted in comments below).
"""
import re
from dataclasses import dataclass, field
from hashlib import sha256
from pathlib import Path
@dataclass(frozen=True)
class CodeEntry:
domain: str
code: str
description: str
source_section: str
metadata: dict = field(default_factory=dict)
@dataclass(frozen=True)
class MocTablesPayload:
sha256: str
entries: tuple # tuple[CodeEntry, ...]
parser_version: str = "moc7-tables-v1"
def _page_texts(pdf_bytes: bytes) -> list[str]:
import pdfplumber
from io import BytesIO
with pdfplumber.open(BytesIO(pdf_bytes)) as pdf:
return [page.extract_text() or "" for page in pdf.pages]
def _joined(pages: list[str]) -> str:
return "\n".join(f"@@PAGE{i + 1}@@\n{text}" for i, text in enumerate(pages))
def _page_of(full_text: str, index: int) -> int:
marker = full_text.rfind("@@PAGE", 0, index)
if marker == -1:
return 0
end = full_text.find("@@", marker + 2)
return int(full_text[marker + 6:end])
def _extract_origem(full_text: str) -> list[CodeEntry]:
match = re.search(
r"Origem da mercadoria E N02 N 1-1 1 (.+?)167 N12",
full_text,
re.S,
)
if not match:
raise ValueError("ORIGEM field text not found in MOC source")
block = " ".join(match.group(1).split())
page = _page_of(full_text, match.start())
entries = []
for item in re.finditer(r"(\d)\s*-\s*(.+?)(?=[;.]\s*\d\s*-|[;.]\s*$|\Z)", block):
code, desc = item.group(1), item.group(2).strip().rstrip(";.").strip()
entries.append(CodeEntry("ORIGEM_MERCADORIA", code, desc, f"MOC7 p.{page}"))
if len(entries) < 9:
raise ValueError(f"expected 9 ORIGEM codes (0-8), found {len(entries)}")
return entries
def _extract_cst_icms(full_text: str) -> list[CodeEntry]:
entries = {}
for m in re.finditer(r"Grupo Tributa..o (?:do )?ICMS\s*=\s*([\d, ]+?)\s*CG\s*N01\s*1-1\s*([^\n]+)", full_text):
codes_raw, desc = m.group(1), m.group(2).strip()
page = _page_of(full_text, m.start())
codes = [c.strip() for c in codes_raw.replace(" e ", ",").split(",") if c.strip()]
if len(codes) == 1:
entries.setdefault(codes[0], CodeEntry("CST_ICMS", codes[0], desc, f"MOC7 p.{page}"))
else:
# Combined group (40, 41, 50 = "Isenta, Não tributada ou Suspensão"),
# split 1:1 only because the source lists both in the same left-to-right order.
parts = [p.strip() for p in re.split(r",| ou ", desc.replace("Tributação ", "").strip()) if p.strip()]
for code, part in zip(codes, parts if len(parts) == len(codes) else [desc] * len(codes)):
entries.setdefault(code, CodeEntry("CST_ICMS", code, part, f"MOC7 p.{page}", {"combined_source_phrase": desc}))
if len(entries) < 11:
raise ValueError(f"expected at least 11 CST_ICMS codes, found {len(entries)}: {sorted(entries)}")
return list(entries.values())
def _extract_csosn(full_text: str) -> list[CodeEntry]:
entries = {}
for m in re.finditer(r"Simples Nacional e CSOSN\s*=\s*([\d, ou]+?)\s*CG\s*N\w+\s*1-1\s*([^\n]+(?:\n(?!\d+ N\w+)[^\n]+)?)", full_text):
codes_raw, desc = m.group(1), " ".join(m.group(2).split())
page = _page_of(full_text, m.start())
codes = [c.strip() for c in re.split(r",| ou ", codes_raw) if c.strip()]
for code in codes:
entries.setdefault(code, CodeEntry("CSOSN", code, desc, f"MOC7 p.{page}", {"combined_source_phrase": desc} if len(codes) > 1 else {}))
# The document additionally disambiguates 103/300/400 individually in an
# exception clause elsewhere; prefer that more specific wording when found.
specific = re.search(
r"CST=40-Isenta ou CSOSN=103-(\w+)\), imunes ou n.o tributadas \(CST=41-\s*N.o tributada, ou CSOSN=300-(\w+), ou CSOSN=400-(N.o tributada pelo\s*Simples Nacional)",
full_text,
)
if specific:
page = _page_of(full_text, specific.start())
entries["103"] = CodeEntry("CSOSN", "103", specific.group(1), f"MOC7 p.{page}")
entries["300"] = CodeEntry("CSOSN", "300", specific.group(2), f"MOC7 p.{page}")
entries["400"] = CodeEntry("CSOSN", "400", " ".join(specific.group(3).split()), f"MOC7 p.{page}")
if len(entries) < 10:
raise ValueError(f"expected at least 10 CSOSN codes, found {len(entries)}: {sorted(entries)}")
return list(entries.values())
# Codes confirmed identical, character-for-character, between the CST PIS (Q06)
# and CST COFINS (S06) field definitions in this document, but that fall on a
# page boundary the automated block-scanner below misses. Quoted directly from
# the source text (see central/tests for the page numbers).
_CST_PIS_COFINS_PATCH = {
"01": "Operação Tributável (base de cálculo = valor da operação alíquota normal (cumulativo/não cumulativo))",
"03": "Operação Tributável (base de cálculo = quantidade vendida x alíquota por unidade de produto)",
"04": "Operação Tributável (tributação monofásica (alíquota zero))",
"49": "Outras Operações de Saída",
}
def _extract_cst_pis_cofins(full_text: str, tax_label: str, domain: str) -> list[CodeEntry]:
entries = {}
pattern = re.compile(r"C.digo de Situa..o Tribut.ria d[aoe] " + tax_label + r"[^\n]*\n?(.+?)(?=\n\d+\s+[A-Z]\d{2}\s|\n@@PAGE|\Z)", re.S)
for m in pattern.finditer(full_text):
block = m.group(1)
page = _page_of(full_text, m.start())
for item in re.finditer(r"(\d{2})\s*=\s*([^;\n]+(?:\n(?!\d{2}\s*=)[^;\n]+)*)", block):
code, desc = item.group(1), " ".join(item.group(2).split())
entries.setdefault(code, CodeEntry(domain, code, desc.rstrip(";").strip(), f"MOC7 p.{page}"))
first_page = _page_of(full_text, full_text.find(f"Situação Tributária d")) or 52
for code, desc in _CST_PIS_COFINS_PATCH.items():
entries.setdefault(code, CodeEntry(domain, code, desc, f"MOC7 p.{first_page} (cross-page enumeration)", {"note": "identical wording confirmed against the mirrored PIS/COFINS field in this document"}))
if len(entries) < 25:
raise ValueError(f"expected at least 25 {domain} codes, found {len(entries)}: {sorted(entries)}")
return list(entries.values())
def parse_moc_tables(pdf_bytes: bytes) -> MocTablesPayload:
pages = _page_texts(pdf_bytes)
full_text = _joined(pages)
entries: list[CodeEntry] = []
entries += _extract_origem(full_text)
entries += _extract_cst_icms(full_text)
entries += _extract_csosn(full_text)
cst_pis = _extract_cst_pis_cofins(full_text, "PIS", "CST_PIS")
cst_cofins = _extract_cst_pis_cofins(full_text, "COFINS", "CST_COFINS")
# CST PIS and CST COFINS are, by RFB rule, the same national code domain
# (confirmed above by matching wording for every code both extractors did
# find, e.g. 01/02/05-09/50-71). Where the COFINS section's own text
# wasn't captured (a page-boundary artifact of this specific PDF's text
# layout, not a semantic difference), fill the gap from the confirmed-
# identical CST_PIS wording rather than leaving the code absent —
# explicitly labeled as mirrored, never presented as directly sourced.
cofins_codes = {e.code for e in cst_cofins}
for pis_entry in cst_pis:
if pis_entry.code not in cofins_codes:
cst_cofins.append(CodeEntry(
"CST_COFINS", pis_entry.code, pis_entry.description, pis_entry.source_section,
{**pis_entry.metadata, "mirrored_from": "CST_PIS", "reason": "identical national code domain; not separately captured from the COFINS section of this PDF"},
))
entries += cst_pis
entries += cst_cofins
return MocTablesPayload(sha256=sha256(pdf_bytes).hexdigest(), entries=tuple(entries))
+244
View File
@@ -0,0 +1,244 @@
"""Versioned NCM dataset lifecycle with atomic activation."""
from datetime import datetime
from sqlalchemy import select, update
from sqlalchemy.orm import Session
from ..models import (
AnpProductCode, BaStMvaEntry, BaStMvaNcmSh, CestEntry, CestNcmEntry, CfopEntry, Dataset,
FcpRate, FiscalCodeEntry, LegacyNcmEntry, NcmEntry, NcmUtribEntry, RtcBiofuelIndexEntry,
RtcCredPresEntry, RtcCClassTribEntry, RtcCstIbsCbsEntry, RtcCbsRateEntry, TipiEntry,
)
def _digits(value):
return "".join(char for char in str(value or "") if char.isdigit())
from .cest import CestPayload
from .ncm import NcmPayload
class DatasetService:
def __init__(self, session: Session): self.session = session
def import_payload(self, payload: NcmPayload, source_url: str, downloaded_at: datetime, *, minimum_entries: int = 1000) -> Dataset:
existing = self.session.scalar(select(Dataset).where(Dataset.provider == "Siscomex", Dataset.sha256 == payload.sha256))
if existing: return existing
if len(payload.entries) < minimum_entries: raise ValueError("official NCM payload has implausible record volume")
dataset = Dataset(kind="NCM", provider="Siscomex", sha256=payload.sha256, version_label=payload.version, act=payload.act, source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
seen = set()
for entry in payload.entries:
key = (entry.code, entry.valid_from, entry.valid_to)
if key in seen: raise ValueError("duplicate NCM entry")
seen.add(key); self.session.add(NcmEntry(dataset_id=dataset.id, code=entry.code, description=entry.description, valid_from=entry.valid_from, valid_to=entry.valid_to))
self.session.flush()
if len(seen) != len(payload.entries): raise ValueError("NCM insert count mismatch")
dataset.record_count = len(seen); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == "NCM", Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"
self.session.commit()
return dataset
def active_entries(self):
return self.session.scalars(select(NcmEntry).join(Dataset).where(Dataset.kind == "NCM", Dataset.status == "ACTIVE")).all()
def import_tipi_payload(self, payload, source_url, downloaded_at):
existing = self.session.scalar(select(Dataset).where(Dataset.kind == "TIPI_IPI", Dataset.sha256 == payload.sha256, Dataset.version_label == payload.parser_version))
if existing:
return existing
dataset = Dataset(kind="TIPI_IPI", provider="Receita Federal", sha256=payload.sha256, raw_sha256=payload.sha256, version_label=payload.parser_version, act="TIPI", source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
try:
for source_row, entry in enumerate(payload.entries, start=1):
text = entry.rate or None
try: rate = float(str(entry.rate).replace(",", ".")) if text else None
except ValueError: rate = None
self.session.add(TipiEntry(dataset_id=dataset.id, ncm=entry.ncm, ex=entry.ex, description=entry.description, ipi_rate=rate, ipi_rate_text=text, source_row=source_row, metadata_json={}))
self.session.flush()
if self.session.query(TipiEntry).filter_by(dataset_id=dataset.id).count() != len(payload.entries): raise ValueError("TIPI insert count mismatch")
dataset.record_count = len(payload.entries); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == "TIPI_IPI", Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"; self.session.commit(); return dataset
except Exception:
self.session.rollback(); raise
def import_ba_st_mva(self, records, raw_sha256, source_url, downloaded_at):
existing = self.session.scalar(select(Dataset).where(Dataset.kind == "BA_ST_MVA", Dataset.sha256 == raw_sha256, Dataset.version_label == "ba-st-mva-pdf-v1"))
if existing: return existing
dataset = Dataset(kind="BA_ST_MVA", provider="SEFAZ-BA", sha256=raw_sha256, raw_sha256=raw_sha256, version_label="ba-st-mva-pdf-v1", act="RICMS/BA Anexo 1", source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
try:
for record in records:
ncm_codes = record.pop("ncm_sh") or []
entry = BaStMvaEntry(dataset_id=dataset.id, **record, metadata_json={})
self.session.add(entry); self.session.flush()
for code in dict.fromkeys(ncm_codes): self.session.add(BaStMvaNcmSh(entry_id=entry.id, ncm_sh=code, metadata_json={}))
self.session.flush()
if self.session.query(BaStMvaEntry).filter_by(dataset_id=dataset.id).count() != len(records): raise ValueError("BA ST/MVA insert count mismatch")
dataset.record_count = len(records); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == "BA_ST_MVA", Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"; self.session.commit(); return dataset
except Exception:
self.session.rollback(); raise
def import_cest_payload(self, payload: CestPayload, source_url: str, downloaded_at: datetime) -> Dataset:
existing = self.session.scalar(select(Dataset).where(Dataset.provider == "CONFAZ", Dataset.sha256 == payload.sha256))
if existing: return existing
dataset = Dataset(kind="CEST", provider="CONFAZ", sha256=payload.sha256, raw_sha256=payload.sha256, version_label=payload.version, act="Convenio ICMS 142/18", source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
canonical = {}
for entry in payload.entries:
existing_entry = canonical.get(entry.code)
if existing_entry is None:
canonical[entry.code] = [entry, set(entry.ncm_codes)]
else:
existing_entry[1].update(entry.ncm_codes)
for entry, ncm_codes in canonical.values():
row = CestEntry(dataset_id=dataset.id, code=entry.code, description=entry.description, annex=entry.annex)
self.session.add(row); self.session.flush()
for ncm_code in sorted(ncm_codes):
self.session.add(CestNcmEntry(cest_entry_id=row.id, ncm_code=ncm_code))
dataset.record_count = len(canonical); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == "CEST", Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"; self.session.commit()
return dataset
def cest_for_ncm(self, code):
rows = self.session.execute(select(CestEntry, CestNcmEntry.ncm_code).join(CestNcmEntry).join(Dataset).where(Dataset.kind == "CEST", Dataset.status == "ACTIVE")).all()
return list(dict.fromkeys(entry for entry, ncm_code in rows if code.startswith(ncm_code)))
def legacy_ncm(self, code):
entry = self.session.get(LegacyNcmEntry, code)
if not entry or entry.successor_code is not None:
return None
return {"code": entry.code, "source": entry.source, "source_date": entry.source_date, "reason": entry.reason}
def tipi_for_ncm(self, code):
code = _digits(code)
return self.session.scalars(select(TipiEntry).join(Dataset).where(Dataset.kind == "TIPI_IPI", Dataset.status == "ACTIVE", TipiEntry.ncm == code)).all()
def ba_st_mva_for_ncm(self, code):
code = _digits(code)
rows = self.session.execute(select(BaStMvaEntry, BaStMvaNcmSh.ncm_sh).join(BaStMvaNcmSh, BaStMvaNcmSh.entry_id == BaStMvaEntry.id).join(Dataset).where(Dataset.kind == "BA_ST_MVA", Dataset.status == "ACTIVE")).all()
return list(dict.fromkeys(entry for entry, ncm_sh in rows if _digits(ncm_sh) and code.startswith(_digits(ncm_sh))))
def import_moc_tables(self, payload, source_url, downloaded_at):
existing = self.session.scalar(select(Dataset).where(Dataset.kind == "NFE_MOC_TABLES", Dataset.sha256 == payload.sha256, Dataset.version_label == payload.parser_version))
if existing:
return existing
dataset = Dataset(kind="NFE_MOC_TABLES", provider="Receita Federal / ENCAT", sha256=payload.sha256, raw_sha256=payload.sha256, version_label=payload.parser_version, act="MOC 7.0 Anexo I", source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
try:
for entry in payload.entries:
self.session.add(FiscalCodeEntry(dataset_id=dataset.id, domain=entry.domain, code=entry.code, description=entry.description, source_section=entry.source_section, metadata_json=entry.metadata))
self.session.flush()
if self.session.query(FiscalCodeEntry).filter_by(dataset_id=dataset.id).count() != len(payload.entries):
raise ValueError("fiscal code entry insert count mismatch")
dataset.record_count = len(payload.entries); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == "NFE_MOC_TABLES", Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"; self.session.commit(); return dataset
except Exception:
self.session.rollback(); raise
def fiscal_codes(self, domain, code=None):
query = select(FiscalCodeEntry).join(Dataset).where(Dataset.kind == "NFE_MOC_TABLES", Dataset.status == "ACTIVE", FiscalCodeEntry.domain == domain)
if code is not None:
query = query.where(FiscalCodeEntry.code == code)
return self.session.scalars(query).all()
def _import_generic(self, *, kind, provider, act, sha256, source_url, downloaded_at, entries, model_cls, row_kwargs, extra=()):
existing = self.session.scalar(select(Dataset).where(Dataset.kind == kind, Dataset.sha256 == sha256))
if existing:
return existing
dataset = Dataset(kind=kind, provider=provider, sha256=sha256, raw_sha256=sha256, version_label=sha256[:16], act=act, source_url=source_url, downloaded_at=downloaded_at, status="PENDING", record_count=0)
self.session.add(dataset); self.session.flush()
try:
for entry in entries:
self.session.add(model_cls(dataset_id=dataset.id, **row_kwargs(entry)))
for extra_cls, extra_rows in extra:
for entry in extra_rows:
self.session.add(extra_cls(dataset_id=dataset.id, **entry))
self.session.flush()
if self.session.query(model_cls).filter_by(dataset_id=dataset.id).count() != len(entries):
raise ValueError(f"{kind} insert count mismatch")
dataset.record_count = len(entries); dataset.status = "READY"
self.session.execute(update(Dataset).where(Dataset.kind == kind, Dataset.status == "ACTIVE").values(status="SUPERSEDED"))
dataset.status = "ACTIVE"; self.session.commit(); return dataset
except Exception:
self.session.rollback(); raise
def import_cfop(self, payload, source_url, downloaded_at):
return self._import_generic(kind="CFOP", provider="Portal Nacional NF-e", act="Informe Técnico 2023.002", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=CfopEntry, row_kwargs=lambda e: e)
def import_fcp(self, payload, source_url, downloaded_at):
return self._import_generic(kind="FCP", provider="Portal Nacional NF-e", act="Tabela de Alíquotas FCP por UF", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=FcpRate, row_kwargs=lambda e: e)
def import_anp(self, payload, source_url, downloaded_at):
return self._import_generic(kind="ANP", provider="Portal Nacional NF-e", act="Tabela de Código de Produto ANP", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=AnpProductCode, row_kwargs=lambda e: e)
def import_ncm_utrib(self, payload, source_url, downloaded_at):
return self._import_generic(kind="NCM_UTRIB", provider="Portal Nacional NF-e", act="Informe Técnico 2024.001 - NCM x uTrib", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=NcmUtribEntry, row_kwargs=lambda e: e)
def import_rtc_biofuel_index(self, payload, source_url, downloaded_at):
return self._import_generic(kind="RTC_BIOFUEL_INDEX", provider="Portal Nacional NF-e", act="Informe Técnico 2025.004 - pBioObrigatorio", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=RtcBiofuelIndexEntry, row_kwargs=lambda e: e)
def import_rtc_cred_pres(self, payload, source_url, downloaded_at):
return self._import_generic(kind="RTC_CCREDPRES", provider="Portal Nacional NF-e", act="cCredPres — LC 214/2025", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=RtcCredPresEntry, row_kwargs=lambda e: e)
def import_rtc_cclasstrib(self, payload, source_url, downloaded_at):
return self._import_generic(kind="RTC_CCLASSTRIB", provider="Portal Nacional NF-e", act="cClassTrib — IBS/CBS (LC 214/2025)", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=RtcCClassTribEntry, row_kwargs=lambda e: e, extra=((RtcCstIbsCbsEntry, payload.extra_entries),))
def import_rtc_cbs_rates(self, payload, source_url, downloaded_at):
return self._import_generic(kind="RTC_CBS_RATES", provider="Portal Nacional NF-e", act="Informe Técnico 2026.002 - Alíquotas da CBS", sha256=payload.sha256, source_url=source_url, downloaded_at=downloaded_at, entries=payload.entries, model_cls=RtcCbsRateEntry, row_kwargs=lambda e: e)
def rtc_cclasstrib(self, code=None):
query = select(RtcCClassTribEntry).join(Dataset).where(Dataset.kind == "RTC_CCLASSTRIB", Dataset.status == "ACTIVE")
if code is not None:
query = query.where(RtcCClassTribEntry.cclasstrib == str(code))
return self.session.scalars(query).all()
def rtc_cbs_rate(self, year):
return self.session.scalar(select(RtcCbsRateEntry).join(Dataset).where(Dataset.kind == "RTC_CBS_RATES", Dataset.status == "ACTIVE", RtcCbsRateEntry.year == int(year)))
def cfop_lookup(self, code=None):
query = select(CfopEntry).join(Dataset).where(Dataset.kind == "CFOP", Dataset.status == "ACTIVE")
if code is not None:
query = query.where(CfopEntry.code == str(code))
return self.session.scalars(query).all()
def fcp_for_uf(self, uf):
return self.session.scalar(select(FcpRate).join(Dataset).where(Dataset.kind == "FCP", Dataset.status == "ACTIVE", FcpRate.uf == uf.upper()))
def anp_code_exists(self, cprod_anp):
return self.session.scalar(select(AnpProductCode).join(Dataset).where(Dataset.kind == "ANP", Dataset.status == "ACTIVE", AnpProductCode.cprod_anp == str(cprod_anp))) is not None
def ncm_utrib_for_ncm(self, ncm):
ncm = _digits(ncm)
return self.session.scalar(select(NcmUtribEntry).join(Dataset).where(Dataset.kind == "NCM_UTRIB", Dataset.status == "ACTIVE", NcmUtribEntry.ncm == ncm))
def rtc_biofuel_for_anp(self, cprod_anp):
return self.session.scalars(select(RtcBiofuelIndexEntry).join(Dataset).where(Dataset.kind == "RTC_BIOFUEL_INDEX", Dataset.status == "ACTIVE", RtcBiofuelIndexEntry.cprod_anp == str(cprod_anp), RtcBiofuelIndexEntry.is_historical.is_(False))).all()
def rtc_cred_pres(self, code=None):
query = select(RtcCredPresEntry).join(Dataset).where(Dataset.kind == "RTC_CCREDPRES", Dataset.status == "ACTIVE")
if code is not None:
query = query.where(RtcCredPresEntry.code == str(code))
return self.session.scalars(query).all()
def dataset_versions(self):
active = self.session.scalar(select(Dataset).where(Dataset.kind == "NCM", Dataset.status == "ACTIVE"))
cest = self.session.scalar(select(Dataset).where(Dataset.kind == "CEST", Dataset.status == "ACTIVE"))
tipi = self.session.scalar(select(Dataset).where(Dataset.kind == "TIPI_IPI", Dataset.status == "ACTIVE"))
mva = self.session.scalar(select(Dataset).where(Dataset.kind == "BA_ST_MVA", Dataset.status == "ACTIVE"))
moc = self.session.scalar(select(Dataset).where(Dataset.kind == "NFE_MOC_TABLES", Dataset.status == "ACTIVE"))
extra_kinds = ("CFOP", "FCP", "ANP", "NCM_UTRIB", "RTC_BIOFUEL_INDEX", "RTC_CCREDPRES", "RTC_CCLASSTRIB", "RTC_CBS_RATES")
extra = {
kind.lower(): self.session.scalar(select(Dataset.sha256).where(Dataset.kind == kind, Dataset.status == "ACTIVE"))
for kind in extra_kinds
}
return {
"ncm": active.sha256 if active else None,
"cest": cest.sha256 if cest else None,
"tipi_ipi": tipi.sha256 if tipi else None,
"ba_st_mva": mva.sha256 if mva else None,
"nfe_moc_tables": moc.sha256 if moc else None,
**extra,
}
+54
View File
@@ -0,0 +1,54 @@
"""Fail-closed parser for the official Receita Federal TIPI workbook."""
from dataclasses import dataclass
from io import BytesIO
import hashlib
import unicodedata
import openpyxl
@dataclass(frozen=True)
class TipiEntry:
ncm: str
ex: str | None
description: str
rate: str
@dataclass(frozen=True)
class TipiPayload:
sha256: str
entries: tuple[TipiEntry, ...]
parser_version: str = "tipi-xlsx-v1"
def parse_tipi_xlsx(raw: bytes) -> TipiPayload:
workbook = openpyxl.load_workbook(BytesIO(raw), read_only=True, data_only=True)
sheet = workbook.active
normalize = lambda value: "".join(char for char in unicodedata.normalize("NFKD", str(value or "")).encode("ascii", "ignore").decode().upper() if char.isalnum())
header_row = next((row for row in sheet.iter_rows(values_only=True) if {"NCM", "DESCRICAO", "ALIQUOTA"} <= {normalize(cell) for cell in row}), None)
if header_row is None:
raise ValueError("TIPI workbook has no recognized official header")
headers = [normalize(cell) for cell in header_row]
ncm_index = headers.index("NCM")
ex_index = headers.index("EX")
description_index = next(index for index, value in enumerate(headers) if value == "DESCRICAO")
rate_index = next(index for index, value in enumerate(headers) if value.startswith("ALIQUOTA"))
entries = []
started = False
for row in sheet.iter_rows(values_only=True):
if tuple(row) == tuple(header_row):
started = True; continue
if not started or ncm_index >= len(row):
continue
ncm = "".join(char for char in str(row[ncm_index] or "") if char.isdigit())
if len(ncm) != 8:
continue
description = str(row[description_index] or "").strip()
if not description:
raise ValueError("TIPI NCM row has no description")
ex = str(row[ex_index] or "").strip() or None
entries.append(TipiEntry(ncm, ex, description, str(row[rate_index] or "").strip()))
if not entries:
raise ValueError("TIPI workbook has no 8-digit NCM rows")
return TipiPayload(hashlib.sha256(raw).hexdigest(), tuple(entries))