From 9d8b84a106dff7d3d992610b2e26bcf5c5e92ab6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:54:15 +0000 Subject: [PATCH] add specs for Module 2: KPI severity cleanup and unified SQL schema - specs/002-mod2-kpi-severity-cleanup/spec.md: Remove INFO severity, restrict to 4 KPI cards (CRITICAL/HIGH/MEDIUM/LOW), remove 'Datos Analizados' tab - specs/003-mod2-unified-sql-schema/spec.md: Unify all 10 SQL queries to 6-column schema (DatabaseName, TableName, ObjectName, FindingCategory, LastCollectTimeStamp, RemediationDDL) Co-Authored-By: Ricardo Enciso --- specs/002-mod2-kpi-severity-cleanup/spec.md | 81 +++++++++++++ specs/003-mod2-unified-sql-schema/spec.md | 127 ++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 specs/002-mod2-kpi-severity-cleanup/spec.md create mode 100644 specs/003-mod2-unified-sql-schema/spec.md diff --git a/specs/002-mod2-kpi-severity-cleanup/spec.md b/specs/002-mod2-kpi-severity-cleanup/spec.md new file mode 100644 index 0000000..4a79001 --- /dev/null +++ b/specs/002-mod2-kpi-severity-cleanup/spec.md @@ -0,0 +1,81 @@ +# Feature: Módulo 2 — Ajuste de severidad de KPIs y Limpieza de UI + +> **Spec ID:** 002-mod2-kpi-severity-cleanup +> **Autor:** Ricardo +> **Fecha:** 2026-05-14 +> **Estado:** Draft + +--- + +## Contexto + +El Módulo 2 actualmente calcula una categoría "INFO" en las tarjetas de KPI y expone +datos crudos en la interfaz. Se requiere restringir los niveles de severidad gerencial +y limpiar el renderizado de la UI. + +--- + +## Requisitos Funcionales + +1. **RF-01:** Eliminar el cálculo, conteo y visualización de la severidad "INFO" + en el Analyzer y la UI. +2. **RF-02:** Mantener exclusivamente 4 tarjetas de KPI de severidad: + CRITICAL, HIGH, MEDIUM, LOW. +3. **RF-03:** Eliminar la pestaña "Datos Analizados" de la UI. + +--- + +## Contrato de Datos (I/O) + +- **Output (Analyzer → UI):** El diccionario resultante que alimenta los KPIs debe + contener ESTRICTAMENTE las claves de severidad: `['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']`. + Si el Analyzer inyecta la clave 'INFO', se considera un fallo de contrato. + +--- + +## Criterios de Aceptación + +### AC-01: Renderizado estricto de KPIs +- **Given** el diccionario de resultados del Analyzer. +- **When** la UI procesa las métricas de severidad. +- **Then** renderiza exactamente 4 columnas (`st.columns(4)`) distribuyendo + CRITICAL, HIGH, MEDIUM, LOW. + +### AC-02: Control de Pestañas (Tabs) +- **Given** la sección "Resultados del Análisis". +- **When** se renderizan las pestañas. +- **Then** el código usa ESTRICTAMENTE `st.tabs(["Hallazgos", "Scripts de Remediación"])`. + No debe existir ninguna referencia a "Datos Analizados". + +--- + +## Restricciones Arquitectónicas + +- [ ] Cambios en `analyzers/mod2_stats_analyzer.py`: eliminar asignación de Severity.INFO +- [ ] Cambios en `ui/pages/2_Module_2_Statistics.py`: 4 columnas de KPI, eliminar tab "Datos Analizados" +- [ ] No modificar la estructura de `collectors/mod2_stats_collector.py` +- [ ] No modificar archivos SQL + +--- + +## Edge Cases + +1. ¿Qué pasa si un hallazgo no tiene severidad asignada? → Debe asignarse LOW por defecto, nunca INFO. +2. ¿Qué pasa si todos los hallazgos son de una sola severidad? → Los 3 KPIs restantes muestran 0. + +--- + +## Fuera de Alcance + +- Modificación del motor de 16 reglas (`analyzers/rules/`) +- Cambios en la Home Page (`ui/app.py`) +- Cambios en otros módulos (3-10) + +--- + +## Archivos a Crear/Modificar + +| Acción | Archivo | Descripción | +|--------|---------|-------------| +| Modificar | `analyzers/mod2_stats_analyzer.py` | Eliminar Severity.INFO | +| Modificar | `ui/pages/2_Module_2_Statistics.py` | 4 KPIs, eliminar tab "Datos Analizados" | diff --git a/specs/003-mod2-unified-sql-schema/spec.md b/specs/003-mod2-unified-sql-schema/spec.md new file mode 100644 index 0000000..04db83f --- /dev/null +++ b/specs/003-mod2-unified-sql-schema/spec.md @@ -0,0 +1,127 @@ +# Feature: Módulo 2 — Estandarización de Esquema SQL (Unified Output) + +> **Spec ID:** 003-mod2-unified-sql-schema +> **Autor:** Ricardo +> **Fecha:** 2026-05-14 +> **Estado:** Draft + +--- + +## Contexto + +Los 10 queries del Módulo 2 retornan diferentes estructuras de columnas. Al +concatenarlos en Python, la tabla de "Hallazgos" en la UI colapsa con columnas +innecesarias y valores nulos. Se requiere un esquema de salida estrictamente +unificado a nivel SQL. + +--- + +## Requisitos Funcionales + +1. **RF-01:** Refactorizar los 10 archivos `.sql` en `sql/module_2_stats/` para + que retornen EXACTAMENTE la misma estructura de columnas. +2. **RF-02:** Si un query no posee la información natural para una columna + (ej. `LastCollectTimeStamp` en un query de tablas sin estadísticas), debe + inyectar un casteo nulo explícito (ej. `CAST(NULL AS TIMESTAMP) AS LastCollectTimeStamp`). +3. **RF-03:** Cada query debe inyectar una columna estática llamada `FindingCategory` + con el nombre de la regla (ej. `'Stale Statistics'`). + +--- + +## Contrato de Datos (I/O — Esquema Obligatorio) + +Todos los queries SQL DEBEN retornar estrictamente este DDL de salida +(en este orden exacto y con estos alias): + +| # | Columna | Tipo | Descripción | +|---|---------|------|-------------| +| 1 | `DatabaseName` | VARCHAR | Nombre de la base de datos | +| 2 | `TableName` | VARCHAR | Nombre de la tabla | +| 3 | `ObjectName` | VARCHAR | ColumnName, IndexName o StatsName según aplique. Si aplica a toda la tabla: `'TABLE LEVEL'` | +| 4 | `FindingCategory` | VARCHAR | Nombre de la regla (ej. `'Missing Partition Stats'`) | +| 5 | `LastCollectTimeStamp` | TIMESTAMP | Última recolección. `CAST(NULL AS TIMESTAMP)` si no aplica | +| 6 | `RemediationDDL` | VARCHAR | Sentencia `COLLECT STATISTICS` lista para ejecutar | + +--- + +## Criterios de Aceptación + +### AC-01: Concatenación Limpia +- **Given** la ejecución de los 10 queries. +- **When** el Collector/Analyzer los unifica. +- **Then** el DataFrame final tiene exactamente 6 columnas, eliminando la explosión de campos. + +### AC-02: FindingCategory presente en cada query +- **Given** cualquiera de los 10 archivos SQL. +- **When** se ejecuta individualmente. +- **Then** el resultado incluye una columna `FindingCategory` con valor estático + correspondiente al nombre de la regla. + +### AC-03: Casteo nulo explícito +- **Given** un query que no tiene información natural para `LastCollectTimeStamp` + (ej. `04_missing_table.sql`). +- **When** se ejecuta. +- **Then** la columna `LastCollectTimeStamp` existe en el resultado con valor `NULL` + (tipo TIMESTAMP), no con la columna ausente. + +### AC-04: RemediationDDL generado en SQL +- **Given** cualquiera de los 10 archivos SQL. +- **When** se ejecuta. +- **Then** la columna `RemediationDDL` contiene una sentencia `COLLECT STATISTICS` + válida construida con `||` (concatenación SQL), no generada en Python. + +--- + +## Restricciones Arquitectónicas + +- [ ] Modificar los 10 archivos `.sql` en `sql/module_2_stats/` +- [ ] Mantener compatibilidad con `BaseCollector.execute_query()` (retorna DataFrame) +- [ ] No agregar lógica de concatenación/renombrado en Python — todo se resuelve en SQL +- [ ] Preservar los placeholders `{placeholder}` existentes para filtros dinámicos +- [ ] SQL puro, sin SQL inline en Python + +--- + +## Edge Cases + +1. ¿Qué pasa si una tabla no tiene ColumnName? → `ObjectName = 'TABLE LEVEL'`. +2. ¿Qué pasa si el query retorna 0 filas? → DataFrame vacío con las 6 columnas del esquema. +3. ¿Qué pasa si `RemediationDDL` excede el límite de VARCHAR? → Truncar a 10000 chars. +4. ¿Qué pasa si `DatabaseName` o `TableName` contienen caracteres especiales? → + Escapar con comillas dobles en `RemediationDDL`. + +--- + +## Fuera de Alcance + +- Cambios en el motor de 16 reglas (`analyzers/rules/`) +- Cambios en la Home Page (`ui/app.py`) +- Refactorización del Collector o Analyzer (los cambios son exclusivamente SQL) + +--- + +## Archivos a Crear/Modificar + +| Acción | Archivo | Descripción | +|--------|---------|-------------| +| Modificar | `sql/module_2_stats/01_unused_objects.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/02_sample_candidates.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/03_missing_partition.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/04_missing_table.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/05_missing_index.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/06_stale_stats.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/07_zero_stats.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/08_multicolumn.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/09_skipped_sample.sql` | Adaptar al esquema unificado de 6 columnas | +| Modificar | `sql/module_2_stats/10_dbc_recommendations.sql` | Adaptar al esquema unificado de 6 columnas | + +--- + +## Notas Adicionales + +- El esquema unificado permite que el Collector haga `pd.concat()` directamente + sin explosión de columnas. +- La columna `FindingCategory` permite filtrar y agrupar hallazgos en la UI sin + necesidad de mapeos adicionales en Python. +- La columna `RemediationDDL` generada en SQL elimina la necesidad de que el + Analyzer o `DDLRecommender` construya las sentencias en Python.