Projeto analise-artigo: análise ABNT de artigos LaTeX

- texparse.py: extração de estrutura abntex2 (título, autores, resumo,
  palavras-chave, seções, citações, figuras), sem dependências externas
- bib.py: parser .bib (BibTeX) com normalização de autores e diacríticos
- analyze.py: checklist NBR 10520/6022/14724, métricas textuais, Flesch
  adaptado pt-BR e heurísticas de prosa
- report.py: relatório Markdown + JSON
- artigos/rumo-a-eficiencia: artigo analisado (main.tex + referencias.bib)
This commit is contained in:
2026-09-25 13:24:56 -03:00
commit 550440f4a7
14 changed files with 1591 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
__pycache__/
*.pyc
.pytest_cache/
relatorios/
.venv/

109
README.md Normal file
View File

@@ -0,0 +1,109 @@
# analise-artigo
Projeto de análise de artigos acadêmicos escritos em **LaTeX (ABNT/abntex2)**.
É 100% Python puro (biblioteca padrão), sem dependências externas — roda em
qualquer máquina com Python ≥ 3.10.
## O que ele analisa
- **Estrutura e conformidade ABNT** (NBR 10520 / 6022 / 14724)
- título, autores, local/data, resumo (150–500 palavras), palavras-chave
- presença de seções: Introdução, Metodologia, Revisão de Literatura, Conclusão
- **Citações e referências**
- citações no corpo vs. entradas do `.bib` (detecta órfãs e não citadas)
- referências mais citadas e distribuição por ano
- **Métricas textuais**
- palavras, frases, média de tamanho de frase, vocabulário único
- legibilidade (Flesch adaptado ao português)
- distribuição de palavras por seção
- **Qualidade de prosa (heurísticas)**
- palavras repetidas / redundâncias (`de de`, `que que`, `mas mas` …)
- frases > 60 palavras e parágrafos > 160 palavras
- expressões mais repetidas no corpo do artigo
## Como usar
```bash
# a partir da raiz do projeto
python -m analise_artigo artigos/rumo-a-eficiencia
# ou apontando direto para o .tex (o .bib é descoberto automaticamente)
python -m analise_artigo artigos/rumo-a-eficiencia/main.tex
# saída em outro diretório
python -m analise_artigo caminho/do/artigo --out relatorios/
```
Saídas geradas no diretório de saída (padrão `relatorios/`):
| Arquivo | Conteúdo |
|-----------------|---------------------------------------------------|
| `relatorio.md` | relatório legível em Markdown |
| `relatorio.json`| dados estruturados (para plotar/automatizar) |
Código de saída do processo: `0` quando não há verificações em `fail`,
`2` quando há, `1` em erro de uso/entrada.
## Estrutura do projeto
```
analise-artigo/
├── analise_artigo/
│ ├── __init__.py # API pública do pacote
│ ├── texparse.py # LaTeX → texto (seções, resumo, citações)
│ ├── bib.py # parser .bib (BibTeX), sem dependências
│ ├── analyze.py # métricas + checklist ABNT + heurísticas
│ ├── report.py # geração do .md e do .json
│ └── __main__.py # CLI
├── artigos/
│ └── rumo-a-eficiencia/ # o artigo analisado (main.tex + referencias.bib)
├── relatorios/ # saída gerada
├── requirements.txt
└── README.md
```
## Usando como biblioteca
```python
from analise_artigo import parse_article, parse_bib, analyze, write_reports
from pathlib import Path
artigo = parse_article("artigos/rumo-a-eficiencia/main.tex")
refs = parse_bib("artigos/rumo-a-eficiencia/referencias.bib")
an = analyze(artigo, refs)
print(an.metrics["flesch"], an.metrics["flesch_faixa"])
for c in an.checks:
print(c.icon, c.code, c.detail)
```
## Limitações conhecidas
- O parser é orientado a abntex2/ABNT; documentos em outras classes
(`article`, `report` com nomenclatura própria) exigem pequenas adaptações
em `texparse.py`.
- **Uma frase/parágrafo por linha**: as heurísticas de sentença/travessão
e de "parágrafo extenso" presumem que cada parágrafo do .tex ocupe uma
linha (convenção verificada nas fontes analisadas). Textos com parágrafos
múltiplos de linha tendem a ser fragmentados.
- **Flesch adaptado (pt-BR)**: usa a fórmula 206,835 − 1,015·MPF − 84,6·VMP
com sílabas estimadas por grupos de vogais (resultado clamped em 0–100).
Texto acadêmico técnico costuma pontuar baixo (p. ex. 0–15) — o índice
reflete vocabulário denso, não um "erro".
- **"se se" excluído** da detecção de palavras repetidas: é uso pronominal
legítimo ("avaliou-se se cada estudo…").
- **Simplificação de `.bib`**: nomes com escapes LaTeX recebem diacríticos
removidos para exibição (`Ki\vs\vs\, M.` → `Kiss`), apenas na apresentação
— o arquivo original não é modificado.
- Heurísticas de prosa não substituem revisão humana ou ferramentas como
LanguageTool.
## Ideias de evolução
- [ ] Exportar relatório em HTML com gráficos (referências por ano,
palavras por seção) a partir do `relatorio.json`
- [ ] Suporte a bibliografia biblatex (`.bbx`) e listas de referências
manualmente digitadas no corpo do .tex
- [ ] Detecção de consistência terminológica (ex.: "agentes pedagógicos"
vs. "agente pedagógico")
- [ ] Integração com LanguageTool via CLI para correção gramatical completa

View File

@@ -0,0 +1,23 @@
"""Análise de artigos acadêmicos em LaTeX (ABNT/abntex2).
Empacota os módulos de parsing (LaTeX e .bib), métricas,
verificações e geração de relatório.
"""
from .texparse import Article, parse_article
from .bib import Reference, parse_bib
from .analyze import Analysis, analyze
from .report import build_markdown, build_json
__all__ = [
"Article",
"parse_article",
"Reference",
"parse_bib",
"Analysis",
"analyze",
"build_markdown",
"build_json",
]
__version__ = "1.0.0"

View File

@@ -0,0 +1,85 @@
"""Linha de comando.
Exemplos:
python -m analise_artigo artigos/rumo-a-eficiencia --out relatorios
python -m analise_artigo caminho/para/main.tex
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from .bib import parse_bib
from .report import write_reports
from .texparse import parse_article
from .analyze import analyze
def find_tex(target: Path) -> Path | None:
if target.is_file() and target.suffix == ".tex":
return target
if target.is_dir():
candidates = sorted(target.glob("*.tex"))
return candidates[0] if candidates else None
return None
def find_bib(tex: Path) -> Path | None:
bib = tex.with_name(tex.stem + ".bib")
if bib.exists():
return bib
for candidate in sorted(tex.parent.glob("*.bib")):
return candidate
return None
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(
prog="analise-artigo",
description="Analisa um artigo LaTeX (ABNT) e gera relatório de "
"estrutura, métricas e citações.",
)
p.add_argument(
"artigo",
help="arquivo .tex ou diretório que contém o .tex",
)
p.add_argument(
"--out", "-o",
default="relatorios",
help="diretório de saída do relatório (padrão: relatorios/)",
)
args = p.parse_args(argv)
target = Path(args.artigo)
tex = find_tex(target)
if tex is None:
print(f"erro: nenhum .tex encontrado em {target}", file=sys.stderr)
return 1
bib = find_bib(tex)
print(f"→ artigo: {tex}")
print(f"→ referências: {bib or 'nenhum .bib encontrado'}")
article = parse_article(tex)
refs = parse_bib(bib) if bib else {}
analysis = analyze(article, refs)
outdir = Path(args.out)
md, js = write_reports(analysis, outdir)
resumo = analysis.metrics["resumo_checks"]
print(f"\nResumo: {resumo['ok']} ok, "
f"{resumo['warn']} aviso(s), "
f"{resumo['fail']} falha(s), "
f"{len(analysis.issues)} questão(ões) de prosa")
print(f"\n→ relatório: {md}")
print(f"→ dados: {js}")
return 0 if resumo["fail"] == 0 else 2
if __name__ == "__main__":
raise SystemExit(main())

346
analise_artigo/analyze.py Normal file
View File

@@ -0,0 +1,346 @@
"""Métricas textuais, consistência de citações e checklist de conformidade.
Regras ABNT/NBR usadas:
- NBR 10520: citações no texto devem existir na referências e vice-versa;
- NBR 6022: elementos obrigatórios do artigo (título, resumo,
palavras-chave, seções, referências);
- NBR 14724: elementos pré/textuais/pós-textuais.
"""
from __future__ import annotations
import re
from collections import Counter
from dataclasses import dataclass, field
from .bib import Reference
from .texparse import Article, Section, count_words
# ---------------------------------------------------------------------------
# Estruturas de resultado
# ---------------------------------------------------------------------------
@dataclass
class Check:
code: str
label: str
status: str # ok | warn | fail
detail: str = ""
@property
def icon(self) -> str:
return {"ok": "✅", "warn": "⚠️", "fail": "❌"}[self.status]
@dataclass
class Analysis:
article: Article
refs: dict[str, Reference]
checks: list[Check] = field(default_factory=list)
metrics: dict = field(default_factory=dict)
issues: list[str] = field(default_factory=list)
# ---------------------------------------------------------------------------
# Métricas de prosa
# ---------------------------------------------------------------------------
VOWELS = r"[aeiouyáàâãéêëíîïóôõúü]"
def syllables(word: str) -> int:
"""Aproximação de sílabas: 1 sílaba por grupo de vogais.
Contar *grupos* (em vez de caracteres) evita inflar ditongos como
"ei", "ão", "au" ("eficiência" → e|i|ê|i|a = 5, correto).
"""
w = re.sub(r"[^a-zA-Zà-öø-ÿ]", "", word.lower())
if not w:
return 1
n = len(re.findall(VOWELS + "+", w))
return max(1, n)
def sentences(text: str) -> list[str]:
"""Divide em frases respeitando as quebras de linha do .tex.
Nos artigos processados cada parágrafo/item de lista ocupa uma linha
própria (convenção verificada nas fontes), então linha vira fronteira
segura de frase; dentro da linha, pontuação [.!?] segue valendo.
"""
out: list[str] = []
for line in text.splitlines():
line = line.strip()
if not line:
continue
parts = re.split(r"(?<=[.!?])\s+(?=[A-ZÀ-ÖØ“\"(])", line)
for p in parts:
p = p.strip()
# ignora marcadores de lista ("-", "1.") e ruído trivial
if len(p) <= 1 or re.fullmatch(r"\d+[\.\)]|-+", p):
continue
out.append(p)
return out
def flesch(text: str) -> float | None:
"""Índice de Flesch adaptado para o português (0-100; mais alto = mais legível).
Fórmula padrão do português: 206,835 − 1,015·MPF − 84,6·VMP, com
sílabas estimadas por grupos de vogais (aproximação, sem acentuação
completa). Resultado clamped em [0, 100].
"""
words = count_words(text)
sents = sentences(text)
if not words or not sents:
return None
syl = sum(syllables(w) for w in words)
mpf = len(words) / len(sents) # média de palavras por frase
vmp = syl / len(words) # vocabulário médio (sílabas/palavra)
score = 206.835 - 1.015 * mpf - 84.6 * vmp
return round(min(100.0, max(0.0, score)), 1)
def flesch_band(score: float | None) -> str:
if score is None:
return "n/d"
if score >= 80:
return "Muito fácil"
if score >= 60:
return "Fácil"
if score >= 40:
return "Médio"
if score >= 20:
return "Difícil"
return "Muito difícil"
# ---------------------------------------------------------------------------
# Heurísticas de qualidade
# ---------------------------------------------------------------------------
REPEATED_WORD = re.compile(r"\b([a-zà-ÿ]{2,})\s+\1\b", re.I)
REDUNDANT = re.compile(
r"\b(a|ao|de|do|da|que|e|mas|no|na|em|por|para)\s+\1\b", re.I
)
LONG_SENTENCE = 60 # palavras
LONG_PARAGRAPH = 160 # palavras
def find_grammar_issues(text: str) -> list[str]:
issues: list[str] = []
for m in REPEATED_WORD.finditer(text):
# "se se" é legítimo em português pronominal ("avaliou-se se cada
# estudo..."); filtrado para não gerar falso-positivo
if m.group(1).lower() == "se":
continue
issues.append(
f"PALAVRA REPLICADA: \"{m.group(0).strip()}\" — "
f"...{context_around(text, m.start())}..."
)
for m in REDUNDANT.finditer(text):
issues.append(
f"REDUNDÂNCIA: \"{m.group(0).strip()}\" — "
f"...{context_around(text, m.start())}..."
)
return issues
def context_around(text: str, pos: int, span: int = 60) -> str:
start = max(0, pos - span)
end = min(len(text), pos + span)
snippet = text[start:end].replace("\n", " ")
return ("…" + snippet) if start > 0 else snippet
def long_sentences(text: str, limit: int = LONG_SENTENCE) -> list[tuple[str, int]]:
out = []
for s in sentences(text):
n = len(count_words(s))
if n > limit:
out.append((s[:120] + ("…" if len(s) > 120 else ""), n))
return sorted(out, key=lambda x: -x[1])[:10]
def long_paragraphs(text: str, limit: int = LONG_PARAGRAPH) -> list[tuple[str, int]]:
# Convenção das fontes analisadas: um parágrafo por linha no .tex,
# portanto cada linha (depois da limpeza) é candidata a parágrafo.
out = []
for p in (line.strip() for line in text.splitlines()):
if not p:
continue
n = len(count_words(p))
if n > limit:
out.append((p[:120] + "…", n))
return sorted(out, key=lambda x: -x[1])[:10]
def frequent_phrases(text: str, n: int = 5, top: int = 12) -> list[tuple[str, int]]:
words = [w.lower() for w in count_words(text)]
stop = {"de", "da", "do", "que", "com", "uma", "uma", "para", "em", "os",
"as", "no", "na", "e", "ou", "ao", "à", "dos", "das", "é", "são",
"and", "or"} # operadores de busca do estilo PRISMA (AND/OR)
words = [w for w in words if w not in stop and len(w) > 2]
grams = [" ".join(words[i : i + n]) for i in range(len(words) - n + 1)]
return Counter(grams).most_common(top)
def citation_stats(article: Article, refs: dict[str, Reference]) -> dict:
cited = Counter(article.citations)
defined = set(refs)
used = set(cited)
return {
"total_citations": len(article.citations),
"unique_cited": len(used),
"defined_refs": len(defined),
"orphan_citations": sorted(used - defined),
"unused_refs": sorted(defined - used),
"most_cited": [
(k, n, refs.get(k).label() if k in refs else "?")
for k, n in cited.most_common(8)
],
"year_distribution": dict(
Counter(
(refs[k].year or "?") if k in refs else "?" for k in used
)
),
}
# ---------------------------------------------------------------------------
# Análise completa
# ---------------------------------------------------------------------------
RECOGNIZED_SECTIONS = ["introdução", "metodologia", "conclusão"]
EXPECTED_LITERATURE = ["revisão de literatura", "revisão da literatura",
"estado da arte", "revisão de literatura",
"fundamentação", "referencial"]
def analyze(article: Article, refs: dict[str, Reference]) -> Analysis:
checks: list[Check] = []
issues: list[str] = []
# --- estrutura / pré-textuais ----------------------------------------
checks.append(Check(
"ABNT-01", "Título presente",
"ok" if article.title else "fail",
article.title or "não encontrado no \\titulo{}",
))
checks.append(Check(
"ABNT-02", "Autores informados",
"ok" if article.authors else "fail",
", ".join(article.authors) or "não encontrado",
))
checks.append(Check(
"ABNT-03", "Local e data",
"ok" if article.local and article.data else "warn",
f"{article.local}, {article.data}".strip(",") or "incompleto",
))
abs_words = len(count_words(article.abstract))
checks.append(Check(
"ABNT-04", "Resumo presente (150-500 palavras, NBR 6022)",
"ok" if 150 <= abs_words <= 500 else "warn",
f"{abs_words} palavras" if abs_words else "não encontrado",
))
checks.append(Check(
"ABNT-05", "Palavras-chave (3 a 8, separadas por ponto)",
"ok" if 3 <= len(article.keywords) <= 8 else "warn",
", ".join(article.keywords) or "não encontradas",
))
all_titles = [s.title.lower() for s in article.sections]
for expected in RECOGNIZED_SECTIONS:
hit = next((t for t in all_titles if expected in t), None)
checks.append(Check(
"ABNT-06", f"Seção '{expected.capitalize()}'",
"ok" if hit else "warn",
hit or "não identificada",
))
lit_hit = next((t for t in all_titles
if any(e in t for e in EXPECTED_LITERATURE)), None)
checks.append(Check(
"ABNT-07", "Seção de revisão de literatura",
"ok" if lit_hit else "fail",
lit_hit or "faltando",
))
# citações / referências ------------------------------------------------
stats = citation_stats(article, refs)
checks.append(Check(
"ABNT-08", "Citações no corpo do texto",
"ok" if stats["total_citations"] >= 5 else "warn",
f"{stats['total_citations']} citações, "
f"{stats['unique_cited']} referências citadas",
))
checks.append(Check(
"ABNT-09", "Citações sem entrada no .bib (NBR 10520)",
"ok" if not stats["orphan_citations"] else "fail",
", ".join(stats["orphan_citations"]) or "nenhuma",
))
checks.append(Check(
"ABNT-10", "Entradas .bib não citadas no texto (NBR 10520)",
"ok" if not stats["unused_refs"] else "warn",
", ".join(stats["unused_refs"]) or "nenhuma",
))
# figuras ------------------------------------------------------------
checks.append(Check(
"ABNT-11", "Figuras/quadros identificados",
"ok" if (article.figures or article.tables) else "warn",
f"{len(article.figures)} figuras, {len(article.tables)} quadros",
))
# prosa ---------------------------------------------------------------
body_text = "\n\n".join(s.text for s in article.sections)
flesch_score = flesch(body_text)
top_phrases = frequent_phrases(body_text)
repeated = find_grammar_issues(article.abstract + " " + body_text)
l_sentences = long_sentences(body_text)
l_paragraphs = long_paragraphs(body_text)
for i in repeated:
issues.append(i)
for s, n in l_sentences:
issues.append(f"Frase longa ({n} palavras): \"{s}\"")
for p, n in l_paragraphs:
issues.append(f"Parágrafo extenso ({n} palavras): \"{p}\"")
metrics = {
"palavras_corpo": len(count_words(body_text)),
"palavras_totais": article.text_words,
"sentencas": len(sentences(body_text)),
"media_palavras_por_sentenca": round(
len(count_words(body_text)) / max(1, len(sentences(body_text))), 1
),
"vocabulario_unico": len(
set(w.lower() for w in count_words(body_text))
),
"flesch": flesch_score,
"flesch_faixa": flesch_band(flesch_score),
"secoes": [
{
"titulo": s.title,
"nivel": s.level,
"palavras": s.words,
"percentual": round(
100 * s.words / max(1, article.text_words), 1
),
}
for s in article.sections
],
"top_frases": top_phrases,
"citacoes": stats,
}
summary = {
status: sum(1 for c in checks if c.status == status)
for status in ("ok", "warn", "fail")
}
metrics["resumo_checks"] = summary
return Analysis(
article=article, refs=refs, checks=checks, metrics=metrics,
issues=issues,
)

167
analise_artigo/bib.py Normal file
View File

@@ -0,0 +1,167 @@
"""Parser simples de arquivos .bib (BibTeX).
Usa apenas a biblioteca padrão; suporta campos com valores em chaves
aninhadas e valores multi-linha.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
class Reference:
key: str
type: str # article, inproceedings, misc, ...
fields: dict[str, str] = field(default_factory=dict)
@property
def authors(self) -> str:
value = self.fields.get("author", "").strip()
# "X, A. and B, C. and others" -> "X, A., B, C. et al." (apenas campos author)
value = re.sub(r"\s+and\s+others\b", " et al.", value, flags=re.I)
return re.sub(r"\s+and\s+", ", ", value, flags=re.I)
@property
def title(self) -> str:
return self.fields.get("title", "").strip()
@property
def year(self) -> str:
return self.fields.get("year", "").strip()
@property
def journal(self) -> str:
return self.fields.get(
"journal", self.fields.get("booktitle", "")
).strip()
def label(self) -> str:
"""Rótulo curto autor (ano)."""
first = re.split(r",|and", self.authors)[0].strip()
if "," in first:
surname = first.split(",")[0].strip()
else:
surname = last_token(first)
return f"{surname} ({self.year})" if self.year else surname
def last_token(name: str) -> str:
parts = name.replace("-", " ").split()
return parts[-1] if parts else name
def _split_entries(text: str) -> list[tuple[str, str, str]]:
"""Retorna [(type, key, body), ...] respeitando aninhamento de chaves."""
entries: list[tuple[str, str, str]] = []
i = 0
while True:
m = re.compile(r"@(\w+)\s*\{").search(text, i)
if not m:
break
etype, key = m.group(1), ""
start = m.end()
depth = 1
j = start
while j < len(text) and depth:
if text[j] == "{":
depth += 1
elif text[j] == "}":
depth -= 1
j += 1
body = text[start : j - 1]
# chave = conteúdo até a primeira vírgula fora de chaves
depth = 0
k = 0
while k < len(body):
c = body[k]
if c == "{":
depth += 1
elif c == "}":
depth -= 1
elif c == "," and depth == 0:
break
if c == "\n" and depth == 0:
break
k += 1
key = body[:k].strip()
entries.append((etype, key, body[k + 1 :]))
i = j
return entries
def _parse_fields(body: str) -> dict[str, str]:
fields: dict[str, str] = {}
i = 0
n = len(body)
while i < n:
while i < n and body[i] in " \t\n,":
i += 1
if i >= n:
break
m = re.compile(r"(\w+)\s*=").match(body, i)
if not m:
break
fname = m.group(1).lower()
i = m.end()
while i < n and body[i] in " \t\n":
i += 1
if i >= n:
break
if body[i] == "{":
depth = 1
j = i + 1
while j < n and depth:
if body[j] == "{":
depth += 1
elif body[j] == "}":
depth -= 1
j += 1
value = body[i + 1 : j - 1]
i = j
else:
m2 = re.compile(r"([^,]+)").match(body, i)
if not m2:
i += 1
continue
value = m2.group(1)
i = m2.end()
value = value.strip()
value = re.sub(r"\s*\n\s*", " ", value)
value = value.replace("{", "").replace("}", "").strip()
value = _clean_bib_value(value)
fields[fname] = value
return fields
def _clean_bib_value(value: str) -> str:
"""Remove escapes LaTeX correntes em .bib (diacríticos, \\& etc.).
Ex.: ``Ki\\vs\\vs`` -> ``Kiss``, ``L'opez`` -> ``Lopez``,
``Tirke\\cs`` -> ``Tirkes``.
"""
value = re.sub(
r"\\[\'\"~vc](?:\{([A-Za-z])\}|([A-Za-z]))",
lambda m: m.group(1) or m.group(2),
value,
flags=re.I,
)
for esc, ch in (("\\&", "&"), ("\\$", "$"), ("\\%", "%"),
("\\_", "_"), ("\\#", "#")):
value = value.replace(esc, ch)
value = value.replace("---", " — ").replace("--", " – ")
return value
def parse_bib(bib_path: Path | str) -> dict[str, Reference]:
text = Path(bib_path).read_text(encoding="utf-8")
refs: dict[str, Reference] = {}
for etype, key, body in _split_entries(text):
if not key:
continue
refs[key] = Reference(key=key, type=etype, fields=_parse_fields(body))
return refs

172
analise_artigo/report.py Normal file
View File

@@ -0,0 +1,172 @@
"""Gera o relatório em Markdown e o artefato JSON."""
from __future__ import annotations
import json
import re
from datetime import datetime, timezone
from pathlib import Path
from .analyze import Analysis
from .bib import Reference
from .texparse import count_words
def _md_escape(text: str) -> str:
return text.replace("|", "\\|")
def _ref_line(ref: Reference) -> str:
parts = [ref.year and ref.year, ref.title, ref.authors, ref.journal]
parts = [re.sub(r"\s*\n\s*", " ", p) for p in parts if p]
if not parts:
return ref.key
return "; ".join(parts) if len(parts) > 1 else parts[0]
def build_markdown(a: Analysis) -> str:
art, m = a.article, a.metrics
lines: list[str] = []
add = lines.append
add(f"# Análise do artigo")
add("")
add(f"> **{art.title}**")
add(f"> Autores: {', '.join(art.authors)} — {art.local}, {art.data} — {art.instituicao}")
add("")
resumo = m["resumo_checks"]
add(f"**Resultado global:** ✅ {resumo['ok']} ok · ⚠️ {resumo['warn']} aviso(s) · ❌ {resumo['fail']} falha(s) · 📝 {len(a.issues)} questão(ões) de prosa")
add("")
# 1. Estrutura ---------------------------------------------------------
add("## 1. Estrutura e elementos")
add("")
add("| # | Verificação | Status | Detalhe |")
add("|---|---|---|---|")
for c in a.checks:
add(f"| {c.code} | {c.label} | {c.icon} | {_md_escape(c.detail)} |")
add("")
# 2. Métricas textuais ------------------------------------------------
add("## 2. Métricas textuais")
add("")
add("| Métrica | Valor |")
add("|---|---|")
add(f"| Palavras (corpo) | {m['palavras_corpo']:,} |")
add(f"| Palavras (total, c/ resumo) | {m['palavras_totais']:,} |")
add(f"| Frases | {m['sentencas']} |")
add(f"| Média de palavras por frase | {m['media_palavras_por_sentenca']} |")
add(f"| Vocabulário único | {m['vocabulario_unico']:,} |")
add(f"| Legibilidade (Flesch adaptado) | {m['flesch']} — {m['flesch_faixa']} |")
add("")
add("### Distribuição por seção")
add("")
add("| Seção | Palavras | % do total |")
add("|---|---:|---:|")
for s in m["secoes"]:
add(f"| {s['titulo']} | {s['palavras']:,} | {s['percentual']}% |")
add("")
# 3. Citações ----------------------------------------------------------
stats = m["citacoes"]
add("## 3. Citações e referências (NBR 10520 / 6023)")
add("")
add(f"- Citações no texto: **{stats['total_citations']}** ({stats['unique_cited']} referências distintas)")
add(f"- Entradas no `.bib`: **{stats['defined_refs']}**")
if stats["orphan_citations"]:
add(f"- ❌ Citadas mas **ausentes do .bib**: {', '.join(stats['orphan_citations'])}")
if stats["unused_refs"]:
add(f"- ⚠️ No .bib mas **não citadas no texto**: {', '.join(stats['unused_refs'])}")
add("")
if stats["most_cited"]:
add("### Referências mais citadas")
add("")
add("| Referência | Cit.ªs | Entrada |")
add("|---|---:|---|")
for key, n, label in stats["most_cited"]:
add(f"| {_md_escape(label)} | {n} | `{key}` |")
add("")
if stats["year_distribution"]:
add("### Referências por ano")
add("")
years = sorted(stats["year_distribution"].items())
add("| Ano | N.º |")
add("|---:|---:|")
for year, n in years:
add(f"| {year} | {n} |")
add("")
# 4. Prosa -------------------------------------------------------------
add("## 4. Questões de prosa")
add("")
if a.issues:
for i in a.issues:
add(f"- {i}")
else:
add("Nenhuma questão detectada pelas heurísticas.")
add("")
if m["top_frases"]:
add("### Expressões mais repetidas (5 palavras)")
add("")
for phrase, n in m["top_frases"]:
add(f"- \"{phrase}\" — {n}×")
add("")
# 5. Referências -------------------------------------------------------
add("## 5. Referências do .bib")
add("")
for key in sorted(a.refs):
ref = a.refs[key]
cited = key in set(a.article.citations)
mark = "✅" if cited else "⚠️"
add(f"{mark} `{key}` — {_md_escape(_ref_line(ref))}")
add("")
add("---")
add(f"_Gerado em {datetime.now(timezone.utc):%Y-%m-%d %H:%M} UTC "
f"pelo projeto `analise-artigo` (v1.0)._")
return "\n".join(lines) + "\n"
def build_json(a: Analysis) -> str:
d = {
"article": {
"title": a.article.title,
"authors": a.article.authors,
"local": a.article.local,
"data": a.article.data,
"instituicao": a.article.instituicao,
"keywords": a.article.keywords,
"abstract_words": len(count_words(a.article.abstract)),
"sections": [
{"titulo": s.title, "nivel": s.level, "palavras": s.words}
for s in a.article.sections
],
"figures": a.article.figures,
"tables": a.article.tables,
},
"checks": [
{"code": c.code, "label": c.label, "status": c.status,
"detail": c.detail}
for c in a.checks
],
"metrics": a.metrics,
"issues": a.issues,
"references": {
k: {"type": r.type, "fields": r.fields,
"cited": k in set(a.article.citations)}
for k, r in a.refs.items()
},
}
return json.dumps(d, ensure_ascii=False, indent=2)
def write_reports(a: Analysis, outdir: Path) -> tuple[Path, Path]:
outdir.mkdir(parents=True, exist_ok=True)
md_path = outdir / "relatorio.md"
json_path = outdir / "relatorio.json"
md_path.write_text(build_markdown(a), encoding="utf-8")
json_path.write_text(build_json(a), encoding="utf-8")
return md_path, json_path

264
analise_artigo/texparse.py Normal file
View File

@@ -0,0 +1,264 @@
"""Extrai a estrutura textual de um documento LaTeX (abntex2/ABNT).
Converte o .tex em texto limpo preservando:
- título, autores, local/data (elementos pré-textuais);
- resumo e palavras-chave;
- seções/subseções e seus parágrafos;
- chaves de citação (\\cite, \\citeonline, \\citeauthor ...).
Não depende de pandoc: usa apenas a biblioteca padrão.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
class Section:
"""Seção do artigo com o texto limpo já convertido para prosa."""
title: str
level: int = 1
@property
def words(self) -> int:
return len(count_words(self.text))
@dataclass
class Article:
title: str = ""
authors: list[str] = field(default_factory=list)
local: str = ""
data: str = ""
instituicao: str = ""
abstract: str = ""
keywords: list[str] = field(default_factory=list)
sections: list[Section] = field(default_factory=list)
citations: list[str] = field(default_factory=list) # ordem de aparição
figures: list[str] = field(default_factory=list) # captions das figuras
tables: list[str] = field(default_factory=list) # captions de quadros
text_words: int = 0
def section(self, title: str) -> Section | None:
for s in self.sections:
if s.title.strip().lower() == title.strip().lower():
return s
return None
# ---------------------------------------------------------------------------
# Conversão LaTeX -> texto
# ---------------------------------------------------------------------------
_BRACE_CMDS = ["textbf", "emph", "textit", "texttt", "underline",
"bold", "itshape", "scshape", "MakeUppercase", "MakeLowercase"]
_CITE_RE = re.compile(r"\\cite\w*\{([^}]*)\}")
_BRACE_CMD_RE = {
cmd: re.compile(r"\\" + cmd + r"\{([^{}]*)\}") for cmd in _BRACE_CMDS
}
# Chaves com barra escapada no LaTeX (ex.: "\_", "\#").
# "\," "\;" "\%" "\$" "\&" já são tratados pela regex de duas linhas acima.
_ESCAPE_MAP = {
"\\_": "_",
"\\#": "#",
"\\\\": "\n",
}
def count_words(text: str) -> list[str]:
return re.findall(
r"[A-Za-z0-9À-ÖØ-öø-ÿ]+(?:['’-][A-Za-z0-9À-ÖØ-öø-ÿ]+)*", text
)
def _fold_brace_commands(s: str) -> str:
changed = True
while changed:
changed = False
for cmd, pat in _BRACE_CMD_RE.items():
s2, n = pat.subn(r"\1", s)
if n:
s, changed = s2, True
return s
def strip_latex(text: str, citations: list[str] | None = None) -> str:
"""Remove comandos e ambientes, preservando o conteúdo legível."""
# citações: captura as chaves e remove do texto
def _cite(m: re.Match) -> str:
if citations is not None:
for key in m.group(1).split(","):
key = key.strip()
if key:
citations.append(key)
return " "
text = _CITE_RE.sub(_cite, text)
# rótulos/referências internas e figuras
text = re.sub(r"\\label\{[^}]*\}", " ", text)
text = re.sub(r"\\(ref|pageref|eqref)\{[^}]*\}", " (ref.) ", text)
text = re.sub(r"\\includegraphics.*", " ", text)
text = re.sub(r"\\noindent", " ", text)
# comandos com argumento em chaves genéricos (legend, caption, textbf...)
text = _fold_brace_commands(text)
text = re.sub(r"\\(legend|caption|text|footnote)\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}", " ", text)
# ambientes: manter marcadores de item, eliminar o resto
text = re.sub(r"\\begin\{itemize\}", "\n- ", text)
text = re.sub(r"\\begin\{enumerate\}", "\n1. ", text)
text = re.sub(r"\\item\b", "\n- ", text)
text = re.sub(r"\\(end|begin)\{[^}]*\}", " ", text)
text = re.sub(r"\\toprule|\\midrule|\\bottomrule", " ", text)
text = text.replace("\\\\", "\n") # quebras de linha (\\) em tabular/autor
# comandos restantes sem argumento
text = re.sub(r"\\[a-zA-Z]+\*?", " ", text)
text = re.sub(r"\\[,;:\\!%$&]", " ", text)
for esc, ch in _ESCAPE_MAP.items():
text = text.replace(esc, ch)
# aspas e travessões
text = text.replace("``", '"').replace("''", '"')
text = text.replace("---", " — ").replace("--", " – ")
# tabular: & vira coluna
text = text.replace("&", " | ")
# limpar pontuação e espaços
text = re.sub(r"\[[a-z]*\]", " ", text) # posicionadores [htb]
text = re.sub(r"[{}]+", " ", text)
text = re.sub(r"[ \t]+", " ", text)
text = re.sub(r" +([,.;:!?])", r"\1", text)
text = re.sub(r"\n\s*", "\n", text)
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip()
# ---------------------------------------------------------------------------
# Parsing da estrutura
# ---------------------------------------------------------------------------
_SECTION_RE = re.compile(r"^\s*\\(section|subsection|subsubsection)\*?\s*\{([^}]+)\}")
def _capture_command(text: str, name: str) -> str:
"""Extrai o último conteúdo de \\name{...} ou \\<name> ... \\end{<name>}."""
m = re.search(r"\\" + name + r"\s*\{", text)
if not m:
return ""
start = m.end()
depth = 1
i = start
while i < len(text) and depth:
if text[i] == "{":
depth += 1
elif text[i] == "}":
depth -= 1
i += 1
return text[start : i - 1].strip()
def _extract_authors(text: str) -> list[str]:
raw = _capture_command(text, "autor")
if not raw:
raw = _capture_command(text, "author")
return [a.strip() for a in re.split(r"\\\\|\\[ ]", raw) if a.strip()]
def parse_article(tex_path: Path | str) -> Article:
tex_path = Path(tex_path)
text = tex_path.read_text(encoding="utf-8")
# remove comentários de linha (não escapados)
text = re.sub(r"(?<!\\)%[^\n]*", "", text)
citations: list[str] = []
raw_sections: list[tuple[int, str, list[str]]] = []
title = strip_latex(_capture_command(text, "titulo")
or _capture_command(text, "title"))
authors = _extract_authors(text)
local = strip_latex(_capture_command(text, "local"))
data = strip_latex(_capture_command(text, "data"))
instituicao = strip_latex(_capture_command(text, "instituicao")
or _capture_command(text, "institution"))
# resumo (ambiente resumoumacoluna do abntex2)
m = re.search(r"\\begin\{resumoumacoluna\}(.*?)\\end\{resumoumacoluna\}",
text, re.S)
abstract_raw = m.group(1) if m else ""
abstract = strip_latex(abstract_raw, citations)
keywords: list[str] = []
km = re.search(
r"Palavras-chave[: ]*\s*(.+)", abstract
)
if km:
kw_body = strip_latex(abstract)
kw_text = kw_body.split("Palavras-chave", 1)[-1]
kw_text = kw_text.split(":", 1)[-1].strip()
keywords = [k.strip() for k in re.split(r"[.;]", kw_text) if k.strip()]
# seções: percorre linha a linha acumulando o corpo de cada seção
current: tuple[int, str, list[str]] | None = None
preamble_text = ""
figures: list[str] = []
tables: list[str] = []
for line in text.splitlines():
sm = _SECTION_RE.match(line)
if sm:
level = {"section": 1, "subsection": 2, "subsubsection": 3}[sm.group(1)]
title_s = strip_latex(sm.group(2), citations)
if current:
raw_sections.append(current)
current = (level, title_s, [])
continue
if current is None:
preamble_text += line + "\n"
continue
current[2].append(line)
if current:
raw_sections.append(current)
article = Article(
title=title,
authors=authors,
local=local,
data=data,
instituicao=instituicao,
abstract=abstract,
keywords=keywords,
)
for level, title_s, lines in raw_sections:
sec = Section(title=title_s, level=level)
body = "\n".join(lines)
# captura captions de figuras/quadros antes de remover
for cap in re.findall(r"\\caption\{([^}]+)\}", body):
if "\\includegraphics" in body or "figura" in body.lower():
figures.append(strip_latex(cap))
else:
tables.append(strip_latex(cap))
sec.text = strip_latex(body, citations)
article.sections.append(sec)
article.citations = citations
article.figures = figures
article.tables = tables
article.text_words = (
len(count_words(title))
+ len(count_words(abstract))
+ sum(len(count_words(s.text)) for s in article.sections)
)
return article

View File

@@ -0,0 +1,11 @@
# Como usar no Overleaf
1. Crie um projeto novo no Overleaf a partir de um arquivo zip (Upload Project) usando o arquivo `projeto_latex.zip`.
2. Nas configurações do projeto (ícone de engrenagem, menu Settings), defina:
- Compiler: pdfLaTeX
- TeX Live version: mais recente disponível
3. O Overleaf detecta sozinho que o `referencias.bib` precisa do Biber (por causa do abntex2cite com backend biblatex). Se a bibliografia não aparecer na primeira compilação, rode "Recompile" uma segunda vez, é o comportamento normal do biber/biblatex.
4. Pendências para você preencher antes de considerar o documento pronto:
- Instituição, curso e e-mail de cada autor no comando \instituicao{} e, se quiser, no \autor{}
- Local exato (cidade) se Penedo/AL não for o correto para a capa
- Conferir se quer manter os anos de Hamdan (2023) e Kišš (2024) como ajustei, com base na lista de referências, ou reverter para os anos que estavam no corpo do texto original (2022 e 2023)

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -0,0 +1,237 @@
\documentclass[
article,
12pt,
oneside,
a4paper,
sumario=tradicional
]{abntex2}
% ---------------------------------------------------------------------------
% Pacotes
% ---------------------------------------------------------------------------
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[brazil]{babel}
\usepackage{graphicx}
\usepackage{indentfirst}
\usepackage{color}
\usepackage{microtype}
\usepackage{booktabs}
% Citações no padrão ABNT (NBR 10520), estilo alfabético (autor-data).
% abntex2cite é baseado em BibTeX classico, não em biblatex: compila com
% pdfLaTeX + BibTeX, sem precisar trocar o compilador no Overleaf.
\usepackage[alf]{abntex2cite}
% ---------------------------------------------------------------------------
% Dados do trabalho
% ---------------------------------------------------------------------------
\titulo{Rumo à Eficiência Linguística Educacional: estratégias e tendências na integração de IA em atividades manuscritas}
\autor{%
Jessica Silva de Oliveira \\
Fernando Stherwerson Rocha Lima \\
Jario Jose dos Santos Junior%
}
\local{Penedo, Alagoas}
\data{2025}
\instituicao{%
% TODO: inserir instituição, curso e e-mail de contato de cada autor
Universidade Federal de Alagoas -- UFAL}
\begin{document}
\selectlanguage{brazil}
\frenchspacing
% ---------------------------------------------------------------------------
% Elementos pré-textuais
% ---------------------------------------------------------------------------
\imprimirtitulo
\vspace{1cm}
\begin{flushright}
\imprimirautor
\end{flushright}
\begin{resumoumacoluna}
Este estudo pretende identificar as principais estratégias, desafios e tendências na integração da Inteligência Artificial (IA) em atividades de escrita manuscrita, com foco no desenvolvimento de agentes pedagógicos. A pesquisa se concretizou numa revisão integrativa de literatura, com base em artigos científicos de alto impacto, que expusessem em sua discussão ponderações sobre reconhecimento de escrita manuscrita, aprendizado de máquina, sensores e interação humano-computador. Os resultados apontam, inicialmente, para uma evolução significativa das técnicas, migrando de abordagens morfológicas para modelos de aprendizado profundo como Long Short-Term Memory (LSTMs) e Transformers. Há indícios de que a combinação de hardware avançado, como sensores flexíveis, com algoritmos robustos é essencial para a captura de dados de alta fidelidade. O uso de IA surge como o caminho mais indicado no alcance de um feedback pedagógico personalizado, porém a efetividade em cenários reais ainda enfrenta desafios como adaptação a novos usuários e a necessidade de grandes volumes de dados anotados. Para trabalhos futuros, sugere-se que se aprofundem no estudo de metodologias de aprendizado semissupervisionado e IA explicável (XAI); que se apresentem propostas de construção de agentes multimodais; e que se aborde o papel da personalização no desenvolvimento de ferramentas educacionais eficazes.
\vspace{\onelineskip}
\noindent
\textbf{Palavras-chave}: Agentes Pedagógicos. Reconhecimento de Escrita Manuscrita. Inteligência Artificial. Aprendizado Profundo. Educação.
\end{resumoumacoluna}
% ---------------------------------------------------------------------------
% Elementos textuais
% ---------------------------------------------------------------------------
\textual
\section{Introdução}
A escrita manuscrita é muito mais do que simplesmente desenhar letras em um papel, ela é uma atividade complexa que envolve habilidades cognitivas e motoras, e sua individualidade é tão única que pode ser usada até mesmo para identificar o autor de um texto \cite{abbas2021}. No mundo de hoje, onde tudo parece ser digital, a habilidade de escrever à mão continua sendo fundamental para o desenvolvimento intelectual e a expressão pessoal. Ao mesmo tempo, a Inteligência Artificial (IA) tem se mostrado uma ferramenta poderosa, capaz de transformar indústrias inteiras, e a educação é uma das áreas com maior potencial de inovação. A união dessas duas áreas, através da criação de ``agentes pedagógicos'' de IA, promete revolucionar a forma como ensinamos e aprendemos a escrever.
Agente pedagógico consiste em tutor digital inteligente. É um sistema de IA que foi projetado para observar, analisar e dar feedback sobre como um aluno escreve, combinando algoritmos matemáticos com sensores avançados para interpretar o conteúdo \cite{liu2022}. A necessidade de um sistema assim é clara quando consideramos o quão desafiador é o reconhecimento da escrita. Cada pessoa tem um estilo único, influenciado por fatores como a força muscular e até mesmo o sistema nervoso central \cite{abbas2021}. Essa variação, que torna cada caligrafia pessoal, é um grande obstáculo para um sistema de computador que precisa entender o que está sendo escrito de forma generalizada \cite{roy2014}.
A pesquisa neste campo se divide em duas grandes áreas: o reconhecimento offline e o on-line. O reconhecimento offline trabalha com uma imagem de algo que já foi escrito, como um arquivo escaneado que foi gerado de uma página. Já o reconhecimento on-line é dinâmico: ele captura os dados enquanto a pessoa escreve, registrando a velocidade, a pressão e o caminho da caneta. Ambas as abordagens são investigadas na literatura \cite{das2016}. É no reconhecimento on-line que os agentes pedagógicos mostram seu verdadeiro potencial, pois eles podem analisar o processo da escrita, e não apenas o resultado final, além de facilitar o trabalho de quem está o utilizando, ele pode oferecer um feedback com riquezas de detalhes e de forma imediata.
Apesar do grande potencial do Aprendizado Profundo (Deep Learning -- DL), ainda existe uma diferença importante entre o que o mercado precisa e o que as universidades ensinam. Segundo \citeonline{tirkes2018}, muitos cursos de ensino superior não oferecem conteúdos específicos sobre DL, o que dificulta a formação de profissionais qualificados nessa área. Este estudo nasce justamente da necessidade de mapear e compreender o que já foi investigado e produzido sobre o uso de agentes pedagógicos de IA voltados para a escrita.
Ao organizar os estudos já realizados, é possível ter uma visão clara do campo, identificar práticas eficazes, apontar os principais desafios e compreender para onde essa área de pesquisa está se desenvolvendo. O objetivo é oferecer uma base estruturada que contribua para o avanço das investigações, destacar as tecnologias mais utilizadas, tanto em termos de software quanto de hardware, e apresentar informações relevantes para estudantes, desenvolvedores e pesquisadores interessados na aplicação da IA na educação.
Este artigo está estruturado da seguinte forma: a Seção 2 detalha a metodologia de revisão utilizada. A Seção 3 apresenta uma revisão aprofundada da literatura, dividida por temas. A Seção 4 discute as estratégias práticas de implementação e os desafios atuais. A Seção 5 conclui o trabalho e aponta para futuras direções de pesquisa.
\section{Metodologia}
Este trabalho foi desenvolvido por meio de uma análise aprofundada da literatura científica, método escolhido para sintetizar o conhecimento de um campo multidisciplinar que abrange desde a ciência dos materiais até o aprendizado de máquina e a pedagogia. A abordagem de revisar múltiplos estudos permite a construção de um panorama coeso sobre o estado da arte, identificando tanto as práticas consolidadas quanto as fronteiras da pesquisa. O campo de reconhecimento de escrita com IA, por natureza, envolve a colaboração de diferentes especialistas, e uma revisão ampla é a forma mais adequada de capturar essa complexidade \cite{castrobleda2013}.
A primeira etapa consistiu na formulação da pergunta de pesquisa, que serviu como pilar para todo o desenvolvimento do estudo. A pergunta central foi definida como: ``Quais são as estratégias, os desafios e as tendências na integração de inteligência artificial em atividades manuscritas com fins educacionais?''
Esta questão foi projetada para guiar a busca e a análise de forma a cobrir os aspectos técnicos, práticos e futuros da aplicação de agentes pedagógicos de IA. Essa questão central foi desdobrada nas seguintes perguntas de pesquisa, que orientaram a extração de dados de cada estudo: (i) como a IA pode auxiliar na correção de manuscritos; (ii) como o uso de IA na correção de atividades manuscritas pode melhorar o desempenho dos estudantes; (iii) como a IA pode ajudar professores a identificar áreas de dificuldade nas atividades manuscritas dos alunos; (iv) como a IA pode auxiliar o professor a aprimorar sua instrução a partir da identificação dessas dificuldades; (v) quais tecnologias são mais adequadas para desenvolver um sistema de feedback adaptativo para atividades manuscritas; (vi) como a IA pode ser treinada para reconhecer padrões de escrita manuscrita em diferentes contextos educacionais; (vii) quais são as etapas essenciais no desenvolvimento de um sistema de feedback adaptativo para atividades manuscritas; (viii) como garantir que o feedback gerado por IA seja adequado às necessidades individuais dos estudantes; e (ix) quais métricas podem ser usadas para avaliar a acurácia do feedback em atividades manuscritas.
A base para esta revisão foi um corpus pré-selecionado de artigos científicos publicados em conferências e periódicos de alto impacto, como os da IEEE e ACM, que são fóruns comuns para a disseminação de pesquisas nesta área. No total foram utilizados 5 bases científicas (ACM, IEEE, Web of Science, Science Direct e Scopus), das quais foram importados, respectivamente, 2.725, 7.019, 3.554, 5.915 e 11 registros, totalizando 19.224 registros identificados. A seleção de palavras-chave foi crucial para abranger as diferentes facetas do problema. Foram utilizados os seguintes termos:
\begin{itemize}
\item (parte 01) -- (\emph{``handwriting recognition''} \textbf{OR} \emph{``handwritten recognition system''} \textbf{OR} \emph{``handwritten text recognition''} \textbf{OR} \emph{``OCR for handwriting''})
\item (parte 02) -- (\emph{``deep learning''} \textbf{OR} \emph{``machine learning''} \textbf{OR} \emph{``neural networks''} \textbf{OR} \emph{``AI''})
\item (parte 03) -- (\emph{``pedagogical agents''} \textbf{OR} \emph{``learning partner''} \textbf{OR} \emph{``virtual tutor''} \textbf{OR} \emph{``educational assistant''})
\item (parte 04) -- (\emph{``learning''} \textbf{OR} \emph{``e-learning''} \textbf{OR} \emph{``online education''} \textbf{OR} \emph{``digital learning''})
\item (parte 05) -- (\emph{``feedback''} \textbf{OR} \emph{``performance assessment''} \textbf{OR} \emph{``student feedback''})
\end{itemize}
\noindent (parte 01) \textbf{AND} (parte 02) \textbf{AND} (parte 03) \textbf{AND} (parte 04) \textbf{AND} (parte 05)
Além disso, foram definidos critérios de exclusão e inclusão para filtrar os conteúdos encontrados para ter conteúdos que atendam aos critérios estabelecidos.
\begin{quadro}
\caption{Critérios estabelecidos}
\centering
\begin{tabular}{p{6cm}p{6cm}}
\toprule
\textbf{Critérios de Inclusão} & \textbf{Critérios de Exclusão} \\
\midrule
Estudos que apresentem o uso de Agentes pedagógicos & Estudos duplicados \\
Estudos que compreendem atividades manuscritas & Estudos em idioma distinto do inglês \\
Estudos sobre o desenvolvimento do aluno & Estudos que não apresentem atividades manuscritas \\
Artigos completos publicados em periódicos revisados por pares ou conferências de alto impacto & Estudos sem validação empírica \\
& Short-paper: estudos com menos de 5 páginas \\
\bottomrule
\end{tabular}
\legend{Fonte: Elaborado pelos autores (2025).}
\end{quadro}
Conforme mostrado no quadro anterior, foram estabelecidos critérios que contribuam diretamente para a aplicação de IA ao reconhecimento de escrita, especialmente em contextos que envolvem Interação Humano-Computador (HCI) ou que tenham clara relevância pedagógica \cite{tirkes2018}. Foram excluídos trabalhos que, embora relacionados, não se aprofundavam no núcleo da questão de pesquisa. No total foram 19.224 artigos encontrados na base de dados, após a seleção dos artigos, a etapa seguinte foi a de análise crítica e extração de dados.
\begin{figure}[htb]
\caption{Extração de dados}
\centering
\includegraphics[width=0.75\textwidth]{images/figura1_extracao.jpg}
\legend{Fonte: Autores (2025).}
\end{figure}
Todo o processo de seleção descrito nesta seção foi conduzido de forma manual pelos autores. Conforme demonstrado na figura acima, na etapa de análise dos registros no Parsifal foi utilizada uma automação apenas como apoio, para auxiliar na seleção dos estudos que compõem este artigo, sem substituir a decisão manual sobre os critérios de pesquisa. Desenvolvida em Python e executada em ambiente Linux, a automação funciona da seguinte forma: o usuário informa as credenciais do Parsifal e define as cinco partes da string de busca (parte 01 a 05) e, a partir disso, o script percorre os registros aprovando aqueles que atendem a pelo menos 4 das 5 partes e rejeitando os demais. A automação foi executada em duas rodadas distintas no Parsifal, que chegaram ao mesmo resultado. Após a utilização da extração de dados, foram aceitos cerca de 25 artigos que atingiam pelo menos 4 dos critérios estabelecidos, após isso foi realizada a leitura manual que constatou que os 25 estão relacionados ao tema e dos 25 foram selecionados 12 artigos para compor esta revisão.
\begin{figure}[htb]
\caption{Fluxograma de como foi realizado}
\centering
\includegraphics[width=0.5\textwidth]{images/figura2_fluxograma.png}
\legend{Fonte: Autores (2025).}
\end{figure}
O fluxo completo de seleção, detalhado na Figura 2, seguiu quatro passos e foi conduzido manualmente do início ao fim, com a automação atuando apenas como apoio na fase de análise dos registros no Parsifal. No Passo 1, os 19.224 registros identificados manualmente nas cinco bases científicas foram submetidos à triagem automática, executada em duas rodadas no Parsifal com o mesmo resultado, que aprovava o registro caso atendesse a pelo menos 4 das 5 partes da string de busca. Esse processo resultou em 25 registros aprovados, excluindo os demais 19.199 por não atingirem o mínimo de partes exigido. No Passo 2, os 25 registros aprovados passaram por leitura completa à luz dos critérios de inclusão e exclusão do Quadro 1. No Passo 3, avaliou-se se cada estudo lido se aprofundava no núcleo da questão de pesquisa, o que levou à exclusão de 13 registros. No Passo 4, chegou-se ao corpus final de 12 artigos, que compõem a base desta revisão.
Cada estudo selecionado foi analisado em sua totalidade com o propósito de identificar e extrair as informações essenciais ao desenvolvimento da pesquisa. Essas informações foram organizadas em categorias temáticas, o que permitiu uma compreensão mais estruturada das principais abordagens científicas no campo de reconhecimento de escrita por meio de inteligência artificial. A categorização revelou quatro eixos centrais que orientam as discussões contemporâneas sobre o tema.
O primeiro aborda a evolução dos algoritmos empregados, que vão desde métodos clássicos, como a morfologia matemática \cite{das2016}, até arquiteturas avançadas de aprendizado profundo, como redes LSTM e modelos baseados em Transformers \cite{hamdan2023}. O segundo refere-se às inovações tecnológicas voltadas à aquisição dos dados manuscritos, destacando-se sensores flexíveis de hidrogel \cite{liu2022} e dispositivos baseados em unidades de medição inercial (IMUs) de baixo custo \cite{wangzhao2024}. O terceiro eixo trata dos desafios persistentes enfrentados pelos sistemas de IA, especialmente no que diz respeito à adaptação dos modelos a diferentes usuários, conhecido como ``desvio de domínio'' \cite{ott2022}. Por fim, o quarto eixo compreende estratégias avançadas de treinamento com conjuntos de dados limitados, como o aprendizado semi-supervisionado, que tem ganhado relevância por sua capacidade de otimizar modelos mesmo com dados escassos \cite{kiss2024}.
A etapa final consistiu na integração dos dados coletados em uma narrativa clara e objetiva, elaborada com o objetivo de conduzir o leitor desde os fundamentos teóricos até os debates mais recentes sobre os desafios e avanços na área. Essa organização cuidadosa garante que as conclusões estejam bem embasadas nas pesquisas mais relevantes, com todas as fontes devidamente citadas, respeitando os critérios acadêmicos exigidos.
\section{Revisão de literatura}
Com base nos artigos selecionados, pode-se definir que a construção de agentes pedagógicos inteligentes voltados para a escrita manuscrita baseia-se em três pilares interdependentes: os algoritmos de inteligência artificial para reconhecimento de padrões, as tecnologias de hardware para captura de dados e os fundamentos pedagógicos que orientam a aplicação desses sistemas em contextos educacionais. Esses pilares formam a base teórica que permite o desenvolvimento de soluções cada vez mais eficazes, precisas e adaptáveis às necessidades dos alunos.
O primeiro pilar está relacionado às abordagens computacionais utilizadas para interpretar a escrita manual. Inicialmente, os sistemas adotavam técnicas clássicas, como a morfologia matemática, que se baseava na análise de componentes visuais específicos, como bolhas e hastes, presentes nos caracteres manuscritos. Essas abordagens apresentavam vantagens como velocidade e independência de grandes conjuntos de dados, conforme apontado por \citeonline{das2016}, mas sua rigidez limitava a aplicação diante da diversidade de estilos caligráficos encontrados na prática cotidiana.
Com o avanço das capacidades computacionais, surgiram os modelos de aprendizado de máquina, como os Modelos Ocultos de Markov (HMMs), que possibilitavam o reconhecimento de sequências sem a necessidade de segmentação manual. Esses modelos foram posteriormente superados pelas redes neurais recorrentes (RNNs), especialmente pelas variantes LSTM e BiLSTM, que incorporaram células de memória para captar dependências temporais mais longas e processar a escrita em múltiplas direções \cite{roy2014, hamdan2023}. No entanto, o processamento sequencial dessas redes dificultava a paralelização, tornando o treinamento mais demorado e sujeito ao problema de desaparecimento de gradientes, uma limitação técnica que, na prática, tornava o sistema mais lento para aprender a reconhecer textos longos. Buscando superar essas limitações, pesquisadores passaram a adotar arquiteturas Transformer, inicialmente desenvolvidas para tarefas de tradução.
A principal inovação do Transformer está no mecanismo de autoatenção, que permite ao modelo considerar simultaneamente todos os elementos de uma sequência e atribuir diferentes pesos conforme sua relevância contextual \cite{hamdan2023}. Essa característica torna os Transformers altamente eficientes e compatíveis com hardware moderno, permitindo um treinamento mais rápido e com maior precisão, o que consolidou essa arquitetura como referência em tarefas relacionadas à linguagem. Além dos modelos sequenciais, é necessário considerar as abordagens voltadas à leitura visual da escrita, que se valem de redes neurais convolucionais (CNNs). Essas redes são especialistas na extração de características visuais e normalmente constituem a primeira etapa dos sistemas de reconhecimento, atuando na conversão de imagens em vetores de características. Posteriormente, essas informações são processadas por redes sequenciais ou Transformers para interpretação textual \cite{kiss2024}.
Versões aprimoradas, como a ResNeSt, utilizam mecanismos de atenção internos para destacar os elementos visuais mais relevantes, elevando a qualidade da representação. Outras arquiteturas, como as Deep Belief Networks (DBNs), têm sido exploradas na fase de refinamento, contribuindo para a escolha da palavra mais provável dentro de múltiplas possibilidades, aumentando a precisão do sistema \cite{roy2014}.
O segundo pilar da pesquisa reside na aquisição dos dados manuscritos e nas formas de interação humano-computador (HCI). Para um agente pedagógico operar em tempo real, a forma como os dados são capturados é fundamental. Diversas estratégias vêm sendo desenvolvidas com o objetivo de tornar essa interação mais natural, eficaz e acessível. A utilização de sensores inerciais, presentes em dispositivos móveis como smartphones, tem ganhado destaque devido à sua portabilidade e baixo custo.
As Unidades de Medição Inercial (IMUs), compostas por acelerômetros e giroscópios, permitem que o dispositivo seja utilizado como uma ``caneta virtual'' sem a necessidade de hardware especializado. Segundo \citeonline{wangzhao2024}, os IMUs são vantajosos por funcionarem em qualquer ambiente, independente de luz, além de apresentarem baixo consumo energético. No entanto, apresentam limitações quanto à precisão, pois estão sujeitos a ruídos e ao fenômeno da deriva, o que compromete a reconstrução exata da trajetória de escrita.
Esse desafio tem motivado pesquisas voltadas ao desenvolvimento de soluções computacionais que compensem tais imprecisões. Outra linha de pesquisa promissora envolve sensores flexíveis e materiais avançados, capazes de captar micro-variações nos movimentos durante o ato de escrever. \citeonline{liu2022} descrevem a aplicação de hidrogéis condutivos, materiais flexíveis que mudam sua resistência elétrica conforme são esticados ou pressionados, possibilitando a coleta de dados com elevada sensibilidade.
Já \citeonline{zhou2025} apresentam sensores compósitos desenvolvidos a partir de elastômeros misturados com negro de fumo supercondutor, capazes de alcançar altos fatores de medição e detectar até os gestos mais sutis. Essas tecnologias não apenas ampliam a fidelidade da coleta, como também promovem uma experiência de interação mais natural entre o usuário e o sistema, alinhando-se aos princípios da HCI \cite{castrobleda2013}. O terceiro pilar, de natureza pedagógica, diz respeito ao modo como essas tecnologias são concebidas e aplicadas no ambiente educacional. A literatura aponta que o desenvolvimento de agentes inteligentes na educação deve estar alinhado a princípios pedagógicos sólidos, capazes de orientar o processo de ensino de forma crítica e inclusiva.
Do ponto de vista teórico, a noção de agente pedagógico dialoga diretamente com duas correntes centrais da psicologia da aprendizagem. A perspectiva sociocultural de \citeonline{vygotsky1978} situa a aprendizagem como um processo mediado, no qual o desenvolvimento de uma habilidade avança quando o aprendiz recebe apoio dentro de sua Zona de Desenvolvimento Proximal, o intervalo entre o que consegue realizar sozinho e o que consegue realizar com o auxílio de um mediador mais experiente.
Um agente pedagógico de escrita manuscrita, nessa leitura, não é apenas um classificador de traços: é um candidato a ocupar o papel de mediador, desde que seu feedback seja calibrado ao nível de desenvolvimento de cada aluno, e não uniforme para toda a turma. Já a Teoria Social Cognitiva de \citeonline{bandura2001} enfatiza a autoeficácia e a autorregulação como determinantes centrais da aprendizagem, o que reposiciona o papel do feedback automatizado: mais do que corrigir o traçado de uma letra, um agente pedagógico bem desenhado precisa contribuir para que o aluno construa confiança em sua própria capacidade de progredir na escrita.
Essa fundamentação, no entanto, não resolve sozinha um problema recorrente na literatura de agentes pedagógicos: a distância entre avanços tecnológicos e evidência sobre eficácia educacional. A revisão-guarda-chuva de \citeonline{schroeder2025}, que sintetizou dezessete revisões sistemáticas e meta-análises sobre agentes pedagógicos, aponta efeitos positivos, porém pequenos, sobre aprendizagem, motivação e variáveis afetivas, e conclui que a área ainda não sabe responder com precisão como desenhar um agente pedagógico eficaz para um contexto educacional específico.
No mesmo sentido, \citeonline{zhangs2024}, em revisão sistemática sobre agentes pedagógicos no ensino básico (K-12), constatam que a aparência e o papel do agente estão interligados, e defendem que o desenho do agente deveria se concentrar no valor pedagógico agregado, e não em atributos visuais ou tecnológicos por si sós. Essas conclusões deslocam o critério de sucesso adotado neste estudo: um agente pedagógico de escrita manuscrita não deve ser avaliado apenas pela acurácia do reconhecimento, mas pela qualidade pedagógica da mediação que proporciona, critério que a literatura técnica revisada nas seções anteriores, isoladamente, não é capaz de responder.
Essa lacuna se reflete também na literatura específica sobre o ensino da escrita manuscrita enquanto prática pedagógica, e não apenas como sinal a ser decodificado. \citeonline{heidig2011} já alertavam que a motivação e a aprendizagem dos estudantes não dependem apenas da presença de um agente, mas de como esse agente medeia a interação, argumento que reforça a necessidade de fundamentar tecnicamente e pedagogicamente cada escolha de design. Do lado da instrução da escrita à mão propriamente dita, a meta-análise de \citeonline{lopezescribano2022}, que reuniu 31 estudos com estudantes do jardim de infância ao sexto ano, mostra que programas estruturados de instrução da escrita produzem ganhos estatisticamente significativos de fluência em comparação a condições sem instrução específica.
Essa constatação é central para o presente artigo: qualquer agente pedagógico de escrita manuscrita movido por IA precisa ser concebido não apenas para reconhecer o que o aluno já escreveu, mas para apoiar deliberadamente o desenvolvimento da fluência e da automaticidade da escrita, competências que a literatura educacional já identifica como centrais, e que a literatura técnica revisada neste artigo, até aqui, trata de forma majoritariamente implícita.
De acordo com \citeonline{tirkes2018}, a formação de profissionais capazes de lidar com aprendizado profundo exige não apenas domínio técnico em matemática e programação, mas também um currículo coeso que integre esses saberes com práticas educacionais. Nesse contexto, metodologias como a Aprendizagem Baseada em Problemas (PBL) e a aprendizagem colaborativa têm se mostrado particularmente eficazes. A PBL propõe que o ensino seja centrado em problemas reais, que instiguem o estudante a buscar conhecimentos teóricos e práticos para resolvê-los \cite{castrobleda2013}. Já a aprendizagem colaborativa valoriza o trabalho em equipe e a troca entre estudantes de diferentes perfis, promovendo um ambiente de cooperação.
Nessas abordagens, o papel do professor passa a ser o de facilitador, atuando como mediador do conhecimento, o que inspira diretamente o desenho dos agentes pedagógicos, que também devem operar como guias do processo de aprendizagem. Para que os agentes pedagógicos funcionem com eficácia, é necessário enfrentar desafios técnicos relevantes. O primeiro diz respeito ao pré-processamento dos dados de escrita, que precisa normalizar sinais e imagens antes da análise. \citeonline{hamdan2023} destacam a aplicação de técnicas de correção de inclinação e compensação de iluminação para imagens, enquanto \citeonline{zhou2025} propõem o uso de filtros gaussianos e da Transformada Contínua de Wavelet (CWT) para sinais captados por sensores, convertendo-os em mapas tempo-frequência que facilitam a análise por redes convolucionais.
Outro desafio é a adaptação dos modelos a novos usuários, uma problemática conhecida como desvio de domínio. Quando um sistema treinado com dados de diversos indivíduos é aplicado a um novo perfil, pode apresentar desempenho inferior. Para contornar essa questão, técnicas de Adaptação de Domínio (DA) têm sido desenvolvidas, como o alinhamento de correlação (CORAL), que aproxima estatisticamente os dados dos diferentes domínios por meio de transformações lineares \cite{ott2022}. Abordagens como o Transporte Ótimo, que utilizam algoritmos como a Distância do Movimentador de Terra (EMD) ou o Transporte de Sinkhorn, buscam a melhor forma de sobrepor os dados de diferentes origens, garantindo maior personalização. Na prática pedagógica, é isso que permite que o sistema continue reconhecendo a letra de um aluno mesmo quando sua caligrafia muda ao longo do ano letivo, em vez de tratar cada novo estilo de escrita como um erro do usuário.
A escrita natural também impõe obstáculos, especialmente pela sua fluidez e ambiguidade. Técnicas como a rede Trajectory Upheaval Network (TUN) permitem a segmentação inteligente das sequências manuscritas, mesmo quando são escritas de forma contínua, destacando pontos de inflexão para diferenciar caracteres \cite{wangzhao2024}. Para lidar com ambiguidades entre traços semelhantes, como os que diferenciam ``O'' e ``0'', tem sido adotada a fusão de características, combinando dados espaciais e temporais por meio de abordagens como o Cooperation-Competition Learning (CCL), em que diferentes redes neurais trabalham em paralelo e sua confiança relativa determina a contribuição final de cada uma \cite{wangzhao2024}. Na prática, são essas técnicas que evitam que o sistema confunda letras parecidas e aponte como erro algo que o aluno escreveu corretamente.
Por fim, o treinamento dos sistemas em ambientes com poucos dados rotulados é uma das dificuldades mais recorrentes. O aprendizado semi supervisionado tem sido apontado como solução promissora. Técnicas como pseudo-labeling utilizam modelos iniciais para gerar rótulos para dados não anotados, integrando-os progressivamente ao conjunto de treinamento conforme sua confiabilidade \cite{kiss2024}. A função de perda SoftCTC amplia essa proposta ao permitir que o modelo treine com múltiplas hipóteses de transcrição simultaneamente, aumentando sua tolerância a erros e ambiguidades. Do ponto de vista pedagógico, é esse tipo de técnica que possibilita treinar o agente mesmo em escolas que não dispõem de um grande acervo de cadernos já corrigidos e anotados por professores.
\section{Desafios e direções futuras}
O caminho para a implementação generalizada de agentes pedagógicos de IA para a escrita é promissor, mas ainda existem obstáculos significativos a serem superados. Os desafios atualmente enfrentados pelos agentes pedagógicos inteligentes indicam caminhos importantes para os avanços futuros na área. À medida que a pesquisa se aprofunda, algumas direções se destacam como especialmente promissoras e alinhadas às tendências tecnológicas e pedagógicas.
Uma dessas direções envolve o desenvolvimento de agentes multimodais. Esses sistemas poderiam reunir, de forma integrada, informações obtidas por sensores inerciais, câmeras que captam o movimento das mãos e até sinais de voz emitidos durante a escrita. A combinação de dados visuais, cinestésicos e auditivos favorece uma leitura mais completa da interação do estudante com o ambiente de aprendizagem. Essa abordagem está alinhada às ideias de \citeonline{castrobleda2013}, que propõem sistemas capazes de se adaptar com mais sensibilidade às nuances do comportamento humano.
Também se destaca a aplicação de aprendizado por reforço na geração de feedback. Em vez de apenas detectar erros ou classificar a escrita, os agentes poderiam ser treinados para oferecer dicas assertivas no momento adequado, com base em simulações envolvendo perfis diversos de estudantes. Isso permitiria que o agente ``aprendesse a ensinar'', ajustando suas estratégias a partir da resposta dos alunos ao longo do tempo. \citeonline{hamdan2023} mostram como esse tipo de adaptação pode tornar o sistema mais centrado no estudante e promover um avanço significativo no processo de aprendizagem.
As tecnologias de realidade aumentada (AR) e realidade virtual (VR) também abrem espaço para experiências mais imersivas. Com o uso de sensores flexíveis ou canetas inteligentes, os estudantes podem interagir em ambientes virtuais guiados por agentes pedagógicos, com projeções que mostram, em tempo real, como realizar o traçado correto de símbolos e letras. \citeonline{wangzhao2024} sugerem que essas projeções facilitam a imitação e o aprendizado. Além disso, ambientes gamificados descritos por \citeonline{liu2022} tornam o processo mais lúdico, fortalecendo o engajamento e a motivação que são fatores cruciais na educação.
Embora o foco atual esteja na escrita textual, as aplicações dessas tecnologias podem ir muito além. A mesma infraestrutura pode ser utilizada para apoiar atividades que exigem coordenação motora fina, como desenhar figuras geométricas, resolver expressões matemáticas à mão ou criar representações visuais de conceitos. Com isso, o agente pedagógico deixaria de ser apenas um especialista em caligrafia para se tornar um aliado cognitivo versátil, atuando em múltiplas disciplinas. Outro caminho interessante está na utilização de modelos de fundação.
Esses sistemas, treinados com grandes volumes de dados manuscritos, permitem ajustes com apenas alguns exemplos adicionais. \citeonline{kiss2024} apontam como esses modelos que são inspirados em arquiteturas como o GPT-3 podem ser pré-treinados com dados variados, incluindo imagens e sinais temporais capturados por sensores. A partir dessa base, é possível adaptar o agente ao estilo específico de escrita de cada estudante, promovendo personalização eficiente e acessível. Para o professor, isso significa que o mesmo sistema pode ser ajustado à caligrafia de um aluno específico sem precisar ser treinado do zero para cada turma.
Além dessas possibilidades, é necessário considerar os principais obstáculos técnicos para a evolução da área. Um deles é o desvio de domínio, que ocorre quando um modelo treinado com dados de um grupo específico de usuários apresenta desempenho inferior ao lidar com perfis novos. A diversidade de estilos caligráficos, a velocidade de escrita e outras particularidades pessoais afetam a precisão dos sistemas. Métodos de Adaptação de Domínio, como o transporte ótimo e o alinhamento estatístico conhecido como CORAL \cite{ott2022}, oferecem alternativas para lidar com esse problema.
Outro desafio significativo está na escassez de dados rotulados, especialmente em contextos educacionais onde há pouca infraestrutura para coleta e anotação de grandes volumes de escrita. Abordagens como o aprendizado semi-supervisionado, que utiliza técnicas como pseudo-labeling e funções de perda como SoftCTC, tornam possível o treinamento de modelos mesmo em ambientes com dados limitados \cite{kiss2024}.
Além disso, os agentes multimodais oferecem uma alternativa para lidar com falhas na leitura dos dados, ao integrar fontes como movimentos corporais, expressões faciais, voz e pressão da escrita. Esse cruzamento de informações aumenta tanto a acurácia quanto a compreensão do contexto, tornando a análise mais rica e próxima da realidade.
No campo das tecnologias imersivas, a escrita pode deixar de ser uma atividade limitada ao papel e ganhar dimensões interativas. \citeonline{wangzhao2024} destacam o uso de projeções em tempo real para guiar o traçado de letras, enquanto \citeonline{liu2022} defendem que ambientes gamificados em VR fortalecem a motivação e a aprendizagem.
Essas ferramentas não se limitam à caligrafia. Estudantes de diferentes áreas como engenharia ou artes, podem contar com agentes pedagógicos como suporte na criação de diagramas técnicos ou ilustrações mais precisas. Trata-se de expandir o conceito de escrita para múltiplas formas de expressão gráfica. Por fim, o desenvolvimento de modelos de fundação oferece um novo horizonte.
Como mostram \citeonline{kiss2024}, esses modelos podem ser treinados com dados diversos e, depois, personalizados com poucos exemplos para atender a tarefas específicas. Essa abordagem não só melhora a eficiência dos sistemas como também amplia a capacidade de adaptação a contextos reais de aprendizagem. Tudo isso revela um campo dinâmico, no qual a inovação tecnológica precisa estar em sintonia com os princípios pedagógicos. O foco deixa de ser apenas a precisão dos sistemas e passa a ser o desenvolvimento de ferramentas capazes de compreender e apoiar os estudantes em sua jornada de forma personalizada, para os auxiliar da melhor forma possível.
\section{Conclusão}
A literatura para o desenvolvimento de agentes pedagógicos inteligentes para atividades manuscritas já revela o quanto essa área avançou e o quanto ainda promete avançar cada vez mais. O encontro entre tecnologia de ponta como sensores mais acessíveis, algoritmos avançados e modelos de linguagem poderosos e uma compreensão cada vez mais cuidadosa dos processos de aprendizagem abriu espaço para soluções que, há pouco tempo, pareciam distantes da realidade escolar.
Ao revisarmos a literatura, fica claro que o campo passou por uma evolução significativa. O foco deixou de ser apenas o reconhecimento isolado de caracteres para abraçar a complexidade da escrita humana, com suas particularidades e sequências variáveis. As BiLSTMs lançaram os alicerces desse caminho, mas hoje, arquiteturas como os Transformers mostram resultados mais expressivos em eficiência e contextualização, conforme apontado por \citeonline{hamdan2023}. As inovações em sensores flexíveis e unidades de medição inercial (IMUs) também estão contribuindo para uma coleta de dados mais natural, rica em detalhes que revelam como os estudantes interagem com o ato de escrever \cite{liu2022, wangzhao2024}.
Apesar da importância dos avanços técnicos, o impacto mais valioso acontece dentro da sala de aula. É ali que os agentes precisam se adaptar a perfis diversos, ritmos diferentes e contextos imprevisíveis. A personalização se torna possível com estratégias como a adaptação de domínio, que ajusta a experiência conforme as necessidades de cada estudante \cite{ott2022}. A interação ganha fluidez com recursos como a segmentação inteligente de movimentos, que preserva o ritmo da escrita sem gerar interrupções \cite{wangzhao2024}. E a escalabilidade dos sistemas se viabiliza com métodos como o aprendizado semi-supervisionado, que reduzem a dependência de grandes volumes de dados anotados manualmente \cite{kiss2024}.
Cabe, por fim, uma ressalva sobre o próprio enquadramento adotado por este estudo. A ideia de ``eficiência'' aplicada à educação não é neutra: parte da literatura educacional a critica justamente por correr o risco de minorar o trabalho pedagógico docente, tratando a aprendizagem como um processo a ser otimizado por métricas técnicas, quando ela é, antes de tudo, uma relação mediada por pessoas.
Reconhecer essa tensão é importante para que a revisão aqui apresentada não seja lida como uma defesa da automação do ensino da escrita, mas como um mapeamento de tecnologias que só têm valor pedagógico na medida em que ampliam e não substituem a mediação do professor. Um agente pedagógico de escrita manuscrita, nesse sentido, é mais bem compreendido como um instrumento a serviço do trabalho docente, e não como um substituto dele.
Investir nesse campo é abrir espaço para uma educação mais justa, acessível e centrada no estudante. É garantir que as futuras gerações tenham acesso não apenas ao conhecimento, mas a ferramentas que as ajudem a aprender de forma autônoma, criativa e confiante num mundo cada vez mais tecnológico, sem deixar de lado o que há de mais humano na aprendizagem. Pois fica cada vez mais evidente que o futuro que envolve os agentes pedagógicos demonstra que estes serão muito mais do que ferramentas de avaliação automática, pois com sua capacidade de se adaptar ao aprendizado para o perfil do aluno, faz com que ele se torne um aliado importante para os estudantes pela sua adaptabilidade.
% ---------------------------------------------------------------------------
% Pós-textuais
% ---------------------------------------------------------------------------
\postextual
\bibliography{referencias}
\end{document}

View File

@@ -0,0 +1,166 @@
@article{abbas2021,
author = {Abbas, F. and others},
title = {Texture feature column scheme for single- and multi-script writer identification},
journal = {IET Biometrics},
volume = {10},
pages = {179--193},
year = {2021}
}
@article{bandura2001,
author = {Bandura, A.},
title = {Social cognitive theory: an agentic perspective},
journal = {Annual Review of Psychology},
volume = {52},
pages = {1--26},
year = {2001}
}
@inproceedings{castrobleda2013,
author = {Castro-Bleda, M. J. and others},
title = {Collaborative learning in international teams on Technologies to Reduce the Access Barrier in Human Computer Interaction (TrabHCI)},
booktitle = {Erasmus Intensive Programme},
year = {2013}
}
@inproceedings{das2016,
author = {Das, P. and Dasgupta, T. and Bhattacharya, S.},
title = {A Novel Scheme for Bengali Handwriting Recognition Based on Morphological Operations with Adaptive Auto-Generated Structuring Elements},
booktitle = {2nd International Conference on Control, Instrumentation, Energy \& Communication (CIEC)},
address = {Kolkata},
publisher = {IEEE},
pages = {211--215},
year = {2016}
}
@article{hamdan2023,
author = {Hamdan, M. and others},
title = {Refocus attention span networks for handwriting line recognition},
journal = {International Journal on Document Analysis and Recognition (IJDAR)},
volume = {26},
pages = {131--147},
year = {2023}
}
@article{heidig2011,
author = {Heidig, S. and Clarebout, G.},
title = {Do pedagogical agents make a difference to student motivation and learning?},
journal = {Educational Research Review},
volume = {6},
number = {1},
pages = {27--54},
year = {2011}
}
@article{kiss2024,
author = {Ki{\v{s}}{\v{s}}, M. and others},
title = {SoftCTC---semi-supervised learning for text recognition using soft pseudo-labels},
journal = {International Journal on Document Analysis and Recognition (IJDAR)},
volume = {27},
pages = {177--193},
year = {2024}
}
@article{liu2022,
author = {Liu, Y. and others},
title = {Machine-Learning Assisted Handwriting Recognition Using Graphene Oxide-Based Hydrogel},
journal = {ACS Applied Materials \& Interfaces},
volume = {14},
pages = {54276--54286},
year = {2022}
}
@article{lopezescribano2022,
author = {L{\'o}pez-Escribano, C. and Mart{\'i}n-Babarro, J. and P{\'e}rez-L{\'o}pez, R.},
title = {Promoting handwriting fluency for preschool and elementary-age students: meta-analysis and meta-synthesis of research from 2000 to 2020},
journal = {Frontiers in Psychology},
volume = {13},
pages = {841573},
year = {2022}
}
@inproceedings{ott2022,
author = {Ott, F. and others},
title = {Domain Adaptation for Time-Series Classification to Mitigate Covariate Shift},
booktitle = {30th ACM International Conference on Multimedia (MM '22)},
address = {Lisboa},
publisher = {ACM},
pages = {1--15},
year = {2022}
}
@inproceedings{roy2014,
author = {Roy, P. P. and Chherawala, Y. and Cheriet, M.},
title = {Deep-Belief-Network based Rescoring for Handwritten Word Recognition},
booktitle = {14th International Conference on Frontiers in Handwriting Recognition (ICFHR)},
address = {Creta},
publisher = {IEEE},
pages = {506--511},
year = {2014}
}
@article{schroeder2025,
author = {Schroeder, N. L. and Davis, R. O. and Yang, E.},
title = {Designing and learning with pedagogical agents: an umbrella review},
journal = {Journal of Educational Computing Research},
volume = {62},
number = {8},
pages = {1907--1936},
year = {2025}
}
@inproceedings{tirkes2018,
author = {Tirke{\c{s}}, G. and others},
title = {An Undergraduate Curriculum for Deep Learning},
booktitle = {3rd International Conference on Computer Science and Engineering (UBMK'18)},
address = {Sarajevo},
publisher = {IEEE},
pages = {604--609},
year = {2018}
}
@book{vygotsky1978,
author = {Vygotsky, L. S.},
title = {Mind in society: the development of higher psychological processes},
address = {Cambridge, MA},
publisher = {Harvard University Press},
year = {1978}
}
@article{wangzhao2024,
author = {Wang, Y. and Zhao, Y.},
title = {Handwriting Recognition Under Natural Writing Habits Based on a Low-Cost Inertial Sensor},
journal = {IEEE Sensors Journal},
volume = {24},
number = {1},
pages = {995--1005},
year = {2024}
}
@article{zhangs2024,
author = {Zhang, S. and others},
title = {Pedagogical agent design for K-12 education: a systematic review},
journal = {Computers \& Education},
volume = {223},
pages = {105165},
year = {2024}
}
@article{zhangx2023,
author = {Zhang, X. and others},
title = {Highly sensitive and high strength hydrogel sensors with interpenetrating network structure for strain-stimulus detection},
journal = {Sensors and Actuators: A. Physical},
volume = {357},
pages = {114388},
year = {2023}
}
@article{zhou2025,
author = {Zhou, X. and others},
title = {Flexible Strain Sensors With Multifusion Algorithm Models for Handwriting Recognition},
journal = {IEEE Sensors Journal},
volume = {25},
number = {7},
pages = {12372--12380},
year = {2025}
}

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
# O projeto usa apenas a biblioteca padrão do Python (>=3.10).
# Nenhuma dependência necessária para o modo atual de uso.
# (Opcional, para extensão futura)
# spacy>=3.7 # tokenização/POS pt-BR
# language-tool-off # correção gramatical via ponteiro