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.
- {baucli-1.0.151 → baucli-1.0.152}/PKG-INFO +1 -1
- {baucli-1.0.151 → baucli-1.0.152}/baucli/__init__.py +1 -1
- {baucli-1.0.151 → baucli-1.0.152}/baucli/cli.py +148 -91
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db_check_pdf.py +23 -8
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db_check_report.py +44 -18
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/PKG-INFO +1 -1
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_check_pdf.py +1 -1
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_check_report.py +19 -5
- {baucli-1.0.151 → baucli-1.0.152}/MANIFEST.in +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/README.md +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/apex_rebuild.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlang.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlang_profile.json +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/apexlint.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/api.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/canon.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/config.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db/__init__.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db/mssql.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db/mysql.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db/oracle.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/db/postgresql.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mssql/crm_data_mssql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mssql/crm_demo_mssql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mysql/crm_data_mysql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/mysql/crm_demo_mysql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/oracle/crm_demo_oracle.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/postgresql/crm_data_postgresql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/democrm_sql/postgresql/crm_demo_postgresql.sql +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/diffenvs.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_baseline.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_build.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_install.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_shell.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_schema.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_tools.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/docmig_spec_workspace.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/git.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/__init__.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi_canon.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/delphi_evidence.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/legacy/magic.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/mcp_server.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/project.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/rag_embed.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/sample_scan.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/scope_scan.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/screenshot.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/seed_check.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/skills.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/stress.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/sync.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/.apex/apexlang.json +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/pages/login.apx.tpl +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/shared-components/lists/navigation-bar.apx +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/app_shell/shared-components/themes/universal-theme/theme.apx +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/templates/logo-bau.jpg +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/uikit.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli/unwrap.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/SOURCES.txt +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/dependency_links.txt +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/entry_points.txt +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/requires.txt +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/baucli.egg-info/top_level.txt +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/pyproject.toml +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/setup.cfg +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/setup.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_binario.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_dialogo.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_apexlint_dominio.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_api_request_db.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_canon_anchored_markers.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_canon_multiengine.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_db_tenant_guard.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_canon.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_datasnap.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_evidence.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_delphi_fmx.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_delta_clock.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_deploy_alias.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_baseline.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build_extend.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_build_new.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_expressividade.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install_extend.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_install_shell.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_project.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_project_new.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_shell.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_analista.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema_11.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_schema_app.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_spec_use_baseline.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_sqlcl_padrao.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docmig_support_objects.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_docrev_dismiss.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_doctab_split.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_documentation_list_db_label.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doc_changeset.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doc_tags.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_doctab_text.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_mcp_schema_context.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_packaging_deps.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_post_apply_uikit.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_profiles_add_prompt.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_project_guarda_chuva.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_call_wrapper.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_datasnap_reach.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_from_depth.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_owner.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_scope_scan_pkg_func_from.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_apex_domain.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_lessons.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_skills_size_budget.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_spec_sem_beg.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_sync_413.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_sync_charset.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_table_status.py +0 -0
- {baucli-1.0.151 → baucli-1.0.152}/tests/test_uikit_scope.py +0 -0
|
@@ -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)
|
|
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)}
|
|
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)}
|
|
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
|
-
#
|
|
6405
|
-
#
|
|
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'}
|
|
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
|
|
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
|
|
6525
|
+
for nome, tipo, ak, rname, _tot in _rule_hits:
|
|
6484
6526
|
por_kind.setdefault(ak, []).append(nome)
|
|
6485
|
-
total = int(
|
|
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
|
|
6498
|
-
f"(ARTIFACT_KIND ≠ PROD)
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
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
|
-
|
|
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
|
|
153
|
-
f"<b>{len(pl['flagged'])}</b> sinalizados
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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:
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|