shapeaudit 1.0.2__tar.gz → 1.0.4__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 (119) hide show
  1. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/PKG-INFO +191 -180
  2. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/README.md +17 -6
  3. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ale_detector.py +8 -1
  4. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ale_fast.py +10 -1
  5. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/api.py +220 -11
  6. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/backends.py +10 -1
  7. {shapeaudit-1.0.2/modelcheck/experimental → shapeaudit-1.0.4/modelcheck}/calibration.py +10 -3
  8. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/cli.py +116 -28
  9. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/explorer.py +36 -13
  10. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/frechet.py +213 -42
  11. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/importance.py +182 -12
  12. shapeaudit-1.0.4/modelcheck/laudo.py +1282 -0
  13. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/orchestrator.py +79 -21
  14. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/pipeline.py +36 -5
  15. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/shape.py +5 -2
  16. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/slope.py +2 -2
  17. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/snapshot.py +91 -16
  18. shapeaudit-1.0.4/modelcheck/validation.py +466 -0
  19. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelguard/__init__.py +3 -1
  20. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/pyproject.toml +57 -57
  21. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/setup.cfg +4 -4
  22. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/PKG-INFO +191 -180
  23. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/SOURCES.txt +7 -0
  24. shapeaudit-1.0.4/tests/test_caminhos.py +122 -0
  25. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_chaos.py +5 -2
  26. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_classes_de_bug.py +100 -5
  27. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_frechet.py +5 -2
  28. shapeaudit-1.0.4/tests/test_idempotencia.py +52 -0
  29. shapeaudit-1.0.4/tests/test_regressao_saudaveis.py +137 -0
  30. shapeaudit-1.0.4/tests/test_revisao_docs.py +177 -0
  31. shapeaudit-1.0.4/tests/test_revisao_laudo.py +395 -0
  32. shapeaudit-1.0.4/tests/test_revisao_nucleo.py +322 -0
  33. shapeaudit-1.0.4/tests/test_revisao_validacao.py +235 -0
  34. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_snapshot_cli.py +13 -2
  35. shapeaudit-1.0.2/modelcheck/laudo.py +0 -885
  36. shapeaudit-1.0.2/modelcheck/validation.py +0 -297
  37. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/LICENSE +0 -0
  38. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/__init__.py +0 -0
  39. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/_validate.py +0 -0
  40. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ale.py +0 -0
  41. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ale_selective.py +0 -0
  42. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/audit.py +0 -0
  43. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/baselines.py +0 -0
  44. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/canary.py +0 -0
  45. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/causal.py +0 -0
  46. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/causal_bootstrap.py +0 -0
  47. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ci.py +0 -0
  48. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/compare/__init__.py +0 -0
  49. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/compare/model_comparator.py +0 -0
  50. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/confidence.py +0 -0
  51. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/contradict.py +0 -0
  52. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/dashboard.py +0 -0
  53. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/drift.py +0 -0
  54. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/__init__.py +0 -0
  55. {shapeaudit-1.0.2/modelcheck → shapeaudit-1.0.4/modelcheck/experimental}/calibration.py +0 -0
  56. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/gtsf.py +0 -0
  57. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/llm.py +0 -0
  58. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/llm_audit.py +0 -0
  59. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/rhale.py +0 -0
  60. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/topology.py +0 -0
  61. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/vision.py +0 -0
  62. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/experimental/zest.py +0 -0
  63. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/explainers/__init__.py +0 -0
  64. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/explainers/shap_explainer.py +0 -0
  65. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/fairness.py +0 -0
  66. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/geometry.py +0 -0
  67. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/gtsf.py +0 -0
  68. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/guard.py +0 -0
  69. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/integrations/__init__.py +0 -0
  70. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/integrations/mlflow_integration.py +0 -0
  71. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/leakage.py +0 -0
  72. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/leakage_temporal.py +0 -0
  73. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/narrate.py +0 -0
  74. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ood.py +0 -0
  75. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/paired.py +0 -0
  76. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/plots.py +0 -0
  77. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/psi_shap.py +0 -0
  78. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/ranking.py +0 -0
  79. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/report.py +0 -0
  80. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/rhale.py +0 -0
  81. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/rules/__init__.py +0 -0
  82. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/rules/business_rules.py +0 -0
  83. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/rules/rule_learner.py +0 -0
  84. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/score.py +0 -0
  85. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/shape_extensions.py +0 -0
  86. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/shapedist.py +0 -0
  87. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/silence.py +0 -0
  88. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/stability.py +0 -0
  89. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/studio.py +0 -0
  90. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/temporal.py +0 -0
  91. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/temporal_shape.py +0 -0
  92. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/topology.py +0 -0
  93. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/trending.py +0 -0
  94. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/universal.py +0 -0
  95. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/modelcheck/zest.py +0 -0
  96. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit/__init__.py +0 -0
  97. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/dependency_links.txt +0 -0
  98. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/entry_points.txt +0 -0
  99. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/requires.txt +0 -0
  100. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/shapeaudit.egg-info/top_level.txt +0 -0
  101. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_comparabilidade.py +0 -0
  102. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_consistency.py +0 -0
  103. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_edge_cases.py +0 -0
  104. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_fairness.py +0 -0
  105. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_golden.py +0 -0
  106. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_importance_core.py +0 -0
  107. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_laudo.py +0 -0
  108. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_llm_experimental.py +0 -0
  109. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_modelcheck.py +0 -0
  110. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_performance.py +0 -0
  111. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_pipeline.py +0 -0
  112. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_properties.py +0 -0
  113. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_public_api.py +0 -0
  114. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_regression.py +0 -0
  115. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_robustez_snapshot.py +0 -0
  116. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_schema_compat.py +0 -0
  117. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_shape_core.py +0 -0
  118. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_tutorial.py +0 -0
  119. {shapeaudit-1.0.2 → shapeaudit-1.0.4}/tests/test_vision_experimental.py +0 -0
@@ -1,180 +1,191 @@
1
- Metadata-Version: 2.4
2
- Name: shapeaudit
3
- Version: 1.0.2
4
- Summary: Descubra se o modelo novo aprendeu a mesma coisa que o antigo — auditoria entre duas versões, com laudo em HTML
5
- Author-email: Riviane Maria Albuquerque Donha <riviane.donha@ufpr.br>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/Rivianee/shapeaudit
8
- Keywords: machine-learning,mlops,model-monitoring,explainability,shap,model-audit,ci-cd
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
- Classifier: Topic :: Software Development :: Quality Assurance
20
- Requires-Python: >=3.9
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: shap<0.53,>=0.44
24
- Requires-Dist: pandas>=2.0.0
25
- Requires-Dist: numpy>=1.24.0
26
- Requires-Dist: pyyaml>=6.0
27
- Requires-Dist: scipy>=1.10
28
- Requires-Dist: matplotlib>=3.7
29
- Requires-Dist: scikit-learn>=1.3
30
- Provides-Extra: mlflow
31
- Requires-Dist: mlflow-skinny>=2.10; extra == "mlflow"
32
- Provides-Extra: xgboost
33
- Requires-Dist: xgboost<3,>=1.7; extra == "xgboost"
34
- Provides-Extra: lightgbm
35
- Requires-Dist: lightgbm>=4.0; extra == "lightgbm"
36
- Provides-Extra: evidently
37
- Requires-Dist: evidently>=0.7; extra == "evidently"
38
- Provides-Extra: fairlearn
39
- Requires-Dist: fairlearn>=0.10; extra == "fairlearn"
40
- Provides-Extra: dev
41
- Requires-Dist: pytest>=7.0; extra == "dev"
42
- Dynamic: license-file
43
-
44
- # ShapeAudit
45
-
46
- **Seu modelo novo tem a mesma acurácia do antigo. Mas será que ele aprendeu a
47
- mesma coisa?**
48
-
49
- Toda vez que um modelo é retreinado, alguém precisa decidir se promove a versão
50
- nova. Na prática, a decisão costuma ser uma só: *a acurácia caiu?* Se não caiu,
51
- sobe.
52
-
53
- O problema é que essa pergunta não cobre tudo. Duas versões podem acertar na
54
- mesma proporção e **usar as variáveis de um jeito diferente** — inclusive uma
55
- delas ter perdido uma coluna no caminho, sem que a métrica acuse.
56
-
57
- Um exemplo real, com dados públicos de churn de telecom:
58
-
59
- ```
60
- versão em produção: 0.815
61
- versão candidata: 0.807
62
- diferença: 0.008 ← praticamente nada
63
-
64
- ShapeAudit: NÃO PROMOVA — a variável `Contract` deixou de
65
- influenciar a previsão e chega constante no treino
66
- ```
67
-
68
- Uma coluna virou constante depois de uma mudança no pipeline. **Um gate por
69
- métrica aprovaria essa versão.**
70
-
71
- ## Instalar
72
-
73
- ```bash
74
- pip install shapeaudit
75
- ```
76
-
77
- ## Usar
78
-
79
- ```python
80
- from shapeaudit import Guard
81
-
82
- r = Guard().audit(modelo_antigo, modelo_novo, X_avaliacao, y_avaliacao,
83
- X_train=dados_de_treino_do_modelo_novo)
84
-
85
- print(r.verdict) # APROVADO | REVISAR | ATENCAO | BLOQUEADO
86
- print(r.root_cause_features) # ['Contract']
87
- open('laudo.html', 'w').write(r.to_html()) # laudo para abrir no navegador
88
- ```
89
-
90
- `X_train` é o treino do **modelo novo**. É esse argumento que separa "a coluna
91
- sumiu do pipeline" de "o modelo deixou de usar a coluna". Sem ele a auditoria
92
- roda, mas avisa que não verificou isso.
93
-
94
- ## O que você recebe
95
-
96
- | veredito | o que aconteceu | o que fazer |
97
- |---|---|---|
98
- | **BLOQUEADO** | uma variável sumiu do modelo e está constante no treino | não promova: verifique o pipeline |
99
- | **ATENÇÃO** | os dados chegam diferentes do treino, ou uma variável quase idêntica assumiu o lugar de outra | promova com monitoramento, recalibre |
100
- | **REVISAR** | com os mesmos dados, o modelo novo responde de outro jeito | olhe as curvas antes de promover |
101
- | **APROVADO** | nenhuma verificação encontrou mudança | pode promover |
102
-
103
- E um laudo HTML que responde, nesta ordem: o que aconteceu, o que mudou de
104
- peso e de comportamento, onde exatamente mudou, se isso muda decisões, por onde
105
- começar a investigar, e **o que não foi verificado**.
106
-
107
- ## Experimentar em dois minutos
108
-
109
- - **No Colab**, sem instalar nada: abra `ShapeAudit_Colab.ipynb` e rode tudo.
110
- - **Com uma planilha sua**: `python demo_excel.py meus_dados.xlsx nome_do_alvo`
111
-
112
- Os dois terminam com o laudo aberto e um caso de pipeline quebrado, para você
113
- ver os dois extremos.
114
-
115
- ## Três perguntas, três camadas
116
-
117
- 1. **Alguma variável sumiu do modelo?** Compara o peso de cada variável nas
118
- duas versões e distingue "a coluna sumiu" de "o modelo deixou de usar".
119
- 2. **Os dados chegam como no treino?** Compara o treino do modelo novo com os
120
- dados que ele vai pontuar.
121
- 3. **O modelo passou a reagir de outro jeito?** Compara a curva de efeito de
122
- cada variável entre as duas versões. Uma distância detecta a mudança e uma
123
- classificação em cinco formas a nomeia.
124
-
125
- O veredito usa só essas três. Extremos, subgrupos e impacto em grupos de
126
- pessoas aparecem como verificações adicionais.
127
-
128
- ## Calibrar no seu caso
129
-
130
- O limiar padrão veio de uma bancada sintética. Calibre com retreinos que você
131
- **teria promovido** — não precisa de rótulo nem de falha conhecida:
132
-
133
- ```python
134
- from shapeaudit.frechet import calibrar_limiar
135
- print(calibrar_limiar([(modelo_jan, modelo_fev), ...], X_avaliacao)['limiar'])
136
- ```
137
-
138
- Medido: de 0,12 a 0,47 em seis conjuntos de dados, e o limiar de modelos de
139
- boosting é cinco a seis vezes o de florestas nos mesmos dados. Calibre por
140
- domínio **e** por família de modelo.
141
-
142
- ## O que ele não faz
143
-
144
- - **Não bloqueia degradação parcial:** com até 80% das linhas de uma variável
145
- corrompidas, o modelo perde 4 pontos de R² e nada dispara.
146
- - **Em bases pequenas** (menos de ~400 linhas de avaliação) pede revisão em
147
- cerca de um terço dos retreinos saudáveis.
148
- - **Fora de árvores** (MLP, ridge) é experimental: bloqueia falha de pipeline,
149
- mas alarma mais e é cerca de dez vezes mais lento.
150
- - **Não distingue sazonalidade de mudança real.**
151
- - **Não diz qual das duas versões está certa** — só que elas discordam.
152
-
153
- `LIMITATIONS.md` traz cada limite com o número medido.
154
-
155
- ## Confiança no veredito
156
-
157
- Em 240 controles saudáveis de benchmark, 161 retreinos reais de séries públicas
158
- do setor elétrico brasileiro e 12 conjuntos públicos, **nenhum retreino saudável
159
- foi bloqueado**. Quando o pipeline quebrou de verdade, bloqueou 16 de 18 casos
160
- nomeando a variável.
161
-
162
- A recíproca não vale: não bloquear não garante que está tudo bem.
163
-
164
- ## Nomes
165
-
166
- A distribuição é `shapeaudit`. Três nomes de import funcionam, e apontam para
167
- a mesma implementação:
168
-
169
- ```python
170
- import shapeaudit # nome atual
171
- import modelguard # nome anterior (pacote `modelguard-ml`), mantido
172
- import modelcheck # nome original, mantido por compatibilidade
173
- ```
174
-
175
- Quem já usava `modelguard-ml` não precisa mudar código: troque a instalação
176
- para `shapeaudit` e os imports antigos continuam funcionando.
177
-
178
- ## Licença
179
-
180
- MIT. README em português; a documentação de limites também.
1
+ Metadata-Version: 2.4
2
+ Name: shapeaudit
3
+ Version: 1.0.4
4
+ Summary: Descubra se o modelo novo aprendeu a mesma coisa que o antigo — auditoria entre duas versões, com laudo em HTML
5
+ Author-email: Riviane Maria Albuquerque Donha <riviane.donha@ufpr.br>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Rivianee/shapeaudit
8
+ Keywords: machine-learning,mlops,model-monitoring,explainability,shap,model-audit,ci-cd
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: shap<0.53,>=0.44
24
+ Requires-Dist: pandas>=2.0.0
25
+ Requires-Dist: numpy>=1.24.0
26
+ Requires-Dist: pyyaml>=6.0
27
+ Requires-Dist: scipy>=1.10
28
+ Requires-Dist: matplotlib>=3.7
29
+ Requires-Dist: scikit-learn>=1.3
30
+ Provides-Extra: mlflow
31
+ Requires-Dist: mlflow-skinny>=2.10; extra == "mlflow"
32
+ Provides-Extra: xgboost
33
+ Requires-Dist: xgboost<3,>=1.7; extra == "xgboost"
34
+ Provides-Extra: lightgbm
35
+ Requires-Dist: lightgbm>=4.0; extra == "lightgbm"
36
+ Provides-Extra: evidently
37
+ Requires-Dist: evidently>=0.7; extra == "evidently"
38
+ Provides-Extra: fairlearn
39
+ Requires-Dist: fairlearn>=0.10; extra == "fairlearn"
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest>=7.0; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # ShapeAudit
45
+
46
+ **Seu modelo novo tem a mesma acurácia do antigo. Mas será que ele aprendeu a
47
+ mesma coisa?**
48
+
49
+ Toda vez que um modelo é retreinado, alguém precisa decidir se promove a versão
50
+ nova. Na prática, a decisão costuma ser uma só: *a acurácia caiu?* Se não caiu,
51
+ sobe.
52
+
53
+ O problema é que essa pergunta não cobre tudo. Duas versões podem acertar na
54
+ mesma proporção e **usar as variáveis de um jeito diferente** — inclusive uma
55
+ delas ter perdido uma coluna no caminho, sem que a métrica acuse.
56
+
57
+ Um exemplo real, com dados públicos de churn de telecom:
58
+
59
+ ```
60
+ versão em produção: 0.815
61
+ versão candidata: 0.807
62
+ diferença: 0.008 ← praticamente nada
63
+
64
+ ShapeAudit: NÃO PROMOVA — a variável `Contract` deixou de
65
+ influenciar a previsão e chega constante no treino
66
+ ```
67
+
68
+ Uma coluna virou constante depois de uma mudança no pipeline. **Um gate por
69
+ métrica aprovaria essa versão.**
70
+
71
+ ## Instalar
72
+
73
+ ```bash
74
+ pip install shapeaudit
75
+ ```
76
+
77
+ ## Usar
78
+
79
+ ```python
80
+ from shapeaudit import Guard
81
+
82
+ r = Guard().audit(modelo_antigo, modelo_novo, X_avaliacao, y_avaliacao,
83
+ X_train=dados_de_treino_do_modelo_novo)
84
+
85
+ print(r.verdict) # APROVADO | REVISAR | ATENCAO | BLOQUEADO | INDETERMINADO
86
+ print(r.root_cause_features) # ['Contract']
87
+ open('laudo.html', 'w', encoding='utf-8').write(r.to_html()) # laudo para abrir no navegador
88
+ ```
89
+
90
+ `X_train` é o treino do **modelo novo**. É esse argumento que separa "a coluna
91
+ sumiu do pipeline" de "o modelo deixou de usar a coluna". Sem ele a auditoria
92
+ roda, mas avisa que não verificou isso.
93
+
94
+ ## O que você recebe
95
+
96
+ | veredito | o que aconteceu | o que fazer |
97
+ |---|---|---|
98
+ | **BLOQUEADO** | uma variável sumiu do modelo e está constante no treino | não promova: verifique o pipeline |
99
+ | **ATENÇÃO** | os dados chegam diferentes do treino, ou uma variável quase idêntica assumiu o lugar de outra | promova com monitoramento, recalibre |
100
+ | **REVISAR** | com os mesmos dados, o modelo novo responde de outro jeito | olhe as curvas antes de promover |
101
+ | **APROVADO** | nenhuma verificação encontrou mudança | pode promover |
102
+ | **INDETERMINADO** | uma verificação principal não rodou (ex.: base de avaliação pequena demais, modelo sem suporte) | não promova com base nesta auditoria: veja a lacuna declarada no laudo |
103
+
104
+ E um laudo HTML que responde, nesta ordem: o que aconteceu, o que mudou de
105
+ peso e de comportamento, onde exatamente mudou, se isso muda decisões, por onde
106
+ começar a investigar, e **o que não foi verificado**.
107
+
108
+ ## Experimentar em dois minutos
109
+
110
+ Os arquivos abaixo estão no [repositório](https://github.com/Rivianee/shapeaudit),
111
+ não no pacote do `pip` — baixe o repositório (ou só o arquivo) antes.
112
+
113
+ - **No Colab**, sem instalar nada: abra `ShapeAudit_Colab.ipynb` e rode tudo.
114
+ Termina com o laudo e com um caso de pipeline quebrado.
115
+ - **Com uma planilha sua**: `python demo_excel.py meus_dados.xlsx nome_do_alvo`
116
+ (sem argumentos usa `exemplo_churn.xlsx`). Audita um retreino da sua
117
+ planilha e grava `laudo_shapeaudit.html`.
118
+
119
+ ## Três perguntas, três camadas
120
+
121
+ 1. **Alguma variável sumiu do modelo?** Compara o peso de cada variável nas
122
+ duas versões e distingue "a coluna sumiu" de "o modelo deixou de usar".
123
+ 2. **Os dados chegam como no treino?** Compara o treino do modelo novo com os
124
+ dados que ele vai pontuar.
125
+ 3. **O modelo passou a reagir de outro jeito?** Compara a curva de efeito de
126
+ cada variável entre as duas versões. Uma distância detecta a mudança e uma
127
+ classificação em cinco formas a nomeia.
128
+
129
+ O veredito usa só essas três. Extremos, subgrupos e impacto em grupos de
130
+ pessoas aparecem como verificações adicionais.
131
+
132
+ ## Calibrar no seu caso
133
+
134
+ O limiar padrão veio de uma bancada sintética. Calibre com retreinos que você
135
+ **teria promovido** — não precisa de rótulo nem de falha conhecida:
136
+
137
+ ```python
138
+ from shapeaudit import Guard
139
+ from shapeaudit.frechet import calibrar_limiar
140
+
141
+ cal = calibrar_limiar([(modelo_jan, modelo_fev), ...], X_avaliacao)
142
+ r = Guard(frechet_threshold=cal['limiar']).audit(producao, candidata, X_avaliacao, y)
143
+ ```
144
+
145
+ Em domínio com efeito por interação forte, calibrar não é opcional: com o
146
+ limiar padrão, retreinos saudáveis pedem revisão quase sempre
147
+ (`LIMITATIONS.md`, seção 7-quater).
148
+
149
+ Medido: de 0,12 a 0,47 em seis conjuntos de dados, e o limiar de modelos de
150
+ boosting é cinco a seis vezes o de florestas nos mesmos dados. Calibre por
151
+ domínio **e** por família de modelo.
152
+
153
+ ## O que ele não faz
154
+
155
+ - **Não bloqueia degradação parcial:** com até 80% das linhas de uma variável
156
+ corrompidas, o modelo perde 4 pontos de R² e nada dispara.
157
+ - **Em bases pequenas** (menos de ~400 linhas de avaliação) pede revisão em
158
+ cerca de um terço dos retreinos saudáveis.
159
+ - **Fora de árvores** (MLP, ridge) é experimental: bloqueia falha de pipeline,
160
+ mas alarma mais e é cerca de dez vezes mais lento.
161
+ - **Não distingue sazonalidade de mudança real.**
162
+ - **Não diz qual das duas versões está certa** — só que elas discordam.
163
+
164
+ `LIMITATIONS.md` traz cada limite com o número medido.
165
+
166
+ ## Confiança no veredito
167
+
168
+ Em 240 controles saudáveis de benchmark, 161 retreinos reais de séries públicas
169
+ do setor elétrico brasileiro e 12 conjuntos públicos, **nenhum retreino saudável
170
+ foi bloqueado**. Quando o pipeline quebrou de verdade, bloqueou 16 de 18 casos
171
+ nomeando a variável.
172
+
173
+ A recíproca não vale: não bloquear não garante que está tudo bem.
174
+
175
+ ## Nomes
176
+
177
+ A distribuição é `shapeaudit`. Três nomes de import funcionam, e apontam para
178
+ a mesma implementação:
179
+
180
+ ```python
181
+ import shapeaudit # nome atual
182
+ import modelguard # nome anterior (pacote `modelguard-ml`), mantido
183
+ import modelcheck # nome original, mantido por compatibilidade
184
+ ```
185
+
186
+ Quem já usava `modelguard-ml` não precisa mudar código: troque a instalação
187
+ para `shapeaudit` e os imports antigos continuam funcionando.
188
+
189
+ ## Licença
190
+
191
+ MIT. README em português; a documentação de limites também.
@@ -39,9 +39,9 @@ from shapeaudit import Guard
39
39
  r = Guard().audit(modelo_antigo, modelo_novo, X_avaliacao, y_avaliacao,
40
40
  X_train=dados_de_treino_do_modelo_novo)
41
41
 
42
- print(r.verdict) # APROVADO | REVISAR | ATENCAO | BLOQUEADO
42
+ print(r.verdict) # APROVADO | REVISAR | ATENCAO | BLOQUEADO | INDETERMINADO
43
43
  print(r.root_cause_features) # ['Contract']
44
- open('laudo.html', 'w').write(r.to_html()) # laudo para abrir no navegador
44
+ open('laudo.html', 'w', encoding='utf-8').write(r.to_html()) # laudo para abrir no navegador
45
45
  ```
46
46
 
47
47
  `X_train` é o treino do **modelo novo**. É esse argumento que separa "a coluna
@@ -56,6 +56,7 @@ roda, mas avisa que não verificou isso.
56
56
  | **ATENÇÃO** | os dados chegam diferentes do treino, ou uma variável quase idêntica assumiu o lugar de outra | promova com monitoramento, recalibre |
57
57
  | **REVISAR** | com os mesmos dados, o modelo novo responde de outro jeito | olhe as curvas antes de promover |
58
58
  | **APROVADO** | nenhuma verificação encontrou mudança | pode promover |
59
+ | **INDETERMINADO** | uma verificação principal não rodou (ex.: base de avaliação pequena demais, modelo sem suporte) | não promova com base nesta auditoria: veja a lacuna declarada no laudo |
59
60
 
60
61
  E um laudo HTML que responde, nesta ordem: o que aconteceu, o que mudou de
61
62
  peso e de comportamento, onde exatamente mudou, se isso muda decisões, por onde
@@ -63,11 +64,14 @@ começar a investigar, e **o que não foi verificado**.
63
64
 
64
65
  ## Experimentar em dois minutos
65
66
 
67
+ Os arquivos abaixo estão no [repositório](https://github.com/Rivianee/shapeaudit),
68
+ não no pacote do `pip` — baixe o repositório (ou só o arquivo) antes.
69
+
66
70
  - **No Colab**, sem instalar nada: abra `ShapeAudit_Colab.ipynb` e rode tudo.
71
+ Termina com o laudo e com um caso de pipeline quebrado.
67
72
  - **Com uma planilha sua**: `python demo_excel.py meus_dados.xlsx nome_do_alvo`
68
-
69
- Os dois terminam com o laudo aberto e um caso de pipeline quebrado, para você
70
- ver os dois extremos.
73
+ (sem argumentos usa `exemplo_churn.xlsx`). Audita um retreino da sua
74
+ planilha e grava `laudo_shapeaudit.html`.
71
75
 
72
76
  ## Três perguntas, três camadas
73
77
 
@@ -88,10 +92,17 @@ O limiar padrão veio de uma bancada sintética. Calibre com retreinos que você
88
92
  **teria promovido** — não precisa de rótulo nem de falha conhecida:
89
93
 
90
94
  ```python
95
+ from shapeaudit import Guard
91
96
  from shapeaudit.frechet import calibrar_limiar
92
- print(calibrar_limiar([(modelo_jan, modelo_fev), ...], X_avaliacao)['limiar'])
97
+
98
+ cal = calibrar_limiar([(modelo_jan, modelo_fev), ...], X_avaliacao)
99
+ r = Guard(frechet_threshold=cal['limiar']).audit(producao, candidata, X_avaliacao, y)
93
100
  ```
94
101
 
102
+ Em domínio com efeito por interação forte, calibrar não é opcional: com o
103
+ limiar padrão, retreinos saudáveis pedem revisão quase sempre
104
+ (`LIMITATIONS.md`, seção 7-quater).
105
+
95
106
  Medido: de 0,12 a 0,47 em seis conjuntos de dados, e o limiar de modelos de
96
107
  boosting é cinco a seis vezes o de florestas nos mesmos dados. Calibre por
97
108
  domínio **e** por família de modelo.
@@ -102,7 +102,14 @@ def ale_shape_profile(model,
102
102
  Xhi[feature] = bins[i + 1]
103
103
 
104
104
  # Efeito local médio do bin
105
- delta = model.predict(Xhi.values) - model.predict(Xlo.values)
105
+ # BUG 84 (v1.0.4): chamava model.predict() direto — em classificador
106
+ # com 3+ classes, rótulo virava número e um par saudável saía
107
+ # APROVADO sem lacuna. Agora usa a mesma saída do ALE rápido, que
108
+ # falha de propósito com 3+ classes (vira lacuna declarada).
109
+ from modelcheck.ale_fast import _predict as _pred_ale
110
+ # .values: mantém o comportamento anterior com X sem os nomes do
111
+ # treino (numpy convertido) — passar o DataFrame quebrava o predict
112
+ delta = _pred_ale(model, Xhi.values) - _pred_ale(model, Xlo.values)
106
113
  ale_means.append(float(delta.mean()))
107
114
  centers.append((bins[i] + bins[i + 1]) / 2)
108
115
 
@@ -33,8 +33,17 @@ def _predict(model, X: pd.DataFrame) -> np.ndarray:
33
33
  p = model.predict_proba(X)
34
34
  if p.ndim == 2 and p.shape[1] == 2:
35
35
  return np.asarray(p[:, 1], dtype=float)
36
+ if p.ndim == 2 and p.shape[1] > 2:
37
+ # BUG 70 (v1.0.4): com 3+ classes caía em predict() e tratava
38
+ # o rótulo como número (0->2 valia o dobro de 0->1). Não há
39
+ # curva única honesta aqui: falha explícita, e a camada vira
40
+ # lacuna declarada em vez de número errado.
41
+ raise ValueError('curva de efeito para classificador com '
42
+ f'{p.shape[1]} classes não é suportada')
43
+ except ValueError:
44
+ raise
36
45
  except Exception:
37
- pass
46
+ pass # noqa: lacuna-nao-aplicavel — sem predict_proba utilizável
38
47
  return np.asarray(model.predict(X), dtype=float)
39
48
 
40
49