{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Projeto Final — Aprendizado de Máquina Supervisionado\n",
        "## Notebook de Apoio: Validação Automatizada do \"Gate de Sanidade\"\n",
        "\n",
        "**Disciplina:** *Aprendizado de Máquina Supervisionado (MC886 / IC-UNICAMP)*  \n",
        "**Professor:** *Marcos Medeiros Raimundo*  \n",
        "**Semestre:** *2º Semestre de 2026*  \n",
        "\n",
        "[← Voltar ao Enunciado do Projeto Final](./projeto.qmd)\n",
        "\n\n| Nome | RA |\n|---|---|\n| *Integrante 1* | *RA 1* |\n| *Integrante 2* | *RA 2* |\n| *Integrante 3 (se houver)* | *RA 3* |\n\n> **Composição da equipe:** 2 ou 3 alunos, impreterivelmente (trabalhos individuais ou grupos com 4+ não são permitidos).\n\n",
        "\n",
        "---\n",
        "\n",
        "### Objetivo deste Notebook\n",
        "\n",
        "Conforme estipulado no [Enunciado Oficial do Projeto Final](./projeto.qmd), antes de iniciar a modelagem, a equipe deve validar se o conjunto de dados escolhido cumpre os **Critérios de Não-Trivialidade e Gate de Sanidade**:\n",
        "\n",
        "1. **Volume Amostral e Dimensionalidade:** $N \\ge 1.000$ instâncias e $d \\ge 8$ atributos preditivos informativos.\n",
        "2. **Proibição de Bases de Brinquedo:** Não são aceitas bases clássicas/artificiais (*Iris*, *Titanic* básico, *Boston Housing*, *Wine Quality*, *Breast Cancer Wisconsin*).\n",
        "3. **Margem para Aprendizado (Não-Trivialidade):** O estimador linear básico inicial (*baseline* regularizado) **não pode atingir acurácia, F1-Score ou ROC-AUC superior a $0{,}92$**. Se um modelo linear ingênuo já atinge performance quase perfeita, o problema é estatisticamente trivial e não permite investigar os mecanismos de viés-variância e ensembles.\n",
        "\n",
        "> **Importante:** Este notebook fornece a função de validação automatizada `check_dataset_gate(...)`. Abaixo, demonstramos a aplicação dessa função em uma base real que **NÃO PASSA NO TESTE** (para exemplificar o diagnóstico de reprovação explícito), e deixamos a estrutura pronta para você testar a base da sua equipe."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import pandas as pd\n",
        "from sklearn.datasets import load_breast_cancer\n",
        "from sklearn.linear_model import LogisticRegression\n",
        "from sklearn.model_selection import StratifiedKFold, cross_validate\n",
        "from sklearn.preprocessing import StandardScaler\n",
        "from sklearn.pipeline import Pipeline\n",
        "import warnings\n",
        "warnings.filterwarnings('ignore')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {},
      "outputs": [],
      "source": [
        "TOY_DATASETS_BLACKLIST = [\n",
        "    \"iris\", \"titanic\", \"boston\", \"boston_housing\", \n",
        "    \"wine\", \"wine_quality\", \"breast_cancer\", \"cancer\", \"diabetes_simple\"\n",
        "]\n",
        "\n",
        "def check_dataset_gate(\n",
        "    X: pd.DataFrame | np.ndarray,\n",
        "    y: pd.Series | np.ndarray,\n",
        "    dataset_name: str = \"Meu_Dataset\",\n",
        "    task_type: str = \"classification\",\n",
        "    max_metric_threshold: float = 0.92,\n",
        "    min_samples: int = 1000,\n",
        "    min_features: int = 8,\n",
        ") -> dict:\n",
        "    \"\"\"\n",
        "    Verifica se o conjunto de dados atende aos requisitos do Gate de Sanidade do Projeto Final.\n",
        "    \"\"\"\n",
        "    N = len(X)\n",
        "    d = X.shape[1] if hasattr(X, \"shape\") else len(X[0])\n",
        "    \n",
        "    failures = []\n",
        "    \n",
        "    # 1. Checagem de Volume e Dimensionalidade\n",
        "    if N < min_samples:\n",
        "        failures.append(f\"Volume amostral insuficiente: N = {N:,} (mínimo exigido: {min_samples:,}).\")\n",
        "    if d < min_features:\n",
        "        failures.append(f\"Dimensionalidade insuficiente: d = {d} atributos (mínimo exigido: {min_features}).\")\n",
        "        \n",
        "    # 2. Checagem de Lista Negra (Toy Datasets)\n",
        "    normalized_name = dataset_name.lower().replace(\" \", \"_\").replace(\"-\", \"_\")\n",
        "    for blacklisted in TOY_DATASETS_BLACKLIST:\n",
        "        if blacklisted in normalized_name:\n",
        "            failures.append(f\"Base de brinquedo/clássica proibida identificada: '{dataset_name}'.\")\n",
        "            break\n",
        "            \n",
        "    # 3. Checagem de Não-Trivialidade (Baseline Linear)\n",
        "    if task_type == \"classification\":\n",
        "        pipe = Pipeline([\n",
        "            (\"scaler\", StandardScaler()),\n",
        "            (\"model\", LogisticRegression(penalty=\"l2\", C=1.0, max_iter=1000, random_state=42))\n",
        "        ])\n",
        "        cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n",
        "        cv_res = cross_validate(pipe, X, y, cv=cv, scoring=[\"accuracy\", \"f1_weighted\", \"roc_auc\"])\n",
        "        \n",
        "        acc = float(np.mean(cv_res[\"test_accuracy\"]))\n",
        "        f1 = float(np.mean(cv_res[\"test_f1_weighted\"]))\n",
        "        auc = float(np.mean(cv_res[\"test_roc_auc\"])) if not np.isnan(cv_res[\"test_roc_auc\"]).any() else acc\n",
        "        \n",
        "        max_metric = max(acc, f1, auc)\n",
        "        metric_name = \"ROC-AUC\" if max_metric == auc else (\"F1\" if max_metric == f1 else \"Acurácia\")\n",
        "        \n",
        "        if max_metric > max_metric_threshold:\n",
        "            failures.append(\n",
        "                f\"Problema estatisticamente trivial: Baseline linear atingiu {metric_name} = {max_metric:.3f} \"\n",
        "                f\"(limite máximo permitido: {max_metric_threshold:.2f}).\"\n",
        "            )\n",
        "    else:\n",
        "        from sklearn.linear_model import Ridge\n",
        "        pipe = Pipeline([(\"scaler\", StandardScaler()), (\"model\", Ridge(alpha=1.0))])\n",
        "        cv_res = cross_validate(pipe, X, y, cv=5, scoring=[\"r2\"])\n",
        "        r2 = float(np.mean(cv_res[\"test_r2\"]))\n",
        "        max_metric = r2\n",
        "        metric_name = \"R²\"\n",
        "        if r2 > max_metric_threshold:\n",
        "            failures.append(\n",
        "                f\"Problema trivial: Baseline linear atingiu R² = {r2:.3f} \"\n",
        "                f\"(limite máximo permitido: {max_metric_threshold:.2f}).\"\n",
        "            )\n",
        "            \n",
        "    # Exibição do Laudo Formatado\n",
        "    print(\"=\" * 72)\n",
        "    print(f\"RELATÓRIO DE AUDITORIA — GATE DE SANIDADE: {dataset_name}\")\n",
        "    print(\"=\" * 72)\n",
        "    print(f\"• Dimensões observadas : N = {N:,} amostras, d = {d} atributos\")\n",
        "    if task_type == \"classification\":\n",
        "        print(f\"• Desempenho Baseline  : Acurácia = {acc:.3f} | F1 = {f1:.3f} | ROC-AUC = {auc:.3f}\")\n",
        "    else:\n",
        "        print(f\"• Desempenho Baseline  : R² = {r2:.3f}\")\n",
        "    print(\"-\" * 72)\n",
        "    \n",
        "    passed = len(failures) == 0\n",
        "    if passed:\n",
        "        print(\"✅ APROVADO NO GATE DE SANIDADE!\")\n",
        "        print(\"O conjunto de dados é perfeitamente elegível para o Projeto Final.\")\n",
        "        print(\"Ele apresenta dimensões adequadas e desafio estatístico suficiente.\")\n",
        "    else:\n",
        "        print(\"❌ REPROVADO NO GATE DE SANIDADE! (ESTA BASE NÃO PASSA NO TESTE)\")\n",
        "        print(\"\\nMotivos formais da reprovação:\")\n",
        "        for idx, fail in enumerate(failures, 1):\n",
        "            print(f\"  {idx}. [FALHA] {fail}\")\n",
        "        print(\"\\n⚠️ AÇÃO NECESSÁRIA:\")\n",
        "        print(\"Esta base NÃO pode ser utilizada no Projeto Final da disciplina.\")\n",
        "        print(\"A equipe deve selecionar um conjunto de dados do mundo real com N >= 1.000\")\n",
        "        print(\"e complexidade suficiente para que a baseline linear não atinja teto quase perfeito.\")\n",
        "    print(\"=\" * 72)\n",
        "    \n",
        "    return {\n",
        "        \"passed\": passed,\n",
        "        \"N\": N,\n",
        "        \"d\": d,\n",
        "        \"failures\": failures,\n",
        "        \"max_metric\": max_metric\n",
        "    }"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "---\n",
        "## 1. Demonstração: Testando uma base que NÃO PASSA no teste\n",
        "\n",
        "Abaixo, submetemos a base clássica **Breast Cancer Wisconsin** (disponível nativamente no scikit-learn).  \n",
        "Ela ilustra com clareza a reprovação por violar múltiplos requisitos do projeto ao mesmo tempo:\n",
        "- Volume amostral menor que o mínimo ($N = 569 < 1.000$);\n",
        "- Base clássica de brinquedo listada na restrição;\n",
        "- Problema linearmente separável / quase trivial (a regressão logística obtém ROC-AUC de $0{,}994$, estourando o teto de $0{,}92$)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "========================================================================\n",
            "RELATÓRIO DE AUDITORIA — GATE DE SANIDADE: Breast Cancer Wisconsin\n",
            "========================================================================\n",
            "• Dimensões observadas : N = 569 amostras, d = 30 atributos\n",
            "• Desempenho Baseline  : Acurácia = 0.975 | F1 = 0.975 | ROC-AUC = 0.994\n",
            "------------------------------------------------------------------------\n",
            "❌ REPROVADO NO GATE DE SANIDADE! (ESTA BASE NÃO PASSA NO TESTE)\n",
            "\n",
            "Motivos formais da reprovação:\n",
            "  1. [FALHA] Volume amostral insuficiente: N = 569 (mínimo exigido: 1,000).\n",
            "  2. [FALHA] Base de brinquedo/clássica proibida identificada: 'Breast Cancer Wisconsin'.\n",
            "  3. [FALHA] Problema estatisticamente trivial: Baseline linear atingiu ROC-AUC = 0.994 (limite máximo permitido: 0.92).\n",
            "\n",
            "⚠️ AÇÃO NECESSÁRIA:\n",
            "Esta base NÃO pode ser utilizada no Projeto Final da disciplina.\n",
            "A equipe deve selecionar um conjunto de dados do mundo real com N >= 1.000\n",
            "e complexidade suficiente para que a baseline linear não atinja teto quase perfeito.\n",
            "========================================================================\n"
          ]
        }
      ],
      "source": [
        "# Carregando a base de demonstração (que falha no teste)\n",
        "cancer_data = load_breast_cancer(as_frame=True)\n",
        "X_cancer = cancer_data.data\n",
        "y_cancer = cancer_data.target\n",
        "\n",
        "# Executando a auditoria oficial do Gate de Sanidade\n",
        "resultado_cancer = check_dataset_gate(\n",
        "    X=X_cancer,\n",
        "    y=y_cancer,\n",
        "    dataset_name=\"Breast Cancer Wisconsin\",\n",
        "    task_type=\"classification\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "---\n",
        "## 2. Como validar a base de dados da sua equipe\n",
        "\n",
        "Para validar a base proposta pela sua equipe (obtida no Kaggle, UCI, OpenML, Hugging Face ou órgãos de dados abertos), utilize o modelo abaixo.  \n",
        "Basta carregar os dados via Pandas e chamar a função `check_dataset_gate`.\n",
        "\n",
        "Inclua o relatório de auditoria gerado na **Seção 1 do seu Relatório Técnico**.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Roteiro para a sua equipe executar:\n",
        "# 1. Carregue seu arquivo de dados:\n",
        "# df = pd.read_csv(\"data/meu_dataset.csv\")\n",
        "\n",
        "# 2. Isole as matrizes X e y:\n",
        "# X_equipe = df.drop(columns=[\"coluna_alvo\"])\n",
        "# y_equipe = df[\"coluna_alvo\"]\n",
        "\n",
        "# 3. Execute a auditoria:\n",
        "# resultado = check_dataset_gate(\n",
        "#     X=X_equipe,\n",
        "#     y=y_equipe,\n",
        "#     dataset_name=\"Nome_Do_Seu_Dataset\",\n",
        "#     task_type=\"classification\"  # ou \"regression\"\n",
        "# )\n"
      ]
    }
  ],
  "metadata": {
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}