baucli 1.0.151__tar.gz → 1.0.152__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {baucli-1.0.151 → baucli-1.0.152}/PKG-INFO +1 -1
  2. {baucli-1.0.151 → baucli-1.0.152}/baucli/__init__.py +1 -1
  3. {baucli-1.0.151 → baucli-1.0.152}/baucli/cli.py +148 -91
  4. {baucli-1.0.151 → baucli-1.0.152}/baucli/db_check_pdf.py +23 -8
  5. {baucli-1.0.151 → baucli-1.0.152}/baucli/db_check_report.py +44 -18
  6. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/PKG-INFO +1 -1
  7. {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_check_pdf.py +1 -1
  8. {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_check_report.py +19 -5
  9. {baucli-1.0.151 → baucli-1.0.152}/MANIFEST.in +0 -0
  10. {baucli-1.0.151 → baucli-1.0.152}/README.md +0 -0
  11. {baucli-1.0.151 → baucli-1.0.152}/baucli/apex_rebuild.py +0 -0
  12. {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlang.py +0 -0
  13. {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlang_profile.json +0 -0
  14. {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlint.py +0 -0
  15. {baucli-1.0.151 → baucli-1.0.152}/baucli/api.py +0 -0
  16. {baucli-1.0.151 → baucli-1.0.152}/baucli/canon.py +0 -0
  17. {baucli-1.0.151 → baucli-1.0.152}/baucli/config.py +0 -0
  18. {baucli-1.0.151 → baucli-1.0.152}/baucli/db/__init__.py +0 -0
  19. {baucli-1.0.151 → baucli-1.0.152}/baucli/db/mssql.py +0 -0
  20. {baucli-1.0.151 → baucli-1.0.152}/baucli/db/mysql.py +0 -0
  21. {baucli-1.0.151 → baucli-1.0.152}/baucli/db/oracle.py +0 -0
  22. {baucli-1.0.151 → baucli-1.0.152}/baucli/db/postgresql.py +0 -0
  23. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm.py +0 -0
  24. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mssql/crm_data_mssql.sql +0 -0
  25. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mssql/crm_demo_mssql.sql +0 -0
  26. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mysql/crm_data_mysql.sql +0 -0
  27. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mysql/crm_demo_mysql.sql +0 -0
  28. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/oracle/crm_demo_oracle.sql +0 -0
  29. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/postgresql/crm_data_postgresql.sql +0 -0
  30. {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/postgresql/crm_demo_postgresql.sql +0 -0
  31. {baucli-1.0.151 → baucli-1.0.152}/baucli/diffenvs.py +0 -0
  32. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig.py +0 -0
  33. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_baseline.py +0 -0
  34. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_build.py +0 -0
  35. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_install.py +0 -0
  36. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_shell.py +0 -0
  37. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_schema.py +0 -0
  38. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_tools.py +0 -0
  39. {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_workspace.py +0 -0
  40. {baucli-1.0.151 → baucli-1.0.152}/baucli/git.py +0 -0
  41. {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/__init__.py +0 -0
  42. {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi.py +0 -0
  43. {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi_canon.py +0 -0
  44. {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi_evidence.py +0 -0
  45. {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/magic.py +0 -0
  46. {baucli-1.0.151 → baucli-1.0.152}/baucli/mcp_server.py +0 -0
  47. {baucli-1.0.151 → baucli-1.0.152}/baucli/project.py +0 -0
  48. {baucli-1.0.151 → baucli-1.0.152}/baucli/rag_embed.py +0 -0
  49. {baucli-1.0.151 → baucli-1.0.152}/baucli/sample_scan.py +0 -0
  50. {baucli-1.0.151 → baucli-1.0.152}/baucli/scope_scan.py +0 -0
  51. {baucli-1.0.151 → baucli-1.0.152}/baucli/screenshot.py +0 -0
  52. {baucli-1.0.151 → baucli-1.0.152}/baucli/seed_check.py +0 -0
  53. {baucli-1.0.151 → baucli-1.0.152}/baucli/skills.py +0 -0
  54. {baucli-1.0.151 → baucli-1.0.152}/baucli/stress.py +0 -0
  55. {baucli-1.0.151 → baucli-1.0.152}/baucli/sync.py +0 -0
  56. {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/.apex/apexlang.json +0 -0
  57. {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/pages/login.apx.tpl +0 -0
  58. {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/shared-components/lists/navigation-bar.apx +0 -0
  59. {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/shared-components/themes/universal-theme/theme.apx +0 -0
  60. {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/logo-bau.jpg +0 -0
  61. {baucli-1.0.151 → baucli-1.0.152}/baucli/uikit.py +0 -0
  62. {baucli-1.0.151 → baucli-1.0.152}/baucli/unwrap.py +0 -0
  63. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/SOURCES.txt +0 -0
  64. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/dependency_links.txt +0 -0
  65. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/entry_points.txt +0 -0
  66. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/requires.txt +0 -0
  67. {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/top_level.txt +0 -0
  68. {baucli-1.0.151 → baucli-1.0.152}/pyproject.toml +0 -0
  69. {baucli-1.0.151 → baucli-1.0.152}/setup.cfg +0 -0
  70. {baucli-1.0.151 → baucli-1.0.152}/setup.py +0 -0
  71. {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_binario.py +0 -0
  72. {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_dialogo.py +0 -0
  73. {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_dominio.py +0 -0
  74. {baucli-1.0.151 → baucli-1.0.152}/tests/test_api_request_db.py +0 -0
  75. {baucli-1.0.151 → baucli-1.0.152}/tests/test_canon_anchored_markers.py +0 -0
  76. {baucli-1.0.151 → baucli-1.0.152}/tests/test_canon_multiengine.py +0 -0
  77. {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_tenant_guard.py +0 -0
  78. {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_canon.py +0 -0
  79. {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_datasnap.py +0 -0
  80. {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_evidence.py +0 -0
  81. {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_fmx.py +0 -0
  82. {baucli-1.0.151 → baucli-1.0.152}/tests/test_delta_clock.py +0 -0
  83. {baucli-1.0.151 → baucli-1.0.152}/tests/test_deploy_alias.py +0 -0
  84. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_baseline.py +0 -0
  85. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build.py +0 -0
  86. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build_extend.py +0 -0
  87. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build_new.py +0 -0
  88. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_expressividade.py +0 -0
  89. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install.py +0 -0
  90. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install_extend.py +0 -0
  91. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install_shell.py +0 -0
  92. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_project.py +0 -0
  93. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_project_new.py +0 -0
  94. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_shell.py +0 -0
  95. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_analista.py +0 -0
  96. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema.py +0 -0
  97. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema_11.py +0 -0
  98. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema_app.py +0 -0
  99. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_use_baseline.py +0 -0
  100. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_sqlcl_padrao.py +0 -0
  101. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_support_objects.py +0 -0
  102. {baucli-1.0.151 → baucli-1.0.152}/tests/test_docrev_dismiss.py +0 -0
  103. {baucli-1.0.151 → baucli-1.0.152}/tests/test_doctab_split.py +0 -0
  104. {baucli-1.0.151 → baucli-1.0.152}/tests/test_documentation_list_db_label.py +0 -0
  105. {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doc_changeset.py +0 -0
  106. {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doc_tags.py +0 -0
  107. {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doctab_text.py +0 -0
  108. {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_schema_context.py +0 -0
  109. {baucli-1.0.151 → baucli-1.0.152}/tests/test_packaging_deps.py +0 -0
  110. {baucli-1.0.151 → baucli-1.0.152}/tests/test_post_apply_uikit.py +0 -0
  111. {baucli-1.0.151 → baucli-1.0.152}/tests/test_profiles_add_prompt.py +0 -0
  112. {baucli-1.0.151 → baucli-1.0.152}/tests/test_project_guarda_chuva.py +0 -0
  113. {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_call_wrapper.py +0 -0
  114. {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_datasnap_reach.py +0 -0
  115. {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_from_depth.py +0 -0
  116. {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_owner.py +0 -0
  117. {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_pkg_func_from.py +0 -0
  118. {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_apex_domain.py +0 -0
  119. {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_lessons.py +0 -0
  120. {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_size_budget.py +0 -0
  121. {baucli-1.0.151 → baucli-1.0.152}/tests/test_spec_sem_beg.py +0 -0
  122. {baucli-1.0.151 → baucli-1.0.152}/tests/test_sync_413.py +0 -0
  123. {baucli-1.0.151 → baucli-1.0.152}/tests/test_sync_charset.py +0 -0
  124. {baucli-1.0.151 → baucli-1.0.152}/tests/test_table_status.py +0 -0
  125. {baucli-1.0.151 → baucli-1.0.152}/tests/test_uikit_scope.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: baucli
3
- Version: 1.0.151
3
+ Version: 1.0.152
4
4
  Summary: Extracts verified business-logic and schema context from Oracle, PostgreSQL, MySQL and SQL Server
5
5
  Home-page: https://www.begcloud.com
6
6
  Author: BEG Support
@@ -3,6 +3,6 @@
3
3
  import warnings
4
4
  warnings.filterwarnings("ignore", message="urllib3 v2 only supports OpenSSL")
5
5
 
6
- __version__ = "1.0.151"
6
+ __version__ = "1.0.152"
7
7
  __product__ = "BAUCLI"
8
8
  __author__ = "BEG Support"
@@ -5799,9 +5799,15 @@ def db_profile(db_name, schema, source, tables_csv):
5799
5799
  "(default: prevoo_<SCHEMA>.pdf na pasta atual).")
5800
5800
  @click.option("--client", "client_name", default=None,
5801
5801
  help="Nome do cliente no cabeçalho do HTML e na capa do PDF (opcional).")
5802
+ @click.option("--all-objects", "all_objects", is_flag=True, default=False,
5803
+ help="NÃO desconta os objetos que as regras de nome do BAU tiram da "
5804
+ "cobertura (_BKP/_TMP/_TESTE…): os números saem do schema inteiro, "
5805
+ "como até a 1.0.151. Por padrão cada seção conta só o que a carga "
5806
+ "vai trazer e diz quantos ficaram de fora.")
5802
5807
  def db_check(db_name: str, schema: str, top: int, src_max: int,
5803
5808
  max_cols: int, strict: bool, verbose: bool,
5804
- fmt: str = "terminal", out_file: str = None, client_name: str = None):
5809
+ fmt: str = "terminal", out_file: str = None, client_name: str = None,
5810
+ all_objects: bool = False):
5805
5811
  """Pré-voo de IA: aponta o que vai dar problema ANTES de documentar.
5806
5812
 
5807
5813
  \b
@@ -6027,6 +6033,97 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6027
6033
  f"[bold]{schema}[/bold] — esse usuário não enxerga nada desse "
6028
6034
  f"schema. Provável --db/BAUCLI_HOME errado.[/red]")
6029
6035
 
6036
+ # ── Regras de objeto: avaliadas ANTES das seções ───────────────────────
6037
+ # Responde "quais objetos deste schema o BAU vai classificar como teste/
6038
+ # backup/temp/ferramenta e portanto tirar da cobertura" -- e, desde a
6039
+ # 1.0.152, DESCONTA esses objetos de todas as seções: o pré-voo mede o que
6040
+ # a carga vai trazer, não o schema inteiro (1.040 tabelas sem PK com 300
6041
+ # _BKP dentro era número de susto, não de decisão). --all-objects volta ao
6042
+ # schema inteiro. A seção 7 continua listando os nomes por classe.
6043
+ #
6044
+ # As regras vem do servidor (GET /rules/objects, script 489) em vez de
6045
+ # embutidas aqui: sao editaveis pela UI e escopaveis por tenant/schema, e
6046
+ # uma copia local viraria segunda fonte de verdade que desatualiza em
6047
+ # silencio -- o check diria uma coisa e a carga faria outra.
6048
+ #
6049
+ # A AVALIACAO roda no Oracle DO CLIENTE, com o mesmo LIKE/REGEXP_LIKE(...,'i')
6050
+ # do FNC_BAU_RESOLVE_ARTIFACT_KIND. Reimplementar o match em Python
6051
+ # arriscaria divergir no dialeto de regex. O servidor ja manda `rank` com o
6052
+ # desempate resolvido, entao aqui e so "primeira regra que casa vence"
6053
+ # (ROW_NUMBER por objeto) -- nenhuma precedencia reimplementada.
6054
+ #
6055
+ # BEST-EFFORT: db check e um pre-voo que roda sem licenca e sem servidor.
6056
+ # Sem credencial ou com o BAU fora do ar, nada e descontado, a secao 7 e
6057
+ # PULADA com um aviso e o resto do check segue igual -- o contrato offline
6058
+ # continua valendo (e o veredito diz que os numeros sao do schema inteiro).
6059
+ regras, motivo, _rule_hits = [], None, []
6060
+ try:
6061
+ _api = get_api()
6062
+ if not getattr(_api, "api_key", None):
6063
+ motivo = "sem api key (rode baucli connect)"
6064
+ else:
6065
+ _resp = _api.object_rules(schema) or {}
6066
+ if _resp.get("status") == "ok":
6067
+ regras = _resp.get("rules") or []
6068
+ else:
6069
+ motivo = _resp.get("message") or "resposta inesperada do servidor"
6070
+ except Exception as e:
6071
+ motivo = str(e).splitlines()[0][:70]
6072
+
6073
+ if regras:
6074
+ def _lit(v):
6075
+ return "NULL" if v is None else "'" + str(v).replace("'", "''") + "'"
6076
+ _linhas = " UNION ALL ".join(
6077
+ f"SELECT {r['rank']} rnk, {_lit(r['catalog'])} cat, "
6078
+ f"{_lit(r['match_kind'])} mk, {_lit(r['match_value'])} mv, "
6079
+ f"{_lit(r.get('object_type'))} ot, {_lit(r['artifact_kind'])} ak, "
6080
+ f"{_lit(r['name'])} rname FROM DUAL" for r in regras)
6081
+ _sql_reg = f"""
6082
+ WITH rules AS ({_linhas}),
6083
+ objs AS (
6084
+ SELECT OBJECT_NAME nome, 'PLSQL' cat, OBJECT_TYPE tipo
6085
+ FROM ALL_OBJECTS
6086
+ WHERE OWNER = :o
6087
+ AND OBJECT_TYPE IN ('PACKAGE','PROCEDURE','FUNCTION','TRIGGER','TYPE')
6088
+ UNION ALL
6089
+ SELECT TABLE_NAME, 'TABLE', 'TABLE' FROM ALL_TABLES WHERE OWNER = :o
6090
+ UNION ALL
6091
+ SELECT VIEW_NAME, 'TABLE', 'VIEW' FROM ALL_VIEWS WHERE OWNER = :o
6092
+ )
6093
+ SELECT nome, tipo, ak, rname, (SELECT COUNT(*) FROM objs) tot FROM (
6094
+ SELECT o.nome, o.tipo, r.ak, r.rname,
6095
+ ROW_NUMBER() OVER (PARTITION BY o.cat, o.nome ORDER BY r.rnk) seq
6096
+ FROM objs o
6097
+ JOIN rules r
6098
+ ON (r.cat = 'BOTH' OR r.cat = o.cat)
6099
+ AND (r.ot IS NULL OR UPPER(r.ot) = UPPER(o.tipo))
6100
+ AND ( (r.mk = 'EXACT' AND UPPER(o.nome) = UPPER(r.mv))
6101
+ OR (r.mk = 'PREFIX' AND UPPER(o.nome) LIKE UPPER(r.mv) || '%')
6102
+ OR (r.mk = 'SUFFIX' AND UPPER(o.nome) LIKE '%' || UPPER(r.mv))
6103
+ OR (r.mk = 'CONTAINS' AND UPPER(o.nome) LIKE '%' || UPPER(r.mv) || '%')
6104
+ OR (r.mk = 'REGEXP' AND REGEXP_LIKE(o.nome, r.mv, 'i')) )
6105
+ ) WHERE seq = 1 ORDER BY ak, nome"""
6106
+ _rule_hits = _timed(f"regras de objeto ({len(regras)} regras x catalogo)",
6107
+ _sql_reg, {"o": schema})
6108
+
6109
+ # Conjunto de exclusão NOME -> classe. Vazio = nada é descontado. O nome
6110
+ # basta: PACKAGE BODY e TYPE BODY levam o nome da spec que a regra casou.
6111
+ _excl = {} if all_objects else {str(n).upper(): ak for (n, _t, ak, _r, _tot) in _rule_hits}
6112
+ report["object_rules"].update(
6113
+ applied=bool(_excl),
6114
+ applied_reason=("flag --all-objects" if all_objects
6115
+ else (None if _excl else (motivo or "nenhuma regra ativa"))))
6116
+
6117
+ def _split_cov(rows, key):
6118
+ """(linhas dentro da cobertura, quantas ficaram fora) pelo nome em row[key]."""
6119
+ if not _excl:
6120
+ return list(rows), 0
6121
+ keep = [r for r in rows if str(r[key] or "").upper() not in _excl]
6122
+ return keep, len(rows) - len(keep)
6123
+
6124
+ def _fora(n):
6125
+ return f" [dim](+{n} fora da cobertura pelas regras)[/dim]" if n else ""
6126
+
6030
6127
  # Tipos que o BAU consegue fatiar: um PACKAGE BODY grande é documentado
6031
6128
  # subprograma a subprograma (chunking DOCPL), então passar do limite da
6032
6129
  # chamada é ESPERADO e tratado — não é impedimento. Um FUNCTION/PROCEDURE/
@@ -6067,6 +6164,7 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6067
6164
  GROUP BY s.TYPE, s.NAME
6068
6165
  ORDER BY 4 DESC
6069
6166
  """, {"o": schema})
6167
+ plsql_rows, _x_pl = _split_cov(plsql_rows, 1)
6070
6168
  flagged = []
6071
6169
  for (otype, oname, lines, chars) in plsql_rows:
6072
6170
  lvl, why = _band_plsql(otype, int(lines or 0), int(chars or 0))
@@ -6076,14 +6174,14 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6076
6174
 
6077
6175
  _ord_lvl = {"CRÍTICO": 0, "ATENÇÃO": 1, "AVISO": 2}
6078
6176
  report["plsql"] = {
6079
- "total": len(plsql_rows),
6177
+ "total": len(plsql_rows), "excluded_by_rules": _x_pl,
6080
6178
  "flagged": [{"level": lvl, "type": otype, "name": oname, "lines": lines,
6081
6179
  "chars": chars, "tokens": int(chars / CHARS_PER_TOKEN), "reason": why}
6082
6180
  for (lvl, otype, oname, lines, chars, why)
6083
6181
  in sorted(flagged, key=lambda r: (_ord_lvl[r[0]], -r[4]))],
6084
6182
  }
6085
- console.print(f"[bold]PL/SQL[/bold] — {len(plsql_rows)} objeto(s) no schema, "
6086
- f"{len(flagged)} sinalizado(s)")
6183
+ console.print(f"[bold]PL/SQL[/bold] — {len(plsql_rows)} objeto(s) na cobertura, "
6184
+ f"{len(flagged)} sinalizado(s){_fora(_x_pl)}")
6087
6185
  if flagged:
6088
6186
  t = RichTable(show_header=True, header_style="bold", border_style="dim")
6089
6187
  t.add_column("Nível", style="bold")
@@ -6126,13 +6224,15 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6126
6224
  HAVING COUNT(*) > :mx
6127
6225
  ORDER BY 2 DESC
6128
6226
  """, {"o": schema, "mx": max_cols})
6227
+ wide, _x_wide = _split_cov(wide, 0)
6129
6228
  findings["ATENÇÃO"] += len(wide)
6130
6229
  report["wide_tables"] = {
6131
- "max_columns": max_cols,
6230
+ "max_columns": max_cols, "excluded_by_rules": _x_wide,
6132
6231
  "items": [{"table": tname, "columns": int(cols), "lobs": int(lobs or 0)}
6133
6232
  for (tname, cols, lobs) in wide],
6134
6233
  }
6135
- console.print(f"\n[bold]Tabelas largas[/bold] — {len(wide)} acima de {max_cols} colunas")
6234
+ console.print(f"\n[bold]Tabelas largas[/bold] — {len(wide)} acima de {max_cols} colunas"
6235
+ f"{_fora(_x_wide)}")
6136
6236
  if wide:
6137
6237
  t = RichTable(show_header=True, header_style="bold", border_style="dim")
6138
6238
  t.add_column("Tabela"); t.add_column("Colunas", justify="right")
@@ -6209,12 +6309,14 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6209
6309
  AND NOT EXISTS (SELECT 1 FROM mv WHERE mv.MVIEW_NAME = t.TABLE_NAME)
6210
6310
  ORDER BY 3, 4, 1
6211
6311
  """, {"o": schema})
6312
+ no_pk, _x_pk = _split_cov(no_pk, 0)
6212
6313
  sem_id = [r for r in no_pk if int(r[2] or 0) == 0 and int(r[3] or 0) == 0]
6213
6314
  com_id = [r for r in no_pk if r not in sem_id]
6214
6315
  findings["ATENÇÃO"] += len(sem_id)
6215
6316
  findings["AVISO"] += len(com_id)
6216
6317
  report["no_pk"] = {
6217
6318
  "total": len(no_pk), "no_identity": len(sem_id), "with_unique": len(com_id),
6319
+ "excluded_by_rules": _x_pk,
6218
6320
  "items": [{"table": tname, "columns": int(cols or 0),
6219
6321
  "rows_est": int(nrows) if nrows is not None else None,
6220
6322
  "uks": int(uks or 0), "unique_indexes": int(uix or 0),
@@ -6224,7 +6326,7 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6224
6326
  console.print(f"\n[bold]Tabelas sem PK[/bold] — {len(no_pk)} "
6225
6327
  f"([red]{len(sem_id)} sem identidade alguma[/red], "
6226
6328
  f"{len(com_id)} com UK/índice único"
6227
- + ")")
6329
+ + ")" + _fora(_x_pk))
6228
6330
  if no_pk:
6229
6331
  t = RichTable(show_header=True, header_style="bold", border_style="dim")
6230
6332
  t.add_column("Tabela"); t.add_column("Colunas", justify="right")
@@ -6253,14 +6355,15 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6253
6355
  WHERE v.OWNER = :o AND NVL(v.TEXT_LENGTH,0) > :mx
6254
6356
  ORDER BY 2 DESC
6255
6357
  """, {"o": schema, "mx": src_max})
6358
+ big_views, _x_bv = _split_cov(big_views, 0)
6256
6359
  findings["CRÍTICO"] += len(big_views)
6257
6360
  report["big_views"] = {
6258
- "limit_chars": src_max,
6361
+ "limit_chars": src_max, "excluded_by_rules": _x_bv,
6259
6362
  "items": [{"view": vname, "chars": int(vlen), "tokens": int(int(vlen) / CHARS_PER_TOKEN)}
6260
6363
  for (vname, vlen) in big_views],
6261
6364
  }
6262
6365
  console.print(f"\n[bold]Views grandes[/bold] — {len(big_views)} acima de "
6263
- f"{src_max:,} chars")
6366
+ f"{src_max:,} chars{_fora(_x_bv)}")
6264
6367
  if big_views:
6265
6368
  t = RichTable(show_header=True, header_style="bold", border_style="dim")
6266
6369
  t.add_column("View"); t.add_column("Chars", justify="right")
@@ -6288,20 +6391,21 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6288
6391
  'TRIGGER','TYPE','TYPE BODY','VIEW')
6289
6392
  ORDER BY o.OBJECT_TYPE, o.OBJECT_NAME
6290
6393
  """, {"o": schema})
6394
+ invalid, _x_inv = _split_cov(invalid, 1)
6291
6395
  broken = [r for r in invalid if (r[3] or "").strip()]
6292
6396
  stale = [r for r in invalid if not (r[3] or "").strip()]
6293
6397
  report["invalid"] = {
6294
- "total": len(invalid),
6398
+ "total": len(invalid), "excluded_by_rules": _x_inv,
6295
6399
  "broken": [{"type": otype, "name": oname, "last_ddl": last_ddl or None,
6296
6400
  "first_error": (err or "").strip()} for (otype, oname, last_ddl, err) in broken],
6297
6401
  "stale": [{"type": otype, "name": oname, "last_ddl": last_ddl or None}
6298
6402
  for (otype, oname, last_ddl, _err) in stale],
6299
6403
  }
6300
6404
 
6301
- if invalid:
6302
- console.print(f"\n[bold]Objetos INVALID[/bold] — {len(invalid)} no total "
6405
+ if invalid or _x_inv:
6406
+ console.print(f"\n[bold]Objetos INVALID[/bold] — {len(invalid)} na cobertura "
6303
6407
  f"([red]{len(broken)} quebrado(s)[/red], "
6304
- f"{len(stale)} só precisando recompilar)")
6408
+ f"{len(stale)} só precisando recompilar){_fora(_x_inv)}")
6305
6409
  console.print(" [dim]O sync extrai só STATUS='VALID': qualquer inválido "
6306
6410
  "fica FORA da documentação.[/dim]")
6307
6411
 
@@ -6362,20 +6466,21 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6362
6466
  WHERE b.OBJECT_NAME = s.OBJECT_NAME)
6363
6467
  ORDER BY 4 DESC, 1
6364
6468
  """, {"o": schema})
6469
+ nobody, _x_nb = _split_cov(nobody, 0)
6365
6470
  sem_corpo = [r for r in nobody if int(r[3] or 0) > 0]
6366
6471
  so_spec = [r for r in nobody if int(r[3] or 0) == 0]
6367
6472
  report["packages_no_body"] = {
6368
- "total": len(nobody),
6473
+ "total": len(nobody), "excluded_by_rules": _x_nb,
6369
6474
  "declaring": [{"package": pname, "subprograms": int(subs or 0),
6370
6475
  "spec_status": status, "last_ddl": last_ddl or None}
6371
6476
  for (pname, last_ddl, status, subs) in sem_corpo],
6372
6477
  "spec_only": [r[0] for r in so_spec],
6373
6478
  }
6374
6479
 
6375
- if nobody:
6376
- console.print(f"\n[bold]Packages sem BODY[/bold] — {len(nobody)} no total "
6480
+ if nobody or _x_nb:
6481
+ console.print(f"\n[bold]Packages sem BODY[/bold] — {len(nobody)} na cobertura "
6377
6482
  f"([red]{len(sem_corpo)} declara(m) subprograma[/red], "
6378
- f"{len(so_spec)} só constantes/tipos)")
6483
+ f"{len(so_spec)} só constantes/tipos){_fora(_x_nb)}")
6379
6484
 
6380
6485
  if sem_corpo:
6381
6486
  findings["ATENÇÃO"] += len(sem_corpo)
@@ -6401,88 +6506,25 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6401
6506
  + "[/dim]")
6402
6507
 
6403
6508
  # ── Regras de objeto: o que o BAU vai marcar como sobra ────────────────
6404
- # Responde ANTES do 1o sync "quais objetos deste schema o BAU vai
6405
- # classificar como teste/backup/temp/ferramenta e portanto tirar da
6406
- # cobertura". Sem isso o usuario so descobre DEPOIS de documentar (p76).
6407
- #
6408
- # As regras vem do servidor (GET /rules/objects, script 489) em vez de
6409
- # embutidas aqui: sao editaveis pela UI e escopaveis por tenant/schema, e
6410
- # uma copia local viraria segunda fonte de verdade que desatualiza em
6411
- # silencio -- o check diria uma coisa e a carga faria outra.
6412
- #
6413
- # A AVALIACAO roda no Oracle DO CLIENTE, com o mesmo LIKE/REGEXP_LIKE(...,'i')
6414
- # do FNC_BAU_RESOLVE_ARTIFACT_KIND. Reimplementar o match em Python
6415
- # arriscaria divergir no dialeto de regex. O servidor ja manda `rank` com o
6416
- # desempate resolvido, entao aqui e so "primeira regra que casa vence"
6417
- # (ROW_NUMBER por objeto) -- nenhuma precedencia reimplementada.
6418
- #
6419
- # BEST-EFFORT: db check e um pre-voo que roda sem licenca e sem servidor.
6420
- # Sem credencial ou com o BAU fora do ar, a secao e PULADA com um aviso e
6421
- # o resto do check segue igual -- o contrato offline continua valendo.
6422
- regras, motivo = [], None
6423
- try:
6424
- _api = get_api()
6425
- if not getattr(_api, "api_key", None):
6426
- motivo = "sem api key (rode baucli connect)"
6427
- else:
6428
- _resp = _api.object_rules(schema) or {}
6429
- if _resp.get("status") == "ok":
6430
- regras = _resp.get("rules") or []
6431
- else:
6432
- motivo = _resp.get("message") or "resposta inesperada do servidor"
6433
- except Exception as e:
6434
- motivo = str(e).splitlines()[0][:70]
6435
-
6509
+ # A avaliação rodou no início (e descontou as seções acima, salvo
6510
+ # --all-objects); aqui é só a lista por classe, para o cliente conferir.
6436
6511
  if not regras:
6437
6512
  report["object_rules"]["skipped"] = motivo or "nenhuma regra ativa"
6438
6513
  console.print(f"\n[bold]Regras de objetos[/bold] [dim]— pulado: "
6439
- f"{motivo or 'nenhuma regra ativa'}[/dim]")
6514
+ f"{motivo or 'nenhuma regra ativa'}; os números acima são do "
6515
+ f"schema inteiro[/dim]")
6440
6516
  else:
6441
- def _lit(v):
6442
- return "NULL" if v is None else "'" + str(v).replace("'", "''") + "'"
6443
- _linhas = " UNION ALL ".join(
6444
- f"SELECT {r['rank']} rnk, {_lit(r['catalog'])} cat, "
6445
- f"{_lit(r['match_kind'])} mk, {_lit(r['match_value'])} mv, "
6446
- f"{_lit(r.get('object_type'))} ot, {_lit(r['artifact_kind'])} ak, "
6447
- f"{_lit(r['name'])} rname FROM DUAL" for r in regras)
6448
- _sql_reg = f"""
6449
- WITH rules AS ({_linhas}),
6450
- objs AS (
6451
- SELECT OBJECT_NAME nome, 'PLSQL' cat, OBJECT_TYPE tipo
6452
- FROM ALL_OBJECTS
6453
- WHERE OWNER = :o
6454
- AND OBJECT_TYPE IN ('PACKAGE','PROCEDURE','FUNCTION','TRIGGER','TYPE')
6455
- UNION ALL
6456
- SELECT TABLE_NAME, 'TABLE', 'TABLE' FROM ALL_TABLES WHERE OWNER = :o
6457
- UNION ALL
6458
- SELECT VIEW_NAME, 'TABLE', 'VIEW' FROM ALL_VIEWS WHERE OWNER = :o
6459
- )
6460
- SELECT nome, tipo, ak, rname, (SELECT COUNT(*) FROM objs) tot FROM (
6461
- SELECT o.nome, o.tipo, r.ak, r.rname,
6462
- ROW_NUMBER() OVER (PARTITION BY o.cat, o.nome ORDER BY r.rnk) seq
6463
- FROM objs o
6464
- JOIN rules r
6465
- ON (r.cat = 'BOTH' OR r.cat = o.cat)
6466
- AND (r.ot IS NULL OR UPPER(r.ot) = UPPER(o.tipo))
6467
- AND ( (r.mk = 'EXACT' AND UPPER(o.nome) = UPPER(r.mv))
6468
- OR (r.mk = 'PREFIX' AND UPPER(o.nome) LIKE UPPER(r.mv) || '%')
6469
- OR (r.mk = 'SUFFIX' AND UPPER(o.nome) LIKE '%' || UPPER(r.mv))
6470
- OR (r.mk = 'CONTAINS' AND UPPER(o.nome) LIKE '%' || UPPER(r.mv) || '%')
6471
- OR (r.mk = 'REGEXP' AND REGEXP_LIKE(o.nome, r.mv, 'i')) )
6472
- ) WHERE seq = 1 ORDER BY ak, nome"""
6473
- hits = _timed(f"regras de objeto ({len(regras)} regras x catalogo)",
6474
- _sql_reg, {"o": schema})
6475
6517
  report["object_rules"].update(source="BAU Server", rules=len(regras))
6476
6518
  console.print(f"\n[bold]Regras de objetos[/bold] [dim]({len(regras)} regras "
6477
6519
  f"· fonte: BAU Server)[/dim]")
6478
- if not hits:
6520
+ if not _rule_hits:
6479
6521
  console.print(" [green]Nenhum objeto atingido — tudo entra como "
6480
6522
  "PROD.[/green]")
6481
6523
  else:
6482
6524
  por_kind = {}
6483
- for nome, tipo, ak, rname, _tot in hits:
6525
+ for nome, tipo, ak, rname, _tot in _rule_hits:
6484
6526
  por_kind.setdefault(ak, []).append(nome)
6485
- total = int(hits[0][4] or 0)
6527
+ total = int(_rule_hits[0][4] or 0)
6486
6528
  report["object_rules"].update(
6487
6529
  total_objects=total, excluded=sum(len(v) for v in por_kind.values()),
6488
6530
  by_kind={ak: list(v) for ak, v in sorted(por_kind.items())})
@@ -6494,8 +6536,10 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6494
6536
  + (f" … +{len(nomes)-len(mostra)}"
6495
6537
  if len(nomes) > len(mostra) else ""))
6496
6538
  console.print(f" [dim]→ {sum(len(v) for v in por_kind.values())} de "
6497
- f"{total} objetos sairiam da cobertura como não-PROD "
6498
- f"(ARTIFACT_KIND ≠ PROD).[/dim]")
6539
+ f"{total} objetos saem da cobertura como não-PROD "
6540
+ f"(ARTIFACT_KIND ≠ PROD)"
6541
+ + ("; já descontados das seções acima" if _excl
6542
+ else "; NÃO descontados (--all-objects)") + ".[/dim]")
6499
6543
 
6500
6544
  # ── Codificacao / acentuacao da ORIGEM ────────────────────────────────
6501
6545
  # Por que isto vive no `db check`, ANTES do primeiro sync: banco de cliente
@@ -6574,17 +6618,21 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6574
6618
  "select 'COLUNA', table_name, column_name, comments from all_col_comments "
6575
6619
  " where owner = :o and comments is not null",
6576
6620
  {"o": schema})
6621
+ # Desconto por ACHADO, não por linha lida: 200 comentários de tabelas
6622
+ # _BKP sem problema nenhum não são "202 fora da cobertura".
6623
+ _src_rows, _x_src = _split_cov(_src_rows, 0)
6577
6624
 
6578
6625
  _cmt_hits = []
6579
6626
  for _k, _tb, _col, _txt in _cmt_rows:
6580
6627
  _sig = _enc_scan(_txt, is_comment=True)
6581
6628
  if _sig:
6582
6629
  _cmt_hits.append((_k, _tb, _col, ",".join(_sig), _txt))
6630
+ _cmt_hits, _x_cmt = _split_cov(_cmt_hits, 1)
6583
6631
 
6584
6632
  _is_utf8 = str(_charset).upper() in ("AL32UTF8", "UTF8")
6585
6633
  report["encoding"] = {
6586
6634
  "charset": _charset, "ncharset": _cs.get("NLS_NCHAR_CHARACTERSET"),
6587
- "is_utf8": _is_utf8, "available": True,
6635
+ "is_utf8": _is_utf8, "available": True, "excluded_by_rules": _x_src + _x_cmt,
6588
6636
  "source_objects": [{"name": _n, "type": _ty, "lines": int(_cnt or 0)}
6589
6637
  for (_n, _ty, _cnt) in _src_rows],
6590
6638
  "comments": [{"where": _k, "table": _tb, "column": _col, "signals": _sig, "text": _txt}
@@ -6690,11 +6738,20 @@ def db_check(db_name: str, schema: str, top: int, src_max: int,
6690
6738
  f"sem impedimento.[/dim]")
6691
6739
  if not any(findings.values()):
6692
6740
  console.print("[green]● Nada sinalizado — schema dentro dos limites de IA.[/green]")
6741
+ if _excl:
6742
+ console.print(f"[dim]Números da cobertura do BAU: {len(_excl)} objeto(s) fora pelas "
6743
+ f"regras de nome (seção Regras de objetos). --all-objects mostra o "
6744
+ f"schema inteiro.[/dim]")
6745
+ elif not all_objects:
6746
+ console.print(f"[dim]Regras de nome não aplicadas ({motivo or 'nenhuma regra ativa'}): "
6747
+ f"números do schema inteiro.[/dim]")
6693
6748
  console.print("[dim]Próximo passo: baucli sync (ou baucli sync --scope <CODE> "
6694
6749
  "para recortar o schema).[/dim]")
6695
6750
 
6696
6751
  report["summary"] = {"critico": findings["CRÍTICO"], "atencao": findings["ATENÇÃO"],
6697
- "aviso": findings["AVISO"]}
6752
+ "aviso": findings["AVISO"],
6753
+ "scope": "cobertura" if _excl else "schema inteiro",
6754
+ "excluded_by_rules": len(_excl)}
6698
6755
  if _structured:
6699
6756
  console.quiet = False
6700
6757
  tally = (f"[dim]({findings['CRÍTICO']} crítico, {findings['ATENÇÃO']} atenção, "
@@ -82,6 +82,10 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
82
82
  def mono(v):
83
83
  return f'<font face="Courier">{_esc(v)}</font>'
84
84
 
85
+ def fora(section):
86
+ n = int((section or {}).get("excluded_by_rules") or 0)
87
+ return f' <font color="{_GRAY}">(+{_n(n)} fora da cobertura pelas regras de nome)</font>' if n else ""
88
+
85
89
  def lvl(level):
86
90
  color = {"CRÍTICO": _RED, "ATENÇÃO": _AMBER}.get(level or "", _GRAY)
87
91
  return f'<font color="{color}"><b>{_esc(level)}</b></font>'
@@ -195,12 +199,19 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
195
199
  out.append(P(f"Limiares aplicados: {_n(th.get('source_max_chars'))} caracteres por chamada de IA · tabela até "
196
200
  f"{_esc(th.get('max_columns'))} colunas no prompt · corpo de package com {_n(th.get('lines_unsafe'))} linhas ou "
197
201
  f"mais é edição insegura · fonte dos limiares: {_esc(th.get('source'))}.", "Small"))
202
+ if orr.get("applied"):
203
+ out.append(P(f"Números da <b>cobertura do BAU</b>: {_n(s.get('excluded_by_rules'))} objetos classificados como "
204
+ "não produtivos pelas regras de nome (seção 7) já foram descontados de todas as seções.", "Small"))
205
+ else:
206
+ why = orr.get("applied_reason") or orr.get("skipped")
207
+ out.append(P("Números do <b>schema inteiro</b>: as regras de nome não foram descontadas"
208
+ + (f" ({_esc(why)})" if why else "") + ".", "Small"))
198
209
  if not any((crit, aten, aviso)):
199
210
  out.append(P("<b>Nada sinalizado</b>: o schema está dentro dos limites de IA."))
200
211
 
201
212
  # ── 1. PL/SQL ──
202
213
  out.append(P("1. Rotinas PL/SQL sinalizadas", "H1"))
203
- out.append(P(f"{_n(pl.get('total'))} objetos no schema, <b>{len(pl['flagged'])}</b> sinalizados."))
214
+ out.append(P(f"{_n(pl.get('total'))} objetos na cobertura, <b>{len(pl['flagged'])}</b> sinalizados.{fora(pl)}"))
204
215
  if pl["flagged"]:
205
216
  out.append(tbl(["Nível", "Tipo", "Objeto", "Linhas", "KB", "~tokens", "Por quê"],
206
217
  [[lvl(f.get("level")), _esc(f.get("type")), mono(f.get("name")), _n(f.get("lines")),
@@ -212,7 +223,7 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
212
223
  out.append(P("2. Tabelas largas", "H1"))
213
224
  out.append(P(f"<b>{len(w['items'])}</b> tabelas com mais de {_esc(w.get('max_columns'))} colunas: a IA documenta coluna a "
214
225
  "coluna (fan-out), multiplicando chamadas e revisão. Tabelas com centenas de colunas costumam ser planilhas "
215
- "importadas ou estruturas desnormalizadas; vale confirmar se interessam ao catálogo."))
226
+ f"importadas ou estruturas desnormalizadas; vale confirmar se interessam ao catálogo.{fora(w)}"))
216
227
  if w["items"]:
217
228
  out.append(P("Formato: TABELA (colunas; LOBs quando houver), em ordem decrescente de colunas.", "Small"))
218
229
  out.append(compact([f"{i['table']} ({i['columns']}{'; ' + str(i['lobs']) + ' LOB' if i.get('lobs') else ''})"
@@ -222,7 +233,7 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
222
233
  out.append(P("3. Tabelas sem chave primária", "H1"))
223
234
  out.append(P(f"<b>{_n(npk.get('total'))}</b> tabelas sem PK: {_n(npk.get('no_identity'))} sem nenhuma identidade de linha e "
224
235
  f"{_n(npk.get('with_unique'))} com chave ou índice único (caso leve). Sem chave, formulários e relatórios "
225
- "gerados não têm como apontar o registro e a ordenação de colunas perde o primeiro critério."))
236
+ f"gerados não têm como apontar o registro e a ordenação de colunas perde o primeiro critério.{fora(npk)}"))
226
237
  com = [i for i in npk["items"] if i.get("has_identity")]
227
238
  sem = [i for i in npk["items"] if not i.get("has_identity")]
228
239
 
@@ -239,7 +250,7 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
239
250
 
240
251
  # ── 4. views ──
241
252
  out.append(P("4. Views grandes", "H1"))
242
- out.append(P(f"{len(bv['items'])} acima de {_n(bv.get('limit_chars'))} caracteres."))
253
+ out.append(P(f"{len(bv['items'])} acima de {_n(bv.get('limit_chars'))} caracteres.{fora(bv)}"))
243
254
  if bv["items"]:
244
255
  out.append(tbl(["View", "Chars", "~tokens"],
245
256
  [[mono(i.get("view")), _n(i.get("chars")), _n(i.get("tokens"))] for i in bv["items"]],
@@ -249,7 +260,7 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
249
260
  out.append(P("5. Objetos inválidos", "H1"))
250
261
  out.append(P(f"<b>{_n(inv.get('total'))}</b> objetos INVALID: {len(inv['broken'])} com erro de compilação e "
251
262
  f"{len(inv['stale'])} invalidados em cascata. O sync extrai só objetos VALID: tudo o que está aqui fica fora "
252
- "da documentação até ser resolvido."))
263
+ f"da documentação até ser resolvido.{fora(inv)}"))
253
264
  if inv["broken"]:
254
265
  out.append(P("Com erro de compilação", "H2"))
255
266
  out.append(tbl(["Tipo", "Objeto", "DDL", "1º erro"],
@@ -265,7 +276,7 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
265
276
  out.append(P("6. Packages sem corpo", "H1"))
266
277
  out.append(P(f"{_n(nb.get('total'))} packages só com especificação: {len(nb['declaring'])} declaram subprogramas sem "
267
278
  "corpo (falham em runtime; a documentação por subprograma fica sem fonte) e "
268
- f"{len(nb['spec_only'])} são só constantes e tipos (legítimo)."))
279
+ f"{len(nb['spec_only'])} são só constantes e tipos (legítimo).{fora(nb)}"))
269
280
  if nb["declaring"]:
270
281
  out.append(tbl(["Package", "Subprogramas", "Spec", "DDL"],
271
282
  [[mono(i.get("package")), _n(i.get("subprograms")), _esc(i.get("spec_status")), _esc(i.get("last_ddl"))]
@@ -280,14 +291,18 @@ def to_pdf(report: dict, client: str | None = None) -> bytes:
280
291
  out.append(P(f"Seção pulada: {_esc(orr['skipped'])}.", "Small"))
281
292
  else:
282
293
  out.append(P(f"{_esc(orr.get('rules'))} regras de nome classificam objetos como não produtivos. "
283
- f"<b>{_n(orr.get('excluded'))}</b> de {_n(orr.get('total_objects'))} objetos saem da cobertura. "
284
- "Confirme se alguma classe está errada para o seu padrão de nomes; a lista é ajustável por cliente."))
294
+ f"<b>{_n(orr.get('excluded'))}</b> de {_n(orr.get('total_objects'))} objetos saem da cobertura"
295
+ + (", já descontados das seções anteriores. " if orr.get("applied")
296
+ else ". Não descontados das seções anteriores. ")
297
+ + "Confirme se alguma classe está errada para o seu padrão de nomes; a lista é ajustável por cliente."))
285
298
  for kind, names in sorted((orr.get("by_kind") or {}).items()):
286
299
  out.append(P(f"{_esc(kind)} ({len(names)})", "H2"))
287
300
  out.append(compact(names))
288
301
 
289
302
  # ── 8. codificação ──
290
303
  out.append(P("8. Codificação e acentuação", "H1"))
304
+ if enc.get("excluded_by_rules"):
305
+ out.append(P(fora(enc).strip(), "Small"))
291
306
  if not enc.get("available", True):
292
307
  out.append(P("Checagem indisponível.", "Small"))
293
308
  else:
@@ -19,7 +19,7 @@ import json
19
19
  from datetime import datetime, timezone
20
20
  from pathlib import Path
21
21
 
22
- SCHEMA_VERSION = 1
22
+ SCHEMA_VERSION = 2 # 2 (baucli 1.0.152): excluded_by_rules por seção, object_rules.applied, summary.scope
23
23
 
24
24
  # Logotipo BAU (quadrado preto) embutido como data: URI: o HTML continua um
25
25
  # arquivo só, sem recurso externo, e abre com a marca em qualquer lugar.
@@ -41,16 +41,19 @@ def empty_report() -> dict:
41
41
  "database": {}, # alias, engine, host, port, service, user
42
42
  "schema": None,
43
43
  "thresholds": {}, # source_max_chars, max_columns, lines_*, size_*, chars_per_token, source
44
- "summary": {"critico": 0, "atencao": 0, "aviso": 0},
45
- "plsql": {"total": 0, "flagged": []},
46
- "wide_tables": {"max_columns": None, "items": []},
47
- "no_pk": {"total": 0, "no_identity": 0, "with_unique": 0, "items": []},
48
- "big_views": {"limit_chars": None, "items": []},
49
- "invalid": {"total": 0, "broken": [], "stale": []},
50
- "packages_no_body": {"total": 0, "declaring": [], "spec_only": []},
44
+ # scope: "cobertura" (regras de nome descontadas) ou "schema inteiro"
45
+ "summary": {"critico": 0, "atencao": 0, "aviso": 0, "scope": None, "excluded_by_rules": 0},
46
+ # excluded_by_rules em cada seção: quantos objetos as regras de nome tiraram dela
47
+ "plsql": {"total": 0, "excluded_by_rules": 0, "flagged": []},
48
+ "wide_tables": {"max_columns": None, "excluded_by_rules": 0, "items": []},
49
+ "no_pk": {"total": 0, "no_identity": 0, "with_unique": 0, "excluded_by_rules": 0, "items": []},
50
+ "big_views": {"limit_chars": None, "excluded_by_rules": 0, "items": []},
51
+ "invalid": {"total": 0, "excluded_by_rules": 0, "broken": [], "stale": []},
52
+ "packages_no_body": {"total": 0, "excluded_by_rules": 0, "declaring": [], "spec_only": []},
51
53
  "object_rules": {"source": None, "skipped": None, "rules": 0,
52
- "total_objects": 0, "excluded": 0, "by_kind": {}},
53
- "encoding": {"charset": None, "is_utf8": None, "available": True,
54
+ "total_objects": 0, "excluded": 0, "by_kind": {},
55
+ "applied": False, "applied_reason": None},
56
+ "encoding": {"charset": None, "is_utf8": None, "available": True, "excluded_by_rules": 0,
54
57
  "source_objects": [], "comments": []},
55
58
  "timings": [],
56
59
  "errors": [],
@@ -118,6 +121,24 @@ def _cols(items) -> str:
118
121
  return '<div class="cols">' + "".join(f"<div>{_e(i)}</div>" for i in items) + "</div>"
119
122
 
120
123
 
124
+ def _fora(section: dict) -> str:
125
+ """' (+N fora da cobertura pelas regras)' quando a seção descontou objetos."""
126
+ n = int((section or {}).get("excluded_by_rules") or 0)
127
+ return f" <span class='note'>(+{_n(n)} fora da cobertura pelas regras de nome)</span>" if n else ""
128
+
129
+
130
+ def _coverage_note(report: dict) -> str:
131
+ orr = report.get("object_rules") or {}
132
+ s = report.get("summary") or {}
133
+ if orr.get("applied"):
134
+ return (f"<p class='note'>Números da <b>cobertura do BAU</b>: {_n(s.get('excluded_by_rules'))} objetos "
135
+ "classificados como não produtivos pelas regras de nome (seção 7) já foram descontados de "
136
+ "todas as seções.</p>")
137
+ why = orr.get("applied_reason") or orr.get("skipped")
138
+ return ("<p class='note'>Números do <b>schema inteiro</b>: as regras de nome não foram descontadas"
139
+ + (f" ({_e(why)})" if why else "") + ".</p>")
140
+
141
+
121
142
  def to_html(report: dict, client: str | None = None) -> str:
122
143
  r = report
123
144
  db = r.get("database") or {}
@@ -146,11 +167,12 @@ def to_html(report: dict, client: str | None = None) -> str:
146
167
  out.append(f"<p class='note'>Limiares: {_n(th.get('source_max_chars'))} caracteres por chamada de IA · tabela até "
147
168
  f"{_e(th.get('max_columns'))} colunas no prompt · corpo de package com {_n(th.get('lines_unsafe'))} linhas "
148
169
  f"ou mais é edição insegura · fonte dos limiares: {_e(th.get('source'))}.</p>")
170
+ out.append(_coverage_note(r))
149
171
 
150
172
  # PL/SQL
151
173
  pl = r["plsql"]
152
- out.append(f"<h2>1. Rotinas PL/SQL sinalizadas</h2><p>{_n(pl.get('total'))} objetos no schema, "
153
- f"<b>{len(pl['flagged'])}</b> sinalizados.</p>")
174
+ out.append(f"<h2>1. Rotinas PL/SQL sinalizadas</h2><p>{_n(pl.get('total'))} objetos na cobertura, "
175
+ f"<b>{len(pl['flagged'])}</b> sinalizados.{_fora(pl)}</p>")
154
176
  if pl["flagged"]:
155
177
  out.append(_table(["Nível", "Tipo", "Objeto", "Linhas", "KB", "~tokens", "Por quê"],
156
178
  [[_lvl(f["level"]), _e(f["type"]), f"<code>{_e(f['name'])}</code>", _n(f["lines"]),
@@ -160,7 +182,7 @@ def to_html(report: dict, client: str | None = None) -> str:
160
182
  # wide
161
183
  w = r["wide_tables"]
162
184
  out.append(f"<h2>2. Tabelas largas</h2><p><b>{len(w['items'])}</b> tabelas com mais de {_e(w.get('max_columns'))} "
163
- "colunas: a IA documenta coluna a coluna (fan-out), multiplicando chamadas e revisão.</p>")
185
+ f"colunas: a IA documenta coluna a coluna (fan-out), multiplicando chamadas e revisão.{_fora(w)}</p>")
164
186
  if w["items"]:
165
187
  out.append(_cols([f"{i['table']} ({i['columns']}{'; ' + str(i['lobs']) + ' LOB' if i.get('lobs') else ''})"
166
188
  for i in w["items"]]))
@@ -170,7 +192,7 @@ def to_html(report: dict, client: str | None = None) -> str:
170
192
  out.append(f"<h2>3. Tabelas sem chave primária</h2><p><b>{_n(npk.get('total'))}</b> tabelas sem PK: "
171
193
  f"{_n(npk.get('no_identity'))} sem nenhuma identidade de linha e {_n(npk.get('with_unique'))} com chave "
172
194
  "ou índice único (caso leve). Sem chave, formulários e relatórios gerados não têm como apontar o registro "
173
- "e a ordenação de colunas perde o primeiro critério.</p>")
195
+ f"e a ordenação de colunas perde o primeiro critério.{_fora(npk)}</p>")
174
196
  com = [i for i in npk["items"] if i.get("has_identity")]
175
197
  sem = [i for i in npk["items"] if not i.get("has_identity")]
176
198
  fmt = lambda i: f"{i['table']} ({i['columns']} col, {_n(i['rows_est']) if i.get('rows_est') is not None else '—'} lin)"
@@ -181,7 +203,7 @@ def to_html(report: dict, client: str | None = None) -> str:
181
203
 
182
204
  # views
183
205
  bv = r["big_views"]
184
- out.append(f"<h2>4. Views grandes</h2><p>{len(bv['items'])} acima de {_n(bv.get('limit_chars'))} caracteres.</p>")
206
+ out.append(f"<h2>4. Views grandes</h2><p>{len(bv['items'])} acima de {_n(bv.get('limit_chars'))} caracteres.{_fora(bv)}</p>")
185
207
  if bv["items"]:
186
208
  out.append(_table(["View", "Chars", "~tokens"],
187
209
  [[f"<code>{_e(i['view'])}</code>", _n(i["chars"]), _n(i["tokens"])] for i in bv["items"]],
@@ -191,7 +213,7 @@ def to_html(report: dict, client: str | None = None) -> str:
191
213
  inv = r["invalid"]
192
214
  out.append(f"<h2>5. Objetos inválidos</h2><p><b>{_n(inv.get('total'))}</b> objetos INVALID: {len(inv['broken'])} com "
193
215
  f"erro de compilação e {len(inv['stale'])} invalidados em cascata. O sync extrai só objetos VALID: tudo "
194
- "o que está aqui fica fora da documentação até ser resolvido.</p>")
216
+ f"o que está aqui fica fora da documentação até ser resolvido.{_fora(inv)}</p>")
195
217
  if inv["broken"]:
196
218
  out.append("<h3>Com erro de compilação</h3>" + _table(
197
219
  ["Tipo", "Objeto", "DDL", "1º erro"],
@@ -204,7 +226,7 @@ def to_html(report: dict, client: str | None = None) -> str:
204
226
  nb = r["packages_no_body"]
205
227
  out.append(f"<h2>6. Packages sem corpo</h2><p>{_n(nb.get('total'))} packages só com especificação: "
206
228
  f"{len(nb['declaring'])} declaram subprogramas sem corpo (falham em runtime; a documentação por "
207
- f"subprograma fica sem fonte) e {len(nb['spec_only'])} são só constantes e tipos (legítimo).</p>")
229
+ f"subprograma fica sem fonte) e {len(nb['spec_only'])} são só constantes e tipos (legítimo).{_fora(nb)}</p>")
208
230
  if nb["declaring"]:
209
231
  out.append(_table(["Package", "Subprogramas", "Spec", "DDL"],
210
232
  [[f"<code>{_e(i['package'])}</code>", _n(i["subprograms"]), _e(i["spec_status"]), _e(i["last_ddl"])]
@@ -219,13 +241,17 @@ def to_html(report: dict, client: str | None = None) -> str:
219
241
  out.append(f"<p class='note'>Seção pulada: {_e(orr['skipped'])}.</p>")
220
242
  else:
221
243
  out.append(f"<p>{_e(orr.get('rules'))} regras de nome classificam objetos como não produtivos. "
222
- f"<b>{_n(orr.get('excluded'))}</b> de {_n(orr.get('total_objects'))} objetos saem da cobertura.</p>")
244
+ f"<b>{_n(orr.get('excluded'))}</b> de {_n(orr.get('total_objects'))} objetos saem da cobertura"
245
+ + (", já descontados das seções anteriores." if orr.get("applied")
246
+ else ". Não descontados das seções anteriores.") + "</p>")
223
247
  for kind, names in sorted((orr.get("by_kind") or {}).items()):
224
248
  out.append(f"<h3>{_e(kind)} ({len(names)})</h3>" + _cols(names))
225
249
 
226
250
  # encoding
227
251
  enc = r["encoding"]
228
252
  out.append("<h2>8. Codificação e acentuação</h2>")
253
+ if enc.get("excluded_by_rules"):
254
+ out.append(f"<p class='note'>{_fora(enc).strip()}</p>")
229
255
  if not enc.get("available", True):
230
256
  out.append("<p class='note'>Checagem indisponível.</p>")
231
257
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: baucli
3
- Version: 1.0.151
3
+ Version: 1.0.152
4
4
  Summary: Extracts verified business-logic and schema context from Oracle, PostgreSQL, MySQL and SQL Server
5
5
  Home-page: https://www.begcloud.com
6
6
  Author: BEG Support
@@ -41,5 +41,5 @@ def test_text_content_when_pypdf_available():
41
41
  text = "\n".join(p.extract_text() for p in reader.pages)
42
42
  for name in ("NAC_PCK_MKT_CAMPANHA", "NAC_RH_CST_GESTAX", "AD_ORG_BKP", "BEG_FNC_RETORNA_IPI",
43
43
  "NAC_PRC_B2B_REC_ITENS", "PCK_IMPORTACAO_MULTINAC", "PKG_URLS", "NAC_TMP", "FNC_LIMPA_STR",
44
- "BEG_CPG_RENEG", "Ziz Performance", "WE8ISO8859P1"):
44
+ "BEG_CPG_RENEG", "Ziz Performance", "WE8ISO8859P1", "fora da cobertura"):
45
45
  assert name in text, name
@@ -12,12 +12,12 @@ def _sample():
12
12
  "port": 1521, "service": "multisc", "user": "COMPIERE"}
13
13
  r["thresholds"] = {"source_max_chars": 50000, "max_columns": 30, "lines_unsafe": 6000,
14
14
  "chars_per_token": 4.0, "source": "BAU Server"}
15
- r["summary"] = {"critico": 1, "atencao": 2, "aviso": 1}
16
- r["plsql"] = {"total": 10, "flagged": [
15
+ r["summary"] = {"critico": 1, "atencao": 2, "aviso": 1, "scope": "cobertura", "excluded_by_rules": 2}
16
+ r["plsql"] = {"total": 10, "excluded_by_rules": 1, "flagged": [
17
17
  {"level": "CRÍTICO", "type": "PACKAGE BODY", "name": "NAC_PCK_MKT_CAMPANHA",
18
18
  "lines": 27898, "chars": 1291000, "tokens": 322750, "reason": "edição por IA insegura"}]}
19
19
  r["wide_tables"] = {"max_columns": 30, "items": [{"table": "NAC_RH_CST_GESTAX", "columns": 614, "lobs": 0}]}
20
- r["no_pk"] = {"total": 2, "no_identity": 1, "with_unique": 1, "items": [
20
+ r["no_pk"] = {"total": 2, "no_identity": 1, "with_unique": 1, "excluded_by_rules": 1, "items": [
21
21
  {"table": "AD_ORG_BKP", "columns": 14, "rows_est": 67, "uks": 0, "unique_indexes": 0, "has_identity": False},
22
22
  {"table": "BAU_CONFIG", "columns": 9, "rows_est": None, "uks": 1, "unique_indexes": 0, "has_identity": True}]}
23
23
  r["invalid"] = {"total": 2,
@@ -28,7 +28,8 @@ def _sample():
28
28
  {"package": "PCK_IMPORTACAO_MULTINAC", "subprograms": 26, "spec_status": "VALID", "last_ddl": "09/01/25 08:36"}],
29
29
  "spec_only": ["PKG_URLS"]}
30
30
  r["object_rules"] = {"source": "BAU Server", "skipped": None, "rules": 31, "total_objects": 6522,
31
- "excluded": 2, "by_kind": {"BACKUP": ["AD_ORG_BKP"], "TEMP": ["NAC_TMP"]}}
31
+ "excluded": 2, "by_kind": {"BACKUP": ["AD_ORG_BKP"], "TEMP": ["NAC_TMP"]},
32
+ "applied": True, "applied_reason": None}
32
33
  r["encoding"] = {"charset": "WE8ISO8859P1", "is_utf8": False, "available": True,
33
34
  "source_objects": [{"name": "FNC_LIMPA_STR", "type": "FUNCTION", "lines": 16}],
34
35
  "comments": [{"where": "COLUNA", "table": "BEG_CPG_RENEG", "column": "NAME",
@@ -42,10 +43,23 @@ def test_json_roundtrip_keeps_everything():
42
43
  r = _sample()
43
44
  back = json.loads(to_json(r))
44
45
  assert back["schema_version"] == SCHEMA_VERSION
45
- assert back["summary"] == {"critico": 1, "atencao": 2, "aviso": 1}
46
+ assert back["summary"] == {"critico": 1, "atencao": 2, "aviso": 1, "scope": "cobertura", "excluded_by_rules": 2}
46
47
  assert back["plsql"]["flagged"][0]["name"] == "NAC_PCK_MKT_CAMPANHA"
47
48
  assert back["no_pk"]["items"][1]["rows_est"] is None
48
49
  assert back["object_rules"]["by_kind"]["TEMP"] == ["NAC_TMP"]
50
+ assert back["summary"]["scope"] == "cobertura" and back["no_pk"]["excluded_by_rules"] == 1
51
+
52
+
53
+ def test_html_says_whether_rules_were_discounted():
54
+ html = to_html(_sample())
55
+ assert "cobertura do BAU" in html and "já descontados" in html
56
+ assert "(+1 fora da cobertura pelas regras de nome)" in html
57
+ r = empty_report()
58
+ r["object_rules"]["applied_reason"] = "flag --all-objects"
59
+ html = to_html(r)
60
+ # o título da seção 7 sempre diz "fora da cobertura"; o que não pode aparecer é o desconto por seção
61
+ assert "schema inteiro" in html and "flag --all-objects" in html
62
+ assert "fora da cobertura pelas regras de nome)" not in html
49
63
 
50
64
 
51
65
  def test_empty_report_has_every_section():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes