> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pictor.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Entregar o relatório de ronda

> A folha de verificação que o cliente final recebe — pronta para virar PDF.

A ronda virtual varre as câmeras de um local em cadência, avalia cada uma por IA
e registra o resultado. O relatório é o documento que sai disso — e é o que o seu
cliente compra.

## Achar o que tem relatório

```bash theme={null}
curl -s -H "Authorization: Bearer $PICTOR_KEY" \
  "$PICTOR_API/api/patrols/sheets?since=2026-09-01T00:00:00Z&limit=50"
```

```json theme={null}
[
  {
    "kind": "alarm",
    "ref": "9f52…",
    "started_at":  "2026-09-03T03:12:04Z",
    "finished_at": "2026-09-03T03:13:41Z",
    "cameras_total": 11,
    "cameras_completed": 11,
    "cameras_with_frame": 10,
    "cameras_pending": 0,
    "trigger_code": "1130",
    "trigger_description": "Alarme de intrusão",
    "account": "4021"
  }
]
```

Sem `since`, a janela padrão é de **2 dias**.

<Warning>
  **`finished_at: null` significa que a varredura ainda não acabou** — há câmera
  pendente. Não entregue o relatório nesse estado: ele vai afirmar menos do que a
  ronda vai apurar. Espere `cameras_pending: 0`.
</Warning>

## Pegar o relatório

`ref` é o identificador do evento. Use-o na rota do relatório:

```bash theme={null}
curl -s -H "Authorization: Bearer $PICTOR_KEY" \
  "$PICTOR_API/api/patrols/sheets/alarm/9f52…"
```

A resposta tem quatro partes: `source`, `header`, `items` (a folha) e `cameras`.

## A folha: oito perguntas

`items` é o checklist, na ordem, com `number` e `question`:

| # | Pergunta                                         |
| - | ------------------------------------------------ |
| 1 | QUAL O LOCAL MONITORADO?                         |
| 2 | CFTV ESTÁ ON-LINE?                               |
| 3 | HÁ ALGUM CANAL SEM IMAGEM, OU COM INTERFERÊNCIA? |
| 4 | HÁ ALGUMA CÂMERA COM OBSTRUÇÃO DE IMAGEM?        |
| 5 | O LOCAL MONITORADO ESTÁ SEGURO?                  |
| 6 | EQUIPAMENTO ESTÁ GRAVANDO 30 DIAS?               |
| 7 | Informar SAs que estão abertas para manutenção:  |
| 8 | EVIDENCIAS DE RONDA                              |

Cada item traz um `state`, e **os três estados são diferentes de propósito**:

| `state`        | O que quer dizer                                     | Como imprimir                         |
| -------------- | ---------------------------------------------------- | ------------------------------------- |
| `answered`     | foi medido; a resposta está em `answer`              | a resposta                            |
| `not_measured` | não deu para medir nesta varredura                   | a frase de `reason`, nunca um "Não"   |
| `phase_2`      | esta pergunta ainda não é respondida automaticamente | deixe em branco para alguém preencher |

<Warning>
  **Nunca imprima `not_measured` como "Não".** "Não há câmera sem imagem" e "não
  foi possível verificar" são afirmações diferentes, e a primeira é uma que você
  não pode fazer. Um relatório que afirma o que não mediu é pior que um relatório
  com uma linha em branco — ele parece completo.
</Warning>

```python theme={null}
def linha_do_relatorio(item: dict) -> str:
    if item["state"] == "answered":
        return item["answer"]
    if item["state"] == "not_measured":
        return item.get("reason") or "Não verificado nesta ronda."
    return ""  # phase_2: espaço para preenchimento manual
```

## As câmeras

`cameras` traz uma linha por câmera da varredura:

```json theme={null}
{
  "external_id": "G87232574",
  "name": "Guarita - entrada",
  "situation": "with_frame",
  "situation_label": "Com imagem",
  "verdict": "sem_ocorrencia",
  "verdict_label": "Sem ocorrência",
  "reason_code": null,
  "reason_label": null
}
```

Use `situation_label`, `verdict_label` e `reason_label` na folha: são as frases
escritas para o operador. Os campos crus (`situation`, `verdict`, `reason_code`)
são para a sua lógica.

`header` fecha a conta: `cameras_total`, `cameras_with_frame`,
`cameras_without_frame` (sem quadro **por culpa do gravador** — é o que a
pergunta 3 conta) e as não avaliadas.

<Tip>
  A distinção entre "sem imagem por culpa do gravador" e "não avaliada" é o que
  mantém a pergunta 3 honesta. Uma câmera que a plataforma não conseguiu amostrar
  **não** entra como canal sem imagem: seria acusar o cliente de um defeito nosso.
</Tip>

## Virar PDF

```python theme={null}
import httpx

def montar_relatorio(chave: str, base: str, alarm_id: str) -> dict:
    cabecalho = {"Authorization": f"Bearer {chave}"}
    with httpx.Client(base_url=base, headers=cabecalho, timeout=60) as http:
        r = http.get(f"/api/patrols/sheets/alarm/{alarm_id}")
        if r.status_code == 404:
            # Nenhuma verificação registrada: não há ronda para relatar.
            # Um relatório vazio afirmaria que ninguém olhou, com a aparência
            # de que alguém olhou.
            return {}
        r.raise_for_status()
        folha = r.json()

        # AS FOTOS: baixe AGORA. Os endereços são assinados e vencem.
        for camera in folha["cameras"]:
            foto = (camera.get("best_frame") or {}).get("snapshot_url")
            if foto:
                camera["imagem_bytes"] = http.get(foto).content
        return folha
```

<Warning>
  Baixe as imagens no mesmo momento em que lê o relatório. Se o seu gerador de PDF
  embute o link em vez dos bytes, o documento abre hoje e falha na primeira vez que
  alguém precisa dele de verdade.
</Warning>

## O relatório impresso

Se você gera o PDF pelo navegador, cuide de duas coisas que sempre escapam:

* **A moldura do seu sistema não vai para o papel.** Barra, menu e cabeçalho da
  sua aplicação precisam sair no `@media print` — senão o cliente recebe uma folha
  com a sua navegação em cima.
* **As imagens precisam estar carregadas antes de imprimir.** Espere o download
  terminar; o `window.print()` disparado cedo produz um relatório com retângulos
  vazios onde estavam as evidências.
