# Spec: Multi-pass Fact Deduplication via Subject-Centric Extraction

> Generated via /vibeflow:gen-spec on 2026-03-22
> PRD: discussion (no formal PRD — emerged from benchmark analysis)
> Budget: ≤ 4 files

## Objective

Reduzir ~71 false positives do multi-pass eliminando facts reformulados do ponto de vista de entidades secundárias, sem perder recall — o graph signal já resolve a navegação cross-entity.

## Context

O benchmark GT mostra que o multi-pass produz duplicatas sistematicamente:
- `"Carlos lives in Curitiba"` (subject: Carlos) + `"Curitiba is where Carlos lives"` (subject: Curitiba)
- Mesma informação, subjects diferentes. O segundo é redundante porque a relação `Carlos → lives_in → Curitiba` já conecta os dois no graph.

O multi-pass extrai facts por entidade via `FACT_EXTRACTION_PROMPT`. Cada batch recebe uma lista de entidades e extrai "ALL atomic facts about each entity". O LLM interpreta isso como "pra cada entidade do batch, descreva tudo que ela faz/é na mensagem" — inclusive reformulando informação que já foi coberta do ponto de vista de outra entidade.

A correção é no prompt: instruir que cada fact deve ser extraído **do ponto de vista do subject principal da informação**. "Carlos mora em Curitiba" é sobre Carlos, não sobre Curitiba. O graph cuida da ponte.

## Definition of Done

1. **[ ] `FACT_EXTRACTION_PROMPT` atualizado** — Inclui regra: "Extract facts from the perspective of the primary subject only. Do not reformulate the same information from a secondary entity's perspective — relationships handle cross-entity navigation."
2. **[ ] `EXTRACTION_SYSTEM_PROMPT` atualizado** — Mesma regra no single-pass (consistência, embora single-pass sofra menos com isso).
3. **[ ] Testes passam** — Todos os 361+ testes existentes passam sem regressão.
4. **[ ] Docs atualizados EN + PT-BR** — Seção de extraction modes documenta o comportamento subject-centric.

## Scope

### 1. `src/arandu/write/extract.py` — Prompt updates

**`FACT_EXTRACTION_PROMPT`** — Adicionar regra após as existentes:

```
- DEDUPLICATION: Extract each fact from the perspective of its PRIMARY subject only.
  The primary subject is the entity that the fact is fundamentally "about".
  Example: "Carlos lives in Curitiba" → subject is Carlos (it's about where Carlos lives).
  Do NOT also create "Curitiba is where Carlos lives" with subject Curitiba.
  The relationship (Carlos → lives_in → Curitiba) already connects the entities in the graph.
  Rule of thumb: if removing the secondary entity changes the meaning, the fact is about the primary entity.
```

**`EXTRACTION_SYSTEM_PROMPT`** — Adicionar regra equivalente na seção FACTS do single-pass.

### 2. `docs/en/concepts/write-pipeline.md` — Doc update

Na seção "Two Modes", adicionar nota sobre subject-centric extraction no multi-pass.

### 3. `docs/pt-BR/concepts/write-pipeline.md` — Doc update PT-BR

Espelhar a mudança em português.

## Anti-scope

- **Dedup por embedding pós-extração** — Não implementar. O prompt resolve o 80% case.
- **Mudança no modelo de dados** — Não adicionar `entity_keys[]` ou tabela associativa.
- **Mudança no RELATION_EXTRACTION_PROMPT** — Não tocar. Relações já são extraídas corretamente.
- **Mudança no ENTITY_SCAN_PROMPT** — Não tocar. Entidades continuam sendo todas extraídas.
- **Mudança na reconciliação** — Não tocar. Reconciliação intra-message fica pra depois.
- **Reflexion prompt** — Não tocar (é quality review, não extração).

## Technical Decisions

### TD-1: Prompt-only fix, sem dedup algorítmico
**Trade-off:** Prompt depende do LLM seguir a instrução. Não é 100% garantido.
**Decisão:** O benchmark mostra que o LLM segue instruções de formato consistentemente (JSON compliance > 99%). Uma regra clara de dedup tem alta chance de compliance. Se não funcionar, dedup algorítmico vira v1.

### TD-2: Mesma regra no single-pass por consistência
**Trade-off:** Single-pass sofre menos com duplicação (1 chamada, sem batches).
**Decisão:** Aplicar mesmo assim — reduz false positives marginais e mantém comportamento consistente.

## Applicable Patterns

- **LLM Interaction** — Prompt engineering com regras claras e exemplos concretos.
- **Error Handling** — Nenhuma mudança de código runtime, só prompts.
- **Testing** — Testes existentes devem passar sem mudança (prompts não afetam mocks).

## Risks

1. **LLM ignora a regra** — Mitigação: regra com exemplo concreto. Se não funcionar, iteramos no wording ou adicionamos dedup algorítmico.
2. **Perda de recall em edge cases** — Se uma informação SÓ faz sentido do ponto de vista de Curitiba (ex: "Curitiba has a population of 2M"), o LLM deveria extrair com subject Curitiba normalmente. A regra diz "primary subject", não "nunca use entidades secundárias".
