Skip to content

DRoqueProgrammer/ufmt-stats

Repository files navigation

UFMT Stats — Análise de Desempenho Acadêmico

Site do projeto de pesquisa (trabalho de graduandos em Estatística — UFMT) "Análise de Desempenho Acadêmico em Cálculo I e VGA — UFMT", construído com Next.js 16 (App Router), Supabase (Postgres + Auth) e pronto pra deploy na Vercel.

✨ Features

  • 📊 Visualizações interativas (Chart.js) com dados reais carregados do banco
  • 🏫 Painel admin protegido por senha (Supabase Auth em produção, senha demo local)
  • 📝 CRUD de notas (adicionar, editar, excluir, filtrar aprovados/reprovados)
  • 📥 Importador de CSV/TSV com pré-visualização e batch upsert com dedup
  • 🌓 Modo demo — funciona sem Supabase, lendo data/seed.json
  • 🔒 Row Level Security (RLS) — leitura pública, escrita só pra autenticados
  • 📱 Responsivo (mobile-first)
  • ⚡ SSG/SSR híbrido, deploy direto na Vercel

🚀 Quick start (modo demo, sem Supabase)

npm install
npm run dev
# http://localhost:3001

Acesse /admin/login e use a senha ufmt2024 (ou o valor de ADMIN_DEMO_PASSWORD).

Modo demo é o estado padrão. Sem NEXT_PUBLIC_SUPABASE_URL + NEXT_PUBLIC_SUPABASE_ANON_KEY setadas, o site lê de data/seed.json (committed, ~3KB) e roda sem Supabase. O painel admin aceita mutações em memória (UI otimista) mas não persiste nada no banco — é pra navegar e demonstrar, não pra uso sério.

Para dados reprodutíveis fim-a-fim: git clone + npm i + npm run dev reproduz toda a análise sem credenciais. Isso é feature, não bug — é o que o PRODUCT.md pede ("reprodutibilidade científica").

🗄️ Setup com Supabase (produção)

1. Crie um projeto no supabase.com

2. Rode o schema SQL

No SQL Editor do dashboard, execute o conteúdo de supabase/schema.sql. Isso cria as tabelas, índices, view e policies de RLS.

3. Popule o banco

Você tem duas opções:

(a) Via interface web (recomendado)

  • Acesse /admin → faça login → use a aba "Importar CSV/TSV" para subir o arquivo data/seed.csv (gerado a partir da planilha original).

(b) Via SQL direto

-- Insere as notas da planilha (execute após rodar schema.sql)
insert into public.notas (disciplina_id, aluno_id, nota_final) values
  ('x-calculo1', 'aluno_1', 0.74),
  ('x-calculo1', 'aluno_2', 0.00),
  -- ... etc
  ;

4. Crie um usuário admin

No Supabase Auth → Users → "Add user" → email/senha. Esse será o login do painel.

5. Configure as variáveis de ambiente

Copie .env.example para .env.local e preencha (veja ./.env.example):

NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...   # ou NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...
ADMIN_DEMO_PASSWORD=              # vazio em produção (não usado se Supabase ativo)

Em produção (Vercel), defina NEXT_PUBLIC_SUPABASE_URL e NEXT_PUBLIC_SUPABASE_ANON_KEY em Environment Variables. Deixe ADMIN_DEMO_PASSWORD vazio — o caminho demo não executa quando Supabase está ativo.

6. Build e rode

npm run build
npm start

🌐 Deploy na Vercel

npm i -g vercel
vercel link
vercel env add NEXT_PUBLIC_SUPABASE_URL
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY
vercel --prod

Ou via dashboard: vercel.com/new → importe o repo → configure as 2 env vars acima → Deploy.

🗄️ Populando o Supabase com a planilha-fonte

Quando você ativar o Supabase (setando NEXT_PUBLIC_SUPABASE_URL e NEXT_PUBLIC_SUPABASE_ANON_KEY na Vercel), a página pública e o admin passam a ler de public.notas no banco — não mais de data/seed.json (o seed.json vira só fallback se o Supabase falhar).

Para popular o banco com as 215 notas da planilha original:

# 1. Rodar o schema uma vez no SQL Editor do Supabase
#    (cria turmas, disciplinas, notas, view v_stats_disciplina, RLS)
psql "$SUPABASE_DB_URL" -f supabase/schema.sql

# 2. Seed via SECRET key (bypassa RLS — só local, nunca no browser)
XLSX_PATH=/path/Notas_Calc_VGA.xlsx \
SUPABASE_URL=https://xxx.supabase.co \
SUPABASE_SECRET_KEY=sb_secret_... \
npm run db:seed

O script é idempotente (upsert onConflict): re-rodá-lo substitui as notas existentes pelas da planilha sem duplicar. Útil para resetar o DB a qualquer momento.

Após o seed, v_stats_disciplina no Supabase retorna as mesmas médias, medianas e taxas de aprovação do resumo_expandido_formatado.docx.

🔌 API pública (v1)

API REST pública, somente leitura, versionada por path (/api/v1/), com envelope consistente { data, meta } / { error: { code, message } }. Usa exclusivamente o client anon key (RLS de leitura pública) e herda o fallback Supabase → seed.json: se o banco cair, a API continua respondendo. Respostas são cacheadas na edge da Vercel (s-maxage=3600, stale-while-revalidate=86400 — dados novos aparecem em até 1h após import). CORS liberado para qualquer origem (Access-Control-Allow-Origin: *).

Especificação completa: public/openapi.yaml (OpenAPI 3.1).

Endpoint Descrição
GET /api/v1/health Disponibilidade da API e do banco
GET /api/v1/disciplinas Lista disciplinas (slug, nome, nº de registros)
GET /api/v1/disciplinas/{slug}/estatisticas Estatísticas descritivas + histograma. Filtro: ?turma=x
GET /api/v1/disciplinas/{slug}/registros Notas individuais anonimizadas. ?turma=, ?page= (≥1), ?limit= (1–200)
GET /api/v1/estatisticas/comparativo Comparação lado a lado. ?disciplinas=calculo-1,vga (2–5 slugs), ?turma=
GET /api/v1/disciplinas/{slug}/resumo-ia Resumo interpretativo gerado por IA (lido do banco). ?turma=. 404 NOT_GENERATED se nunca gerado

Nota de modelagem: o dado não tem semestre letivo — tem turmas (x, y). Os slugs de disciplina (calculo-1, vga) agregam todas as turmas; use ?turma= para filtrar.

curl https://<dominio>/api/v1/disciplinas
curl "https://<dominio>/api/v1/disciplinas/calculo-1/estatisticas?turma=x"
curl "https://<dominio>/api/v1/disciplinas/vga/registros?page=1&limit=50"
curl "https://<dominio>/api/v1/estatisticas/comparativo?disciplinas=calculo-1,vga"

Exemplo de resposta (/disciplinas/calculo-1/estatisticas):

{
  "data": {
    "disciplina": { "slug": "calculo-1", "nome": "Cálculo I" },
    "turma": null,
    "n": 103,
    "media": 2.02,
    "mediana": 0.4,
    "desvio_padrao": 2.87,
    "minimo": 0,
    "maximo": 9.63,
    "quartis": { "q1": 0, "q2": 0.4, "q3": 3.52 },
    "taxa_aprovacao": 0.21,
    "distribuicao": [ { "faixa": "0-1", "count": 66 }, { "faixa": "1-2", "count": 3 } ]
  },
  "meta": { "generated_at": "2026-07-09T12:00:00.000Z", "version": "v1" }
}

Erros: 400 BAD_REQUEST (parâmetro inválido), 404 NOT_FOUND/NOT_GENERATED (recurso inexistente), 500 INTERNAL_ERROR. Tipos TypeScript das respostas em lib/api/types.ts.

Escrita continua restrita ao painel admin (não passa pela API pública).

🤖 Como funciona a integração com IA

O endpoint /resumo-ia serve resumos interpretativos gerados por LLM, mas nenhuma rota pública chama o LLM. A arquitetura é gerar pouco, servir muito: um admin autenticado dispara a geração no painel (/admin/resumos), uma Server Action busca as estatísticas agregadas (nunca notas individuais), chama o provedor uma única vez e grava o resultado na tabela resumos_ia (RLS: leitura pública, escrita só admin). O público lê do banco — custo zero por visitante, latência baixa e a API key fora de alcance de tráfego externo.

A geração é idempotente via data_hash (SHA-256 das estatísticas): se os dados não mudaram, a action reaproveita o resumo salvo sem chamar o LLM. O badge no admin usa o mesmo hash para indicar resumo atualizado/desatualizado, e o campo atualizado do endpoint expõe isso ao consumidor.

O provedor é desacoplado em lib/ia/provider.ts (formato OpenAI /chat/completions via fetch): trocar Groq por outro provedor é mudar LLM_BASE_URL/LLM_MODEL/LLM_API_KEY no ambiente, sem tocar em código. Erros (incluindo 429 do free tier) viram mensagem amigável no admin e nunca gravam conteúdo parcial.

curl "https://ufmt-stats.vercel.app/api/v1/disciplinas/calculo-1/resumo-ia"

🧪 Verificação de integridade dos dados

npm run hash-data    # gera data/seed.json.sha256

O hash do data/seed.json é commitado em data/seed.json.sha256 para permitir que revisores verifiquem que os bytes exatos produzem os números exatos da análise. Para um paper com claims publicáveis, isso é parte da reprodutibilidade científica.

📁 Estrutura

app/
  page.tsx              # Página pública (home com hero, gráficos, etc.)
  layout.tsx            # Layout raiz com banner de modo demo
  api/v1/               # API REST pública (health, disciplinas, estatísticas)
  admin/
    (auth)/
      login/            # Tela de login (Supabase Auth ou senha demo)
    (protected)/
      page.tsx          # Dashboard com KPIs
      notas/            # CRUD de notas
      turmas/           # Cadastro de turmas e disciplinas
      importar/         # Importador de CSV/TSV com batch upsert
  globals.css           # Tokens OKLCH, brand, print, motion
components/
  Navbar.tsx            # Nav pública sticky
  PrintHeader.tsx       # Cabeçalho print-only
  charts/
    Charts.tsx          # Boxplot, Histograma, Barras (Chart.js)
    StatusBreakdown.tsx
  Seo/
    AcademicArticleJsonLd.tsx
data/
  seed.json             # Dados da planilha (fallback demo)
  seed.json.sha256      # Hash SHA-256 do seed (reprodutibilidade)
lib/
  api/                  # Camada da API pública (envelope, validação Zod, queries)
  data.ts               # Camada de dados (lê seed.json ou Supabase)
  stats.ts              # Cálculos estatísticos (describe, approval, bin)
  supabase.ts           # Flag e env Supabase (server + client)
  supabase-server.ts    # Cliente Supabase server
  supabase-browser.ts   # Cliente Supabase browser
  admin-auth.ts         # Guard de rotas /admin/*
  types.ts              # Tipos compartilhados
scripts/
  print-css-tweak.py    # Script de ajuste de print (debug histórico)
docs/
  CHANGELOG-impeccable.md  # Histórico de polish de design
supabase/
  schema.sql            # Schema + RLS + view de stats
  seed-notas.sql        # Seed SQL opcional
proxy.ts                # Matcher Next 16 para /admin/*

🧪 Stack

  • Next.js 16.2.9 — App Router, Server Components, Server Actions
  • React 19 + TypeScript strict
  • Tailwind CSS 3.4 — utility-first, com tokens OKLCH customizados
  • Supabase — Postgres + Auth + RLS (@supabase/ssr)
  • Chart.js 4 — visualizações client-side com tokens OKLCH via color-mix()

📜 Licença

MIT — pesquisa acadêmica, dados anônimos.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors