# Spec: Write Pipeline Code Fixes — enable_llm_resolution + strength reinforcement

> Origem: Auditoria de documentação vs código (2026-03-19)
> Budget: ≤ 4 arquivos

## Objetivo

Corrigir dois comportamentos do write pipeline onde o código não entrega o que o `MemoryConfig` promete: (1) `enable_llm_resolution` é dead code e (2) strength de relações nunca é reforçado incrementalmente.

## Contexto

Encontrados durante auditoria exaustiva docs vs código:

**Bug 1 — `enable_llm_resolution` dead code:** `MemoryConfig.enable_llm_resolution` (default `True`) existe mas nunca é checado em `entity_resolution.py`. O dev configura `enable_llm_resolution=False` achando que desliga LLM na entity resolution, mas não faz nada. Bug de contrato.

**Bug 2 — Strength reinforcement não funciona:** `track_entity_relationship()` (upsert.py L505) faz `+0.05` incremental, mas o pipeline chama `upsert_relations()` que usa `GREATEST(existing, new)` — pega o máximo, não incrementa. Relações que aparecem 50 vezes têm o mesmo strength de uma que apareceu 2 vezes.

## Definition of Done

1. **`enable_llm_resolution=False` desliga LLM na entity resolution** — Quando `config.enable_llm_resolution` é `False`, a Phase 3 (LLM fallback) em `_resolve_single()` é pulada: candidatos ambíguos (similarity 0.50–0.85) criam nova entidade ao invés de chamar LLM.
2. **Strength é reforçado incrementalmente no pipeline** — `upsert_relations()` usa `LEAST(1.0, memory_entity_relationships.strength + 0.1)` no ON CONFLICT UPDATE ao invés de `GREATEST(existing, new)`. Relações repetidas aumentam strength até 1.0.
3. **Testes cobrem ambos os fixes** — (a) Test com `enable_llm_resolution=False` verifica que LLM não é chamado e entidade nova é criada. (b) Test verifica que ON CONFLICT SET usa expressão incremental.
4. **Testes existentes continuam passando.**

## Escopo

- Modificar `src/arandu/write/entity_resolution.py`: adicionar check `if not config.enable_llm_resolution` em `_resolve_single()`.
- Modificar `src/arandu/write/upsert.py`: trocar `GREATEST(...)` por `LEAST(1.0, strength + 0.1)` em `upsert_relations()`.
- Modificar/criar testes para cobrir os dois fixes.

## Anti-escopo

- **Não** alterar `track_entity_relationship()` — função separada, não chamada pelo pipeline.
- **Não** alterar prompts de extração.
- **Não** alterar pipeline.py.
- **Não** alterar models.py.
- **Não** alterar docs (já sendo corrigidos separadamente).

## Decisões Técnicas

### DT1: enable_llm_resolution — skip Phase 3

A entity resolution em `_resolve_single()` tem 3 fases:
1. Exact match
2. Fuzzy match (retorna candidatos)
3. LLM fallback (quando candidatos ambíguos: 0.50–0.85)

Quando `enable_llm_resolution=False`, a Phase 3 é pulada. Candidatos ambíguos são tratados como "sem match" → cria nova entidade. Isso é conservador (prefere duplicatas a merges errados sem LLM).

A função precisa receber `config` como parâmetro. Verificar se já recebe ou se precisa ser adicionado.

### DT2: Strength incremental — `+ 0.1` com cap em 1.0

Trocar:
```sql
GREATEST(memory_entity_relationships.strength, EXCLUDED.strength)
```
Por:
```sql
LEAST(1.0, memory_entity_relationships.strength + 0.1)
```

Increment de `0.1` (não `0.05` como em `track_entity_relationship`) porque o pipeline roda menos frequentemente que track manual. Cap em `1.0` via `LEAST`.

## Applicable Patterns

- **Data Access**: Query pattern consistente com o projeto.
- **Error Handling**: Skip de LLM é fail-safe (cria nova entidade ao invés de falhar).
- **Testing**: Mock-based, sem DB.

## Riscos

| Risco | Mitigação |
|-------|-----------|
| `_resolve_single()` não recebe `config` | Verificar assinatura. Se não recebe, adicionar parâmetro (propagação via `resolve_entities()`). |
| Strength `+0.1` é muito agressivo | Valor pode ser ajustado depois. `0.1` é razoável para pipeline-level reinforcement. |
