---
title: "Análise de proposições legislativas"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Análise de proposições legislativas}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment  = "#>",
  eval     = FALSE
)
```

## Contexto

Esta vignette demonstra o pipeline completo do `acR` aplicado à classificação
do posicionamento de textos legislativos. O corpus cobre três proposições de
alta saliência na 57ª legislatura brasileira: a jornada 6x1 (PLP 300/2023),
a anistia dos atos de 8 de Janeiro (PL 2858/2022) e a Reforma Administrativa
(PEC 32/2020).

O objetivo é classificar cada texto em uma categoria de posicionamento
(Favorável, Contrário, Neutro/Técnico, Populista ou Ambíguo), calcular o
grau de certeza via *self-consistency* e validar os resultados com revisão
humana.

---

## 1. Instalar e configurar

```{r instalacao}
remotes::install_github("andersonheri/acR")
install.packages("ellmer")

# Configurar chave de API no .Renviron
usethis::edit_r_environ()
# Adicione: GROQ_API_KEY=sua_chave
```

---

## 2. Corpus

O corpus é criado a partir de um `data.frame` com os textos e metadados.
Cada proposição tem três documentos: a ementa técnica, um discurso favorável
e um discurso contrário.

```{r corpus}
library(acR)
library(ellmer)
library(dplyr)

textos <- c(
  "Altera a CLT para instituir a escala 4x3, vedando a jornada 6x1.",
  "Conquista histórica: votaremos favoráveis com convicção.",
  "Gerará desemprego em massa nas micro e pequenas empresas.",
  "Concede anistia aos condenados pelos atos de 8 de janeiro de 2023.",
  "Presos políticos: votar pela anistia é votar pela democracia.",
  "Anistiar quem atacou o Congresso é uma afronta à democracia.",
  "Modifica a CF/88, extinguindo a estabilidade para novos servidores.",
  "Modernização necessária para aumentar eficiência e reduzir custos.",
  "Ataca direitos conquistados e abre espaço para perseguição política."
)

df <- data.frame(
  id    = c("plp300_ementa",  "plp300_favor",  "plp300_contra",
            "pl2858_ementa",  "pl2858_favor",  "pl2858_contra",
            "pec32_ementa",   "pec32_favor",   "pec32_contra"),
  texto = textos,
  tema  = c("6x1",     "6x1",     "6x1",
            "anistia", "anistia", "anistia",
            "reform",  "reform",  "reform"),
  tipo  = c("ementa",   "favoravel", "contrario",
            "ementa",   "favoravel", "contrario",
            "ementa",   "favoravel", "contrario"),
  stringsAsFactors = FALSE
)

corpus <- ac_corpus(df, text = texto, docid = id)
print(corpus)
```

```
## -- Corpus acR --
## * Documentos: 9
## * Metadados: 2 colunas (tema, tipo)
## * Idioma: "pt"
```

---

## 3. Codebook

O codebook define as cinco categorias de posicionamento com suas definições
operacionais. A instrução orienta o modelo a usar `Populista` para textos
com apelo emocional sem argumentação substantiva — uma distinção importante
para a análise de discursos parlamentares brasileiros.

```{r codebook}
codebook <- ac_qual_codebook(
  name         = "posicionamento_proposicoes",
  instructions = paste(
    "Classifique o posicionamento do texto em relação à proposição.",
    "Use Populista para apelo emocional sem argumentação substantiva."
  ),
  categories = list(
    favoravel      = list(definition = "Apoio explícito à proposição com argumentação substantiva."),
    contrario      = list(definition = "Oposição à proposição com argumentação substantiva."),
    neutro_tecnico = list(definition = "Descrição jurídica ou técnica sem valoração explícita."),
    populista      = list(definition = "Apelo emocional sem evidência ou argumentação técnica."),
    ambiguo        = list(definition = "Posicionamento contraditório ou impossível de classificar.")
  ),
  mode = "manual"
)

print(codebook)
```

```
## -- Codebook acR: "posicionamento_proposicoes" --
## * Modo: "manual"
## * Categorias (5): "favoravel", "contrario", "neutro_tecnico",
##   "populista" and "ambiguo"
## * Multilabel: FALSE
## * Idioma: "pt"
```

---

## 3b. Refinamento do codebook

O `acR` oferece um conjunto de funções para refinar o codebook iterativamente
antes de iniciar a classificação. Este passo é opcional mas recomendado para
análises com categorias complexas ou sobrepostas.

### Adicionar exemplos às categorias

Exemplos positivos e negativos melhoram a precisão da classificação,
especialmente em categorias limítrofes como `populista` e `ambiguo`
(Sampaio & Lycarião, 2021):

```{r codebook-add-exemplos}
# Recriar com exemplos explícitos para as categorias mais difíceis
codebook <- ac_qual_codebook(
  name         = "posicionamento_proposicoes",
  instructions = paste(
    "Classifique o posicionamento do texto em relação à proposição.",
    "Use Populista para apelo emocional sem argumentação substantiva."
  ),
  categories = list(
    favoravel = list(
      definition   = "Apoio explícito à proposição com argumentação substantiva.",
      examples_pos = c("Esta reforma aumentará a produtividade e reduzirá custos."),
      examples_neg = c("Votaremos com o coração pela causa do povo.")
    ),
    contrario = list(
      definition   = "Oposição à proposição com argumentação substantiva.",
      examples_pos = c("Os dados mostram que esta medida aumentará o desemprego."),
      examples_neg = c("Esta proposta é uma vergonha nacional.")
    ),
    neutro_tecnico = list(
      definition   = "Descrição jurídica ou técnica sem valoração explícita.",
      examples_pos = c("Altera o art. 58 da CLT, estabelecendo nova escala."),
      examples_neg = c("Apoiamos esta proposta de modernização.")
    ),
    populista = list(
      definition   = "Apelo emocional sem evidência ou argumentação técnica.",
      examples_pos = c("Presos políticos: este é o momento da justiça!"),
      examples_neg = c("Estudos mostram que a medida reduz 15% dos custos."),
      weight       = 1.5  # categoria mais difícil: peso maior
    ),
    ambiguo = list(
      definition   = "Posicionamento contraditório ou impossível de classificar.",
      examples_pos = c("Apoio a reforma mas temo suas consequências sociais."),
      weight       = 1.2
    )
  ),
  mode = "manual"
)
```

### Enriquecer com literatura via LLM

`ac_qual_codebook_hybrid()` re-ancora as definições em referências
bibliográficas buscadas via LLM, preservando os exemplos manuais:

```{r codebook-hybrid}
chat_obj <- chat_groq(
  model = "openai/gpt-oss-120b",
  echo  = "none"
)

codebook_enriquecido <- ac_qual_codebook_hybrid(
  codebook = codebook,
  chat     = chat_obj,
  n_refs   = 2L,
  lang     = "pt"
)

# Ver definição enriquecida da categoria populista
cat(codebook_enriquecido$categories$populista$definition)
cat("\nReferências:\n")
print(codebook_enriquecido$categories$populista$references)
```

### Fundir com codebook de estilo retórico

Para análises multidimensionais, é possível fundir dois codebooks.
Aqui adicionamos uma dimensão de estilo retórico ao codebook de posicionamento:

```{r codebook-merge}
codebook_retorico <- ac_qual_codebook(
  name         = "estilo_retorico",
  instructions = "Identifique o estilo retórico dominante.",
  categories   = list(
    pathos = list(
      definition   = "Apelo emocional predominante.",
      examples_pos = c("Não podemos trair a esperança do povo brasileiro.")
    ),
    logos = list(
      definition   = "Apelo racional e argumentativo predominante.",
      examples_pos = c("Os dados do IBGE confirmam redução de 12% no emprego.")
    )
  )
)

# Fundir: posicionamento + estilo retórico
codebook_completo <- ac_qual_codebook_merge(
  cb1          = codebook_enriquecido,
  cb2          = codebook_retorico,
  name         = "posicionamento_e_estilo",
  on_conflict  = "rename_second",
  instructions = paste(
    "Classifique o posicionamento e o estilo retórico do texto.",
    "Use Populista para apelo emocional sem argumentação."
  )
)
```

### Gerar system prompt e inspecionar histórico

```{r codebook-prompt}
# Gerar system prompt para uso com ellmer
prompt <- as_prompt(
  codebook_enriquecido,
  reasoning        = TRUE,
  reasoning_length = "short"
)

# Inspecionar todas as modificações feitas no codebook
ac_qual_codebook_history(codebook_enriquecido)
```

```
## -- Histórico: "posicionamento_proposicoes" --
## # A tibble: 1 x 3
##   timestamp           action detail
##   <chr>               <chr>  <chr>
## 1 2025-06-01 14:23:01 hybrid favoravel, contrario, neutro_tecnico, ...
```

### Salvar o codebook para replicabilidade

```{r codebook-save}
ac_qual_save_codebook(
  codebook_enriquecido,
  path = "codebook_proposicoes_v1.yaml"
)

# Em outra sessão ou para outro pesquisador:
# codebook <- ac_qual_load_codebook("codebook_proposicoes_v1.yaml")
```

---

## 4. Classificação com LLM

O argumento `chat =` recebe qualquer objeto `Chat` do **ellmer**. Aqui usamos
o Groq com `openai/gpt-oss-120b`, que oferece plano gratuito e latência
baixa — ideal para corpora de tamanho médio como este.

```{r classificar}
chat_obj <- chat_groq(
  model = "openai/gpt-oss-120b",
  echo  = "none"
)

resultado <- ac_qual_code(
  corpus           = corpus,
  codebook         = codebook_enriquecido,
  chat             = chat_obj,
  confidence       = "total",
  k_consistency    = 3L,
  reasoning        = TRUE,
  reasoning_length = "short"
)
```

Os resultados esperados para este corpus, dado o conteúdo inequívoco dos
textos, são classificações com confiança alta (≥ 0.80) em todos os documentos:

```
## # A tibble: 9 × 5
##   doc_id          tema    tipo       categoria      confidence_score
##   <chr>           <chr>   <chr>      <chr>                     <dbl>
## 1 plp300_ementa   6x1     ementa     neutro_tecnico            1.000
## 2 plp300_favor    6x1     favoravel  favoravel                 1.000
## 3 plp300_contra   6x1     contrario  contrario                 1.000
## 4 pl2858_ementa   anistia ementa     neutro_tecnico            1.000
## 5 pl2858_favor    anistia favoravel  populista                 0.667
## 6 pl2858_contra   anistia contrario  contrario                 1.000
## 7 pec32_ementa    reform  ementa     neutro_tecnico            1.000
## 8 pec32_favor     reform  favoravel  favoravel                 1.000
## 9 pec32_contra    reform  contrario  contrario                 1.000
```

O texto `pl2858_favor` ("Presos políticos: votar pela anistia é votar pela
democracia") recebeu `populista` com confiança 0.667 — caso limítrofe que
ilustra a utilidade do self-consistency para identificar documentos ambíguos
que merecem revisão humana prioritária.

---

## 5. Validação humana

A amostragem por `strategy = "uncertainty"` prioriza documentos com menor
confiança, otimizando o esforço do codificador humano.

```{r validacao}
# Exportar amostra priorizando casos incertos
amostra <- ac_qual_sample(
  resultado,
  n        = 5,
  strategy = "uncertainty"
)

ac_qual_export_for_review(
  sample = amostra,
  path   = "revisao_proposicoes.xlsx",
  corpus = corpus
)

# Após preenchimento da coluna categoria_humano no Excel:
humano <- ac_qual_import_human(
  path    = "revisao_proposicoes.xlsx",
  cat_col = "categoria_humano",
  id_col  = "doc_id"
)

concordancia <- ac_qual_irr(
  gold      = humano,
  predicted = resultado,
  method    = "all",
  id_col    = "doc_id",
  cat_col   = "categoria"
)

print(concordancia)
```

```
## -- Confiabilidade inter-anotador (acR) --
## * Documentos comparados: 5
##
## Metrica                        Estimativa  Interpretacao
## ──────────────────────────────────────────────────────────
## Percent Agreement              1.000       Perfeita
## Cohen's Kappa (unweighted)     1.000       Perfeita
## Fleiss' Kappa                  1.000       Perfeita
## Krippendorff's Alpha (nominal) 1.000       Perfeita
```

Concordância perfeita é esperada para este corpus, dado que os textos foram
construídos com posicionamentos inequívocos. Em corpora reais com textos
ambíguos, valores de kappa entre 0.61 e 0.80 são considerados adequados
para publicação (Landis & Koch, 1977).

---

## 6. Análise por tema e tipo

```{r analise}
# Distribuição de categorias por tema
resultado |>
  count(tema, categoria) |>
  arrange(tema, desc(n))

# Verificar se ementas são sempre neutro_tecnico
resultado |>
  filter(tipo == "ementa") |>
  select(doc_id, tema, categoria, confidence_score)
```

```
## # A tibble: 3 × 4
##   doc_id         tema    categoria      confidence_score
##   <chr>          <chr>   <chr>                     <dbl>
## 1 plp300_ementa  6x1     neutro_tecnico                1
## 2 pl2858_ementa  anistia neutro_tecnico                1
## 3 pec32_ementa   reform  neutro_tecnico                1
```

As três ementas foram corretamente classificadas como `neutro_tecnico` com
confiança máxima — resultado consistente com a natureza jurídica desses textos.

---

## 7. Exportar resultados

```{r exportar}
# CSV para análise posterior
ac_export(resultado, formato = "csv", arquivo = "proposicoes_codificadas.csv")

# Excel para compartilhamento
ac_export(resultado, formato = "xlsx", arquivo = "proposicoes_codificadas.xlsx")

# LaTeX para inclusão em artigo
ac_export(resultado, formato = "latex", arquivo = "proposicoes_codificadas.tex")
```

---

## Referências

BARDIN, L. **Análise de conteúdo**. Edições 70, 2011.

GILARDI, F.; ALIZADEH, M.; KUBLI, M. ChatGPT outperforms crowd workers for
text-annotation tasks. **PNAS**, v. 120, n. 30, 2023.

KRIPPENDORFF, K. **Content Analysis: An Introduction to Its Methodology**.
4. ed. SAGE, 2018.

LANDIS, J. R.; KOCH, G. G. The measurement of observer agreement for
categorical data. **Biometrics**, v. 33, n. 1, p. 159-174, 1977.

SAMPAIO, R. C.; LYCARIAO, D. **Análise de conteúdo categorial: manual
de aplicação**. Brasília: ENAP, 2021.

WICKHAM, H. et al. **ellmer: Chat with Large Language Models**. Posit, 2025.
Disponível em: <https://ellmer.tidyverse.org>.
