"""Discover inbound fiscal evidence from SGV databases using SELECT-only access.""" from __future__ import annotations import json import os import re from pathlib import Path from typing import Any, Iterable ROOT = Path(__file__).resolve().parents[1] SENSITIVE = re.compile(r"CNPJ|CPF|XML|CHAVE|RAZAO|NOME|ENDERECO|FONE|EMAIL", re.I) TOKEN_GROUPS = { "HEADER": {"NOTA", "NF", "DOCUMENTO", "CABECALHO", "EMISSAO", "SERIE"}, "ITEM": {"ITEM", "CODPRODUTO", "CODIGO", "QTD", "QUANTIDADE", "VALOR"}, "SUPPLIER": {"FORNEC", "EMITENTE", "CNPJ", "CPF", "RAZAO"}, "KEY": {"CHAVE", "NFE", "NFCE", "ACCESS", "PROTOCOLO"}, "FISCAL_ITEM": {"NCM", "CFOP", "CST", "CSOSN", "CEST", "ORIG", "ICMS", "PIS", "COFINS", "IPI", "MVA"}, } def classify_evidence_source(table: str, columns: Iterable[str]) -> set[str]: text = f"{table} {' '.join(columns)}".upper() result = {category for category, tokens in TOKEN_GROUPS.items() if any(token in text for token in tokens)} return result or {"UNKNOWN"} def discover_from_rows(column_rows: Iterable[tuple[Any, ...]], relationship_rows: Iterable[tuple[Any, ...]]) -> dict[str, Any]: tables: dict[str, dict[str, Any]] = {} for table, field, field_type, field_length in column_rows: table = str(table).strip() field = str(field).strip() record = tables.setdefault(table, {"columns": [], "categories": [], "xml_or_blob": False}) if not SENSITIVE.search(field): record["columns"].append(field) if field_type == 261 or re.search(r"XML|BLOB|PAYLOAD|DOCUMENT", field, re.I): record["xml_or_blob"] = True for table, record in tables.items(): categories = classify_evidence_source(table, record["columns"]) record["categories"] = sorted(categories) relationships = [] for from_table, from_field, to_table, to_field in relationship_rows: relationships.append({"from_table": str(from_table).strip(), "from_field": str(from_field).strip(), "to_table": str(to_table).strip(), "to_field": str(to_field).strip()}) return {"tables": tables, "relationships": relationships} def safe_row_count(cursor: Any, table: str) -> dict[str, Any] | int: try: cursor.execute(f'SELECT COUNT(*) FROM "{table}"') return int(cursor.fetchone()[0]) except Exception as exc: # external tables can be blocked by server policy return {"status": "ROW_COUNT_UNAVAILABLE", "error_type": type(exc).__name__} def _load_env() -> None: path = ROOT / ".env" if path.exists(): for line in path.read_text(encoding="utf-8").splitlines(): if "=" in line and not line.lstrip().startswith("#"): key, _, value = line.partition("=") os.environ.setdefault(key.strip(), value.strip()) def discover_database(path: Path) -> dict[str, Any]: import fdb conn = fdb.connect( database=str(path), user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"], fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"), isolation_level=fdb.ISOLATION_LEVEL_READ_COMMITED_RO, ) try: cur = conn.cursor() cur.execute("""SELECT TRIM(rf.RDB$RELATION_NAME), TRIM(rf.RDB$FIELD_NAME), f.RDB$FIELD_TYPE, COALESCE(f.RDB$CHARACTER_LENGTH, f.RDB$FIELD_LENGTH) FROM RDB$RELATION_FIELDS rf JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME WHERE COALESCE(r.RDB$SYSTEM_FLAG, 0) = 0""") columns = cur.fetchall() cur.execute("""SELECT TRIM(rc.RDB$RELATION_NAME), TRIM(s.RDB$FIELD_NAME), TRIM(rc2.RDB$RELATION_NAME), TRIM(s2.RDB$FIELD_NAME) FROM RDB$RELATION_CONSTRAINTS rc JOIN RDB$INDEX_SEGMENTS s ON s.RDB$INDEX_NAME = rc.RDB$INDEX_NAME JOIN RDB$REF_CONSTRAINTS ref ON ref.RDB$CONSTRAINT_NAME = rc.RDB$CONSTRAINT_NAME JOIN RDB$RELATION_CONSTRAINTS rc2 ON rc2.RDB$CONSTRAINT_NAME = ref.RDB$CONST_NAME_UQ JOIN RDB$INDEX_SEGMENTS s2 ON s2.RDB$INDEX_NAME = rc2.RDB$INDEX_NAME WHERE rc.RDB$CONSTRAINT_TYPE = 'FOREIGN KEY'""") relationships = cur.fetchall() result = discover_from_rows(columns, relationships) for table, record in result["tables"].items(): record["row_count"] = safe_row_count(cur, table) result.update({"database": path.name, "mode": "READ_ONLY", "firebird_writes": 0, "xml_documents_available": any(r["xml_or_blob"] for r in result["tables"].values())}) return result finally: conn.close() def main() -> None: _load_env() paths = [ROOT / "TESTESOFICIAIS.FDB"] + sorted((ROOT / "Banco").glob("*.FDB")) output = {"artifact": "ERP_INBOUND_FISCAL_EVIDENCE_MAP_V1", "mode": "READ_ONLY", "databases": [], "firebird_writes": 0} for path in paths: try: output["databases"].append(discover_database(path)) except Exception as exc: # record unavailable evidence without guessing output["databases"].append({"database": path.name, "mode": "READ_ONLY", "status": "UNAVAILABLE", "error_type": type(exc).__name__, "firebird_writes": 0}) destination = ROOT / "docs" / "ERP_INBOUND_FISCAL_EVIDENCE_MAP_V1.json" destination.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(json.dumps({"artifact": output["artifact"], "databases": len(output["databases"]), "firebird_writes": 0})) if __name__ == "__main__": main()