Skip to content

frebarcelos/DeepCheckPR

Repository files navigation

GitHub PR Analyzer

Ferramenta de análise de Pull Requests do GitHub usando paradigma funcional em Python 3.11+.

Disciplina AL0337 — Linguagens de Programação, UNIPAMPA. 5 desenvolvedores (dev1–dev5), TDD obrigatório, pre-commits rigorosos.


Visão Geral

O GitAnalyzer lê datasets de Pull Requests (CSV/JSON), aplica uma cadeia de transformações funcionais puras, classifica cada PR semanticamente via LLM (Groq ou Ollama) e exibe os resultados num dashboard Streamlit interativo.

CSV / JSON
    │
    ▼
io/csv_reader          ← leitura lazy via generators (dev1)
    │
    ▼
transforms/            ← filter / map / reduce puros, sem I/O (dev2)
  filters.py           ← by_state, by_language, with_min_size, combine_filters
  mappers.py           ← normalize_language, normalize_state
  reducers.py          ← count_by_language, count_by_project_type, EnrichedPR
  heuristics.py        ← pré-classificação por keywords (sem LLM)
    │
    ▼
pipeline/builder.py    ← compose(), build_pipeline(), pipeline_from_env() (dev4)
    │
    ▼
cache/memo.py          ← SHA-256 + LRU + persistência JSON ou SQLite (dev4)
cache/sqlite_store.py  ← SqliteKVStore WAL + thread-safe (dev4)
    │
    ▼
llm/classifiers.py     ← enrich_prs async/batch/tools via Groq|Ollama (dev3)
llm/client.py          ← HTTP keep-alive, retry com backoff exponencial
llm/metrics.py         ← ClassificationMetrics: throughput, fallback rate
llm/system_probe.py    ← auto-detecção CPU/RAM/GPU → PipelineConfig ideal
    │
    ▼
ui/ (Streamlit)        ← upload, filtros, gráficos, export (dev5)

Funcionalidades

Funcionalidade User Story Status
Leitura lazy de CSV com detecção de schema US01
Filtros por linguagem, estado, tamanho e corpo US02
Classificação de tipo de projeto via LLM US03
Classificação de natureza de contribuição via LLM US04
Avaliação de clareza de descrição via LLM US05
Gráfico de distribuição por tamanho de corpo (chars/words) US06
Gráfico de clareza cruzada com tipo/natureza/linguagem US07
Filtros de tipo de projeto e clareza na sidebar US08
Cache SHA-256 + LRU com persistência JSON RG04
Pipeline configurável por variáveis de ambiente RG06
Heurísticas de pré-classificação (sem LLM) LLM-04
Cliente async + batch + retry com backoff LLM-01/02/07
HTTP keep-alive por thread LLM-03
Cache SQLite persistente entre containers LLM-10
Métricas de throughput e fallback rate LLM-06
Auto-detecção de hardware (CPU/RAM/GPU) system_probe
Result-cache cross-sessão: PR já classificado não é reclassificado v6
Seletor de escala com estimativas de tempo (500/2k/10k/50k) v6
"Todas as bases": carrega e concatena todos os datasets em data/ v6
pipeline-all: processa dataset completo sem limite via CLI v6
Complexidade de revisão (review_complexity): low/medium/high por PR v7
Guardrails de entrada: sanitize + validate_pr rejeitam PRs corrompidos antes do LLM v7

Arquitetura de Módulos

Módulo Dev Tipo Responsabilidade
src/pr_analyzer/io/ dev1 (Bernardo) Efeito colateral Leitura lazy CSV via yield; exportadores
src/pr_analyzer/transforms/ dev2 (Pedro) Puro filter(), map(), reduce(); heuristics.py (keywords)
src/pr_analyzer/llm/ dev3 (Dean) Efeito colateral Groq/Ollama async+batch+tools; metrics.py; system_probe.py
src/pr_analyzer/cache/ dev4 (Frederico) Misto hashlib + LRU JSON; sqlite_store.py WAL persistente
src/pr_analyzer/pipeline/ dev4 (Frederico) Puro compose(), build_pipeline(), HOFs
src/pr_analyzer/ui/ dev5 (Diogo) Efeito colateral Streamlit: upload, filtros, gráficos, download

Módulos PUROS (transforms/, pipeline/): zero I/O, zero estado global mutável, zero loops imperativos — violações bloqueiam o commit.


Requisitos

  • Docker e Docker Compose
  • Groq API Key — para classificação LLM via nuvem
  • pipx — para instalar o pre-commit no host:
    sudo apt install pipx && pipx ensurepath
  • (Opcional) Ollama — para classificação LLM local, sem API key

Configuração Rápida

git clone <repo>
cd marco-2-rp3

cp .env.example .env       # preencha GROQ_API_KEY e ANTHROPIC_API_KEY

make docker-build          # constrói a imagem Docker (uma vez, ~2 min)
make hooks                 # instala pre-commit e os git hooks no host (uma vez)
make docker-run            # Streamlit em http://localhost:8501

Nunca use sudo make docker-* — o grupo docker já tem permissão e sudo causa conflitos de cgroup.


Variáveis de Ambiente

Variável Padrão Descrição
GROQ_API_KEY API key do Groq (obrigatória para backend groq)
ANTHROPIC_API_KEY API key da Anthropic (hook claude-review no push)
LLM_BACKEND groq Backend LLM: groq ou ollama
LLM_MODEL llama3 Modelo a usar (ex: llama-3.1-8b-instant para Groq, llama3 para Ollama)
OLLAMA_HOST http://localhost:11434 Host do servidor Ollama
CACHE_PATH .cache/llm_cache.json Caminho do cache persistente LLM
DATASET Caminho do CSV para o pipeline CLI
OUTPUT Caminho de saída JSON do pipeline CLI
LIMIT 100 Número máximo de registros no pipeline CLI
Otimizações de throughput
LLM_MAX_WORKERS auto Threads paralelas (auto detecta hardware, número fixo sobrescreve)
LLM_USE_TOOLS false Tool calling: 1 chamada/PR em vez de 3 (requer modelo compatível)
LLM_BATCH_SIZE 1 PRs por chamada batch (5–10 com qwen2:1.5b, 1 = individual)
LLM_MAX_RETRIES 3 Tentativas de retry em falha de conexão
LLM_RETRY_BASE_DELAY 1.0 Delay base do backoff exponencial (segundos)
LLM_BODY_CHARS_SINGLE 400 Caracteres do body em chamadas individuais
LLM_BODY_CHARS_BATCH 100 Caracteres do body em chamadas batch (prompt compacto)

Backend LLM — Groq vs Ollama

Groq (padrão — requer API key)

# .env
GROQ_API_KEY=gsk_...
LLM_BACKEND=groq
LLM_MODEL=llama-3.1-8b-instant

Ollama (local — sem API key)

# 1. Instale em https://ollama.com
# 2. Baixe um modelo:
ollama pull llama3

# 3. Faça o Ollama escutar em todas as interfaces (necessário para o Docker):
sudo tee /etc/systemd/system/ollama.service.d/override.conf << 'EOF'
[Service]
Environment="OLLAMA_HOST=0.0.0.0"
EOF
sudo systemctl daemon-reload && sudo systemctl restart ollama

# 4. Configure o .env:
LLM_BACKEND=ollama
OLLAMA_HOST=http://host.docker.internal:11434
LLM_MODEL=llama3

A sidebar detecta automaticamente o backend e exibe o seletor BACKEND LLM.


Rodando

make docker-run            # dashboard Streamlit em http://localhost:8501
make docker-test           # suite de testes dentro do Docker
make docker-build          # reconstrói a imagem (após alterar dependências)

# Pipeline via CLI (sem UI):
make pipeline DATASET=data/arquivo.csv OUTPUT=out.json LIMIT=50

Desenvolvimento local (sem Docker)

make setup                 # instala deps completas + hooks (Python 3.11+ necessário)
make run                   # Streamlit local
make test                  # testes unitários

Testes

make docker-test           # roda pytest dentro do Docker com cobertura ≥80%

Cobertura por módulo (v7.0.0 — 2026-05-30):

Módulo Cobertura
transforms/ ~100%
pipeline/ ~100%
cache/ ~100%
io/ ~100%
llm/classifiers.py ~94%
llm/metrics.py 100%
llm/system_probe.py ~39% (I/O — mockado intencionalmente)
Total 89%

Os testes em transforms/ e pipeline/ usam Hypothesis para property-based testing:

@given(st.text())
def test_filter_by_state_does_not_raise(state: str) -> None:
    predicate = filter_by_state(state)
    assert callable(predicate)

Otimizações de Performance (Sprint 5 + v6)

O pipeline com llama3 (4.7 GB) processava ~2000 PRs em ~5h sequencialmente. Após as otimizações com qwen2:1.5b + todas as flags ativas: ~12 min. Com result-cache SQLite (v6): execuções repetidas nos mesmos dados → instantâneo.

Otimização Módulo Ganho
Async HTTP (asyncio.gather) llm/classifiers.py 2–4× throughput
Adaptive batch size llm/classifiers.py Elimina fallbacks caros
HTTP keep-alive por thread llm/client.py 5–15% latência
Heurísticas de pré-classificação transforms/heuristics.py 10–30% chamadas eliminadas
Cache de tipo por repositório llm/classifiers.py ~33% menos tokens
Prompt compression (body truncado) llm/classifiers.py 10–20% menos tokens
Retry com backoff exponencial llm/client.py Resiliência a timeouts
SQLite cache persistente (WAL) cache/sqlite_store.py Cache sobrevive ao Docker rebuild
Auto-detecção de hardware llm/system_probe.py Workers/batch ajustados ao hardware
Métricas de observabilidade llm/metrics.py Throughput, fallback rate em tempo real
Result-cache cross-sessão llm/classifiers.py PRs já classificados: 0 chamadas LLM

Configuração recomendada para máxima velocidade (Ollama)

LLM_BACKEND=ollama
LLM_MODEL=qwen2:1.5b        # ollama pull qwen2:1.5b
LLM_MAX_WORKERS=auto         # detecta CPU/RAM/GPU automaticamente
LLM_USE_TOOLS=true
LLM_BATCH_SIZE=5

Paradigma Funcional — Regras

Estas regras aplicam-se a transforms/ e pipeline/. Violações bloqueiam o commit:

Regra Proibido Alternativa
FP001 Loop for map(), filter(), generator expression
FP002 Loop while Recursão ou itertools
FP003 x[i] = y (mutação por índice) dict | {k: v} ou nova tupla
FP004 .append(), .update(), .pop() list + [x], frozenset | {x}
FP005 open(), print() fora de io/ ou ui/ Isolar nos módulos de efeito colateral

Exemplos de código funcional

# pipeline/builder.py — compose sem loops
def compose(*fns: Callable) -> Callable:
    return reduce(lambda f, g: lambda x: g(f(x)), fns, lambda x: x)

# cache/memo.py — chave SHA-256 determinística
def make_cache_key(prompt: str, model: str) -> str:
    payload = json.dumps({"prompt": prompt, "model": model}, sort_keys=True)
    return hashlib.sha256(payload.encode()).hexdigest()

# transforms/reducers.py — reduce sem mutação
def count_by_language(prs: tuple[PRRecord, ...]) -> dict[str, int]:
    return reduce(
        lambda acc, pr: acc | {pr.language: acc.get(pr.language, 0) + 1},
        prs, {}
    )

Estrutura do Projeto

marco-2-rp3/
├── src/pr_analyzer/
│   ├── io/              # dev1 — leitura lazy CSV, exportadores
│   ├── transforms/      # dev2 — filtros, mappers, reducers (PURO)
│   │   ├── filters.py
│   │   ├── mappers.py
│   │   └── reducers.py  # define EnrichedPR (NamedTuple canônico)
│   ├── llm/             # dev3 — classificação Groq/Ollama
│   │   ├── client.py        # HTTP keep-alive, retry backoff, async
│   │   ├── classifiers.py   # enrich_prs async/batch/tools/heuristics
│   │   ├── metrics.py       # ClassificationMetrics: throughput, fallbacks
│   │   ├── skills.py        # system prompt + few-shot examples
│   │   └── system_probe.py  # auto-detecção CPU/RAM/GPU → PipelineConfig
│   ├── cache/           # dev4 — SHA-256 + LRU + JSON/SQLite
│   │   ├── memo.py          # cached_classify + make_enriched_classifier
│   │   └── sqlite_store.py  # SqliteKVStore WAL thread-safe
│   ├── pipeline/        # dev4 — compose, build_pipeline (PURO)
│   │   └── builder.py
│   └── ui/              # dev5 — Streamlit
│       ├── app.py
│       ├── components/
│       │   ├── charts.py
│       │   ├── kpis.py
│       │   ├── sidebar.py
│       │   └── tabs.py
│       └── utils/
│           ├── data.py
│           ├── exports.py
│           ├── pipeline_bridge.py
│           └── styles.py
├── tests/               # TDD — espelha src/
├── data/                # datasets locais (gitignored)
├── .env.example
├── Makefile
├── docker-compose.yml
└── pyproject.toml

Pre-commit Hooks

Hook Quando roda O que faz
conventional-pre-commit commit-msg Valida formato Conventional Commits
ruff commit Linting, imports, complexidade ciclomática (max=10)
ruff-format commit Formatação determinística
mypy commit Type checking estrito (--strict)
check-paradigm commit AST: loops, mutações, I/O em módulos puros
debug-statements commit Bloqueia print() e breakpoint() acidentais
check-added-large-files commit Impede commitar datasets >500 KB
pytest-unit push Suite completa de testes unitários
coverage-check push Cobertura ≥80% (branch coverage)
claude-review push Revisão semântica via Claude API

Formato de commit (Conventional Commits)

<tipo>(<escopo>): <descrição em minúsculas>

feat(cache): implementa make_cache_key com SHA-256
fix(io): corrige parsing de datas inválidas no CSV
test(transforms): adiciona casos de borda para by_date_range

Branches e Fluxo de Integração

Branch Responsável Módulo
bernardo dev1 io/
pedro dev2 transforms/
dev/dev3 dev3 (Dean) llm/
frederico-barcelos dev4 cache/, pipeline/
diogo dev5 ui/
feature branch → PR → develop → PR → main
  • PRs para main exigem aprovação de 1 colega
  • Nunca commitar diretamente em develop — sempre via branch de dev → PR
  • Push é sempre feito pelo desenvolvedor, nunca automatizado

Cronograma

Sprint Verificação Meta de Cobertura Status
1 — Estrutura base 04/05/2026 Módulo funciona
2 — Transformações 11/05/2026 >50%
3 — LLM + Pipeline 18/05/2026 >65%
4 — UI + Integração 25/05/2026 ≥80%
5 — Otimizações LLM 26/05/2026 ≥80% ✅ (87%)
6 — Persistência + UI 26/05/2026 ≥80% ✅ (88%)
7 — Integração Gift 30/05/2026 ≥80% ✅ (89%)
Entrega Final (Marco 02) 01/06/2026 ≥80%

About

Dashboard e pipeline funcional em Python para analisar Pull Requests com Groq/Ollama, cache JSON/SQLite e Streamlit.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages