# Mídia adversa

<https://unifokal.com/docs/modulos/midia-adversa>

## Mídia adversa

O módulo `midia_adversa` confere o **nome lido do documento** contra um **corpus aberto de notícias** (negative news) mantido e atualizado por nós, construído sobre o GDELT Project (dados do Global Knowledge Graph, uso comercial livre, com atribuição). Ele exige **Verificação de Identidade + Face Match + Liveness** no mesmo flow: o nome consultado vem do documento apresentado por quem está vivo na frente da câmera, nunca de um nome digitado. O widget não ganha nenhum passo novo.

! **Este módulo nunca reprova sozinho.** Notícia não publica CPF, então todo casamento é por **nome**, e homônimo é o caso comum. Um hit forte leva a verificação para `review` com a evidência no webhook (fonte, data e link), para a sua análise humana decidir. Nunca a `denied` automático: uma coincidência de nome não pode destruir o cadastro de um inocente.

**O grau de confiança do casamento vem sempre declarado.** Cada hit carrega `name_match` (`strong` ou `possible`) e a `similarity` de 0 a 100; a resposta agrega o grau mais forte no topo. Um **hit forte** vem com a evidência completa: a fonte, a **data de publicação** e o **link da notícia**, que é o que o seu revisor abre para julgar. Um **possível homônimo** (zona cinzenta) apenas sinaliza: sai o grau, a similaridade e uma referência opaca, **sem link, sem veículo e sem data**, porque provável terceiro não ganha dossiê. Divergência de data de nascimento, quando existe nos dois lados, rebaixa o grau e fica marcada em `birth_date_mismatch`, nunca esconde o hit.

**A lista de hits tem teto de 20, e o corte vem declarado.** Homônimo comum produz dezenas de notícias, e um payload sem limite viraria incidente de custo e de leitura do seu lado. Passando de 20, `hits` sai cortado e `hits_truncated` vem `true`. O que continua verdadeiro no corte: `hits_total` declara o número **real** de hits, `aggregates` é calculado sobre o conjunto **completo**, e a ordenação é determinística **antes** do corte (hit forte na frente do possível, depois maior similaridade). Sem essa ordem, um homônimo com 50 hits fracos empurraria para fora da lista justamente o único hit forte, que é o que o seu revisor precisa ver primeiro.

Cada resposta carimba a fonte que a sustentou em `coverage` e a idade da base em `dataset_versions`. Se a base estiver vencida, ou ainda sem carga, o módulo devolve `pending` com `dataset_stale` e a verificação vai para `review`: o resultado é **indeterminado**, nós não afirmamos "nada consta" sobre base que não conseguimos atualizar, e esse caso **não é cobrado**.

```
// midia_adversa: nada consta (base fresca)
{ "module": "midia_adversa", "passed": true, "outcome": "approved", "score": 95,
  "data": { "has_adverse_media": false, "flagged": false, "name_match": "none", "reason": "clear",
            "hits": [], "hits_total": 0, "hits_truncated": false,
            "aggregates": { "has_adverse_media": false, "possible_matches": 0, "strongest_match": null },
            "coverage": ["gdelt_gkg"], "coverage_degraded": [],
            "dataset_versions": { "gdelt_gkg": { "ingested_at": "2026-08-27T03:00:00Z", "age_hours": 4,
                                                 "stale": false, "disabled": false } },
            "name_source": "ocr" } }

// midia_adversa: hit FORTE -> review humano com a evidência (fonte + data + link)
{ "module": "midia_adversa", "passed": false, "outcome": "failed", "score": 40,
  "data": { "has_adverse_media": true, "flagged": true, "name_match": "strong",
            "reason": "adverse_media_strong",
            "hits": [ { "source": "gdelt_gkg", "name_match": "strong", "similarity": 94,
                        "url": "https://noticia.example.com/materia",
                        "outlet": "noticia.example.com", "published_at": "2025-11-20",
                        "birth_date_mismatch": false, "entry_ref": "gdelt_gkg:e_7a31d2" } ],
            "hits_total": 1,
            "aggregates": { "has_adverse_media": true, "possible_matches": 0,
                            "strongest_match": "strong" } } }

// midia_adversa: possível homônimo -> aprova com sinalização, SEM link e SEM data (minimização)
{ "module": "midia_adversa", "passed": true, "outcome": "approved", "score": 70,
  "data": { "has_adverse_media": false, "flagged": true, "name_match": "possible",
            "reason": "possible_match",
            "hits": [ { "source": "gdelt_gkg", "name_match": "possible", "similarity": 78,
                        "url": null, "outlet": null, "published_at": null,
                        "birth_date_mismatch": false, "entry_ref": "gdelt_gkg:e_2c19aa" } ],
            "hits_total": 1 } }

// midia_adversa: base sem carga/vencida -> pending (indeterminado), review, e NÃO cobrado
{ "module": "midia_adversa", "passed": null, "outcome": "pending", "score": 0,
  "data": { "reason": "dataset_stale", "coverage": [], "coverage_degraded": ["gdelt_gkg"] } }
```

Em sandbox nada é consultado: o desfecho vem do sufixo do documento. `02` devolve um hit forte com a evidência completa, `33` um possível homônimo que aprova com sinalização e `01` o caminho de base vencida (pendente, indeterminado, não cobrado). Flows com este módulo não permitem renovação de sessão pelo widget: a renovação é sempre pelo seu servidor.
