From 7e2dd3a4cb845cdde37f86242084b40e80b05315 Mon Sep 17 00:00:00 2001
From: Francisco Solla
Date: Sat, 19 Sep 2026 12:05:35 -0300
Subject: [PATCH] =?UTF-8?q?feat(C200):=20separar=20falas=20por=20pessoa=20?=
=?UTF-8?q?nas=20grava=C3=A7=C3=B5es=20enviadas=20(Closes=20#1167)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/changelog/2026-09-19-c200.md | 1 +
.../plans/acervo-separacao-por-pessoa-impl.md | 239 +
docs/plans/gates-gravacoes-predicados.md | 1 +
.../acervo/gravacoes/[id]/falantes/route.ts | 32 +
.../acervo/gravacoes/[id]/page.tsx | 2 +
.../comunicacao/acervo/gravacoes/types.ts | 5 +
.../(app)/comunicacao/acervo/page.tsx | 35 +-
.../(campaign)/campanha/actions/recording.ts | 85 +
src/collections/Recording.ts | 70 +-
src/collections/RecordingSegment.ts | 13 +-
.../recording/RecordingDetailPlayer.tsx | 104 +-
.../recording/RecordingPersonFilter.tsx | 87 +
.../recording/RecordingResultCard.tsx | 17 +-
.../recording/RecordingSpeakerDialog.tsx | 174 +
.../recording/RecordingSpeakerTranscript.tsx | 103 +
.../RecordingTranscriptSegmentButton.tsx | 51 +
src/lib/campaignPaths.ts | 4 +
src/lib/recording.ts | 30 +
src/lib/recordingDiarization.ts | 241 +
src/lib/recordingTranscription.ts | 42 +-
src/lib/schemas/recording.ts | 25 +-
...0260919_032729_add_recording_speakers.json | 16760 ++++++++++++++++
.../20260919_032729_add_recording_speakers.ts | 51 +
src/migrations/index.ts | 10 +-
src/payload-types.ts | 32 +
src/utilities/ai/assemblyAiDiarize.ts | 178 +
src/utilities/recordings/recordingJob.ts | 93 +-
.../recordings/recordingListFilters.ts | 21 +-
src/utilities/recordings/recordingListUrl.ts | 53 +-
src/utilities/recordings/recordingPageData.ts | 76 +-
.../recordings/recordingViewModels.ts | 107 +-
tests/e2e/campaignSpeechAcervo.e2e.spec.ts | 189 +-
tests/int/recording.int.spec.ts | 214 +-
tests/unit/assemblyAiDiarize.unit.spec.ts | 134 +
tests/unit/recording.unit.spec.ts | 14 +
tests/unit/recordingDiarization.unit.spec.ts | 177 +
tests/unit/recordingListFilters.unit.spec.ts | 44 +
tests/unit/recordingListUrl.unit.spec.ts | 62 +
38 files changed, 19479 insertions(+), 97 deletions(-)
create mode 100644 docs/changelog/2026-09-19-c200.md
create mode 100644 docs/plans/acervo-separacao-por-pessoa-impl.md
create mode 100644 src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/falantes/route.ts
create mode 100644 src/components/campaign/recording/RecordingPersonFilter.tsx
create mode 100644 src/components/campaign/recording/RecordingSpeakerDialog.tsx
create mode 100644 src/components/campaign/recording/RecordingSpeakerTranscript.tsx
create mode 100644 src/components/campaign/recording/RecordingTranscriptSegmentButton.tsx
create mode 100644 src/lib/recordingDiarization.ts
create mode 100644 src/migrations/20260919_032729_add_recording_speakers.json
create mode 100644 src/migrations/20260919_032729_add_recording_speakers.ts
create mode 100644 src/utilities/ai/assemblyAiDiarize.ts
create mode 100644 tests/unit/assemblyAiDiarize.unit.spec.ts
create mode 100644 tests/unit/recordingDiarization.unit.spec.ts
create mode 100644 tests/unit/recordingListFilters.unit.spec.ts
diff --git a/docs/changelog/2026-09-19-c200.md b/docs/changelog/2026-09-19-c200.md
new file mode 100644
index 00000000..65fbca59
--- /dev/null
+++ b/docs/changelog/2026-09-19-c200.md
@@ -0,0 +1 @@
+**C200 — Separar falas por pessoa nas gravações enviadas (2026-09-19):** as gravações do C199 saíam com a transcrição em bloco único e a assessoria não conseguia responder "o que fulano disse nesta gravação?". Entregou-se a camada de quem-falou-o-quê com agrupamento **acústico** e identificação **humana** (sem voiceprint, sem match voz↔pessoa, sem `Contact`): (1) **diarização** — o job ganhou uma etapa opcional atrás de um seam injetável (`SpeakerDiarizer`); o áudio extraído inteiro (uma passada, para a numeração ser global) vai ao provedor recomendado **AssemblyAI** `universal-2` + `speaker_labels` (US$0,17/h; chave `ASSEMBLYAI_API_KEY` ainda não configurada — sem chave/falha, a transcrição continua em bloco, nunca finge separação), e os turnos são alinhados aos segmentos do Whisper por sobreposição máxima (empate pelo ponto médio, sem sobreposição → turno mais próximo) com numeração `speaker-N` por primeira aparição; (2) **schema/migration** `20260919_032729_add_recording_speakers` — `recording_segment.speaker_key`, `recording.speaker_labels` (`{speakerKey,label}[]`), `recording.speaker_names` (`hasMany` derivado por hook, base da faceta) e `recording.speaker_labels_dropped`; o SQL duplicado de `reel.source_hash` que o snapshot defasado do C199 gerava foi removido à mão e o snapshot desta migration o corrige; (3) **reprocessamento não perde rótulo**: `reconcileSpeakerLabels` (puro) só mantém um rótulo em casamento mútuo 1:1 por sobreposição de tempo — split/merge descartam com aviso `speakerLabelsDropped` renderizado no próprio banner; (4) **faceta "Pessoa"** na fonte enviadas: param repetível `person` no contrato da lista (`contains` case-insensitive sobre `speaker_names`), chips removíveis (design cena 3), chip "aparece nesta gravação" no card e preservação do filtro ao submeter a busca; (5) **ação "Identificar falante"** (`POST .../gravacoes/[id]/falantes`) — rotula um agrupamento validando que a chave ainda existe na gravação, gate fail-closed do acervo (a escrita usa o predicado do dono; `advisor`/`leader` negados) e limpa o aviso de reconciliação; (6) **design C portado classe-a-classe** das cenas 1–5 (blocos por falante com avatar/estado/edição, diálogo de identificação, aviso âmbar de imprecisão sempre visível, mobile compacto); (7) **testes**: 17 unit novos (alinhamento/numeração/reconciliação/URL/where/provedor com fetch mockado), 4 int novos (faceta no join real, matriz de papéis da action, job com diarizador fake preservando rótulo e fallback com aviso) e 5 e2e novos na vertical; migração aplicada só em banco local, nenhuma gravação antiga reprocessada automaticamente.
diff --git a/docs/plans/acervo-separacao-por-pessoa-impl.md b/docs/plans/acervo-separacao-por-pessoa-impl.md
new file mode 100644
index 00000000..df15eaea
--- /dev/null
+++ b/docs/plans/acervo-separacao-por-pessoa-impl.md
@@ -0,0 +1,239 @@
+# Impl: C200 — Separar falas por pessoa nas gravações enviadas
+
+Status: aprovado
+Atualizado em: 2026-09-19
+Issue: #1167
+Intenção: docs/plans/acervo-separacao-por-pessoa.md
+Appetite restante: herdado (~2–3 dias eng; um outcome verificável — a assessoria identifica quem fala numa gravação enviada e filtra o acervo pelas falas de uma pessoa)
+
+## Leitura da intenção
+
+- **Outcome:** gravações novas do acervo (fonte "Gravações enviadas", C199) passam a ter a transcrição **dividida por agrupamento acústico** ("Falante 1", "Falante 2", …); a assessoria ouve e usa "Identificar falante" para nomear um agrupamento, valendo para todas as falas dele naquela gravação; a lista ganha a faceta **"Pessoa"** que recorta as gravações onde aquele rótulo aparece. A busca geral continua cobrindo a transcrição inteira.
+- **O que NÃO negociar:** agrupamento é acústico e a identificação é **humana** (nada de voiceprint, match voz↔pessoa, inferência ou sugestão automática); rótulo é texto por gravação, **sem segundo cadastro de pessoa** (`Contact` intocado); rótulo não preenchido permanece "Falante N"; sem `Consent` novo (mídia interna de staff); sem score de confiança de "quem falou" na UI; gate fail-closed do acervo (`communicator` + `coordinator`/`candidate`; `advisor`/`leader` negados); nada de backfill automático das gravações antigas; sem UI nova de reprocessar/diarizar fora do design aprovado.
+- **O que reavaliar** (hipóteses da intenção):
+ - "O import substitui os segmentos em bloco — um rótulo só sobrevive se o reprocessamento carregar/re-aplicar" (intenção): confirmado; a reconciliação é decisão desta impl (D6). Nota de realidade: hoje o lifecycle **não tem** caminho `ready → processing` (retry só `failed`, e falha não grava segmentos), então a reconciliação é um contrato do job testado no int, pronto para a ação futura de reprocessar/diarizar (§D8).
+ - "A fonte enviadas já tem o padrão de chips/omnibox" (achado do explorador): **não tem** — hoje é só `CampaignSearchInput`. A faceta nova reusa o **`CampaignHeaderFilterPopover`** (multi-seleção) com adaptação local, sem importar o omnibox da Câmara.
+ - "Reusar o pipeline de transcrição por chunks para diarizar": não serve — chaves por chunk não se ligam entre chunks (ver D2).
+ - "Provedor de diarização" (questão em aberto da intenção): resolvido em D1/D2 (provedor externo, áudio inteiro numa passada) e registrado como **GATE humano de custo/chave**.
+
+## Abordagem recomendada
+
+```mermaid
+flowchart TD
+ U[Upload C199] --> J[recordingJob]
+ J -->|"ffmpeg: mp3 mono 16k / chunks 20min"| W["Deep Infra Whisper deepInfraTranscribeSegments"]
+ W --> M[mergeChunkTranscriptions]
+ J -->|"se há provedor: full.mp3 numa passada ffmpeg"| D["AssemblyAI universal-2 language_code=pt, speaker_labels"]
+ D -->|"utterances speaker/start/end"| A["assignSpeakerKeys alinhamento por sobreposicao"]
+ M --> A
+ A --> T[["tx: recordingSegment.speakerKey + recording.speakerLabels reconciliadas"]]
+ D -.->|"falha, timeout ou sem chave"| F["fallback honesto: transcricao sem agrupamento"]
+ F --> T
+ T --> R[(recording / recordingSegment)]
+ L["Lista ?source=enviadas&person=..."] -->|"speakerNames contains"| R
+ P["Detalhe /gravacoes/id"] -->|"blocos por falante"| R
+ DLG["Dialogo Identificar falante"] -->|"POST /gravacoes/id/falantes"| AC[labelRecordingSpeakerForActor]
+ AC --> R
+```
+
+**Opções consideradas:** A | B | C em cada decisão abaixo.
+**Recomendação:** manter o Whisper/Deep Infra como dono da transcrição e adicionar uma **etapa opcional de diarização** sobre o áudio inteiro (AssemblyAI, `universal-2`, `speaker_labels`), **alinhando os turnos aos segmentos existentes** por sobreposição de tempo; chave do agrupamento no `recordingSegment`, rótulos na `recording` com a faceta denormalizada em `speakerNames` (`hasMany text` + `contains`, precedente `Speech.keywords`); reconciliação conservadora de rótulos no job + aviso; sem provedor/chave, a transcrição sai **sem agrupamento** (nunca fingir separação).
+
+### Decisões de engenharia
+
+**D1 — Provedor de diarização (pesquisa web, números oficiais).**
+`Opções: A) AssemblyAI | B) Deepgram | C) Gladia | D) Speechmatics | E) diarização local no homeserver | F) LLM de áudio | G) Deep Infra (atual)`
+
+`Recomendação: A — AssemblyAI é o único com preço/hora mais baixo entre os verificados, aceita arquivos de até 10 h (cobre plenária inteira numa passada), tem pt com WER ≤10% declarado, diarização explícita no modelo universal-2 e fluxo assíncrono com polling (submit → poll), que é exatamente a forma do job C199 (heartbeat por atualização do updatedAt).`
+
+| Provedor | Diarização | Preço oficial (pré-gravado) | pt-BR | Limite por arquivo | Forma do fluxo |
+| ---------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
+| **AssemblyAI** | `speaker_labels: true` (`universal-2`, `universal-3-5-pro`) | **US$0,15/h** (`universal-2`) + aditivo diarização standard **+US$0,02/h = US$0,17/h** ≈ US$0,0028/min | `pt` com "high accuracy (≤10% WER)"; `universal-3-5-pro` cita dialeto pt-BR | **10 h** por arquivo; 5 GB via URL/2,2 GB no `/v2/upload` | `POST /v2/upload` → `POST /v2/transcript` → poll; `utterances[]` com `speaker`/`start`/`end` (ms) |
+| Deepgram | `diarize_model=latest` | Nova-3 pré-gravado **US$0,0043/min = US$0,258/h** (diarização incluída no pré-gravado) | `pt-BR` na lista do nova-3 | não verificado nesta sessão | `POST /v1/listen` único e síncrono (ou callback) |
+| Gladia | incluída (pyannoteAI Precision-2) | async **US$0,61/h** | 100+ idiomas | **135 min** / 1 000 MB por request | upload → `/v2/pre-recorded` → poll |
+| Speechmatics | incluída | Melia 1 batch **a partir de US$0,129/h** (em _production preview_ oficial) | 55+ idiomas | batch, limites no portal | submit → poll |
+| Deep Infra (atual) | **não diariza** — catálogo ASR verificado ao vivo (6 modelos, nenhum com diarização) | US$0,00045/min no Whisper large-v3 | pt | chunks de 20 min (C199) | já é o ASR do job |
+| Local (pyannote/sherpa-onnx) | sim (montagem própria) | sem API; custo de ops/CPU/modelos | depende do modelo | — | processo/serviço Python no homeserver |
+| LLM de áudio (Gemini/OpenAI) | **não é diarização acústica** | por token | sim | — | áudio inteiro ao LLM |
+
+- **A (recomendada):** `utilities/ai/assemblyAiDiarize.ts` como dono do provedor (forma de `deepInfraTranscribe.ts`), `ASSEMBLYAI_API_KEY` nova. A diarização roda só com chave; a política de delete do transcript remoto (`DELETE /v2/transcript/{id}`) é best-effort pós-leitura (higiene), e **jamais** se liga `speech_understanding.speaker_identification` (nome automático — proibido pelo guardrail).
+- **Rejeitadas:**
+ - **B** porque custa ~50% mais por hora (US$0,258 vs US$0,17) e é chamada síncrona de horas — sem o polling nativo que dá heartbeat barato ao job; qualidade comparável e pt-BR ok, então fica como **alternativa de segunda escolha no gate**.
+ - **C** porque o async aceita no máximo **135 min/1 000 MB** por request (docs oficiais): uma plenária de horas exigiria fatiar e a numeração global entre fatias volta a ser um palpite (o problema que D2 elimina); além de ~3,6× o custo.
+ - **D** porque o modelo de menor preço (Melia 1) está em _production preview_ oficial e o tier de maior acurácia (Enhanced) não publica um rate card único e estável nas fontes oficiais consultadas; sem vantagem decisiva sobre A.
+ - **E** porque o homeserver não tem GPU e montar pyannote/sherpa-onnx adiciona runtime Python, download de pesos e um domínio de falha de ops sem seam de teste; revisitar só com custo/volume material ou mudança de política de dados.
+ - **F** porque LLM de áudio atribui falante probabilisticamente (alucina atribuição e nome), não entrega turnos com timestamp confiável e colidiria com a regra OPS123 (provider `openai/*` reservado a design no Teqo).
+ - **G** porque capacidades verificadas: nenhum dos 6 modelos ASR do catálogo Deep Infra diariza; uma chave nova no mesmo provedor não resolve.
+
+**D2 — Escopo da diarização: áudio inteiro vs por chunk de 20 min.**
+`Opções: A) uma passada no áudio inteiro | B) por chunk de 20 min com numeração global por ordem de primeira aparição | C) por chunk com re-associação por embedding entre chunks`
+
+`Recomendação: A — o agrupamento é uma propriedade do áudio inteiro; diarizar por chunk gera chaves que não se ligam entre chunks e repetir "Falante 1" em cada chunk refere-se a pessoas diferentes (enganoso, contra o guardrail). O áudio extraído (mono 16 kHz, 32 kbps ≈ 14 MB/h) fica muito abaixo do limite de tamanho do provedor e dentro das 10 h de duração; acima de 10 h o job pula a diarização (fallback honesto).`
+
+- **Rejeitadas:** **B** porque "Falante N" por chunk com numeração global por ordem de aparição **não** garante a mesma pessoa (sem biometria não há re-associação) e mente para a assessoria; **C** porque re-associação por embedding é voiceprint — proibida pelo produto e pela intenção.
+
+**D3 — Papel do provedor: diarizar e alinhar vs substituir o ASR.**
+`Opções: A) provedor só fornece turnos; alinhamento aos segmentos do Whisper | B) provedor passa a ser o ASR da gravação (utterances viram segmentos) | C) provedor só quando há chave; Whisper quando não há`
+
+`Recomendação: A — o Whisper continua dono do texto, do searchText e dos timestamps por segmento (C199 pinado); o provedor entra apenas como camada de agrupamento. Falha do provedor não afeta a transcrição, e o alinhamento vira uma função pura testável sem rede.`
+
+- **Rejeitadas:** **B** porque troca o dono do texto, muda o custo do caminho feliz (~10× por minuto), reescreve a busca e faria a falha do provedor derrubar a transcrição inteira; **C** porque duas qualidades de transcrição no mesmo acervo sem o usuário saber, e dois donos do texto quebrando pinos C199.
+
+**D4 — Modelo de dados dos rótulos.**
+`Opções: A) estado na recording (array `speakerLabels`) + `speakerNames` denormalizado (hasMany text) + chave no segmento | B) rótulo copiado em cada segmento | C) collection nova de rótulos | D) campo JSON`
+
+`Recomendação: A — exatamente o que a tarefa fixou: recordingSegment.speakerKey (chave do agrupamento) + estado de rótulos na recording; a faceta "Pessoa" usa speakerNames (text hasMany → text[] no Postgres; precedente Activity.tags/Speech.keywords) com contains (ILIKE), funcionando no Payload 3.82.`
+
+- A: `speakerLabels: { speakerKey, label }[]` (readOnly no admin; uma linha por agrupamento identificado) e hook `deriveSpeakerNames` preenchendo `speakerNames` (dedupe case-insensitive, ordem de aparição) — espelho de `RecordingSegment.deriveSearchText`. `speakerLabelsDropped: boolean` guarda o aviso de reconciliação (D6). Segmentos guardam só a chave.
+- **Rejeitadas:** **B** porque renomear um agrupamento tocaria N segmentos e a faceta viraria um join caro; **C** é collection nova para um estado 1:N simples (custo de access/index/migration sem ganho) e foge da direção da tarefa; **D** não tem validação/índice nem precedente de query de faceta.
+
+**D5 — Semântica da faceta "Pessoa".**
+`Opções: A) `{ speakerNames: { contains: valor } }`OR entre valores selecionados | B)`{ speakerNames: { in: valores } }` exato | C) normalizar rótulo em slug na gravação`
+
+`Recomendação: A — o precedente da casa (speechListFilters.ts:70, Speech.keywords contains) é case-insensitive e tolera variação ("Solla" casa "Dep. Jorge Solla"); a URL guarda o texto literal, a curadoria varia o rótulo e o recorte não fragmenta.`
+
+- **Rejeitadas:** **B** porque a variante livre ("Solla" vs "Dep. Jorge Solla") fragmentaria a faceta (anti-goal de curadoria acidental); **C** porque cria uma segunda representação do rótulo e um merge implícito que a intenção cortou.
+
+**D6 — Preservação de rótulos ao reprocessar.**
+`Opções: A) reconciliação por sobreposição de tempo, conservadora 1:1, com flag + aviso | B) casar por igualdade de speakerKey | C) aceitar a perda silenciosa | D) bloquear o reprocessamento quando há rótulo`
+
+`Recomendação: A — sem biometria não há re-associação entre execuções; carregar um rótulo só quando um agrupamento novo casa 1:1 por tempo com um antigo é o contrato honesto. Quem não casa fica sem nome e o job liga speakerLabelsDropped, que rende o aviso no detalhe.`
+
+- A: o job lê labels + segmentos antigos **antes** de apagar, computa `reconcileSpeakerLabels` (puro: sobreposição acumulada por par, 1:1, ordenado por primeira aparição) dentro da mesma transação e grava `speakerLabels` reconciliadas + `speakerLabelsDropped`. O aviso vive **dentro** do banner âmbar da Cena 4 (uma linha condicional — ver §UI).
+- **Rejeitadas:** **B** porque a chave é por execução e a mesma chave pode referir outra pessoa (o aceite manda re-vincular com aviso, não fingir); **C** viola o aceite; **D** porque o reprocessamento de hoje só existe para `failed` (que não tem rótulo) e, na ação futura, bloquear sem alternativa é pior que reconciliar e avisar.
+
+**D7 — Fallback sem provedor/chave/timeout.**
+`Opções: A) transcrição sem agrupamento (estado C199, sem "Falante") | B) falhar a gravação | C) criar um "Falante 1" único`
+
+`Recomendação: A — o acervo continua útil e a ausência de blocos não afirma nada; é exatamente o detalhe C199 já aprovado, sem markup novo.`
+
+- **Rejeitadas:** **B** porque faz a transcrição (que não depende do provedor) depender de um custo humano ainda não aprovado; **C** porque sugere um falante único onde há vários — fingir separação, proibido.
+
+**D8 — Reprocessar/diarizar gravações antigas.**
+`Opções: A) ação explícita por gravação agora | B) backfill automático | C) sem ação agora; follow-up com gatilho | D) UI de reprocessar junto com este item`
+
+`Recomendação: C — a tarefa veta UI nova de reprocessar fora do design e a intenção veta backfill. A reconciliação (D6) fica pronta no job para quando a ação existir.`
+
+- **Gatilho do follow-up:** chave do provedor ativa em produção **e** (pedido operacional de diarizar o acervo antigo **ou** passarem ~30 gravações novas sem diarização). Aí o designer estende o artefato **antes** do markup (trigger a). **Débito absorvido pelo D8 (triagem do /simplify):** quando a ação de reprocessar existir, a semântica de limpar `speakerLabelsDropped` no rótulo precisa considerar rótulos ainda perdidos (hoje inalcançável: não há `ready → processing`).
+- **Débito absorvido pelo C202:** a action de rótulo gateia no action-level com `canReadCommunicationCatalog` (a collection já exige `canUploadRecording`); a troca pelo predicado de escrita do dono foi adicionada ao plano `docs/plans/gates-gravacoes-predicados.md` (#1186).
+- **Rejeitadas:** **B** porque o custo e o risco de mexer em rótulos não justificam varrer tudo; **A/D** porque contrariam a restrição de superfície do item.
+
+## Componentes / mudanças
+
+- **`src/lib/recordingDiarization.ts`** (novo, puro/client-safe): `DiarizedTurn`, `assignSpeakerKeys` (sobreposição máxima; empate pelo ponto médio; sem sobreposição → turno mais próximo; numeração global `speaker-1…` por primeira aparição), `reconcileSpeakerLabels` (D6), `speakerNamesFromLabels`, `recordingSpeakerDefaultLabel(n)` → `Falante ${n}`, `RECORDING_DIARIZATION_MAX_SECONDS = 10h` (limite do provedor citado em comentário). Mesmo padrão de dono dos puros de `lib/recordingTranscription.ts`.
+- **`src/lib/recordingTranscription.ts`** (editar): `buildRecordingFullAudioFfmpegArgs` (mp3 mono 16k/32k sem segmentação) ao lado do args de chunks; merge/searchText intocados.
+- **`src/lib/recording.ts`** (editar): `RECORDING_SPEAKER_LABEL_MAX_LENGTH = 120`, mensagens pt-BR do diálogo/action, nada de status novo.
+- **`src/utilities/ai/assemblyAiDiarize.ts`** (novo, `server-only`): dono do provedor (molde `deepInfraTranscribe.ts:98-137`) — upload → transcript (`language_code: 'pt'`, `speaker_labels: true`, sem identificação automática) → poll com deadline de 60 min e intervalo de 5 s; normalização ms→s; nunca lança (`{ ok, turns }` / `{ ok:false, error, status }`); `DELETE` best-effort do transcript; chave ausente → 503. Exporta `assemblyAiDiarize` e `configuredSpeakerDiarizer()` (null sem chave).
+- **`src/utilities/recordings/recordingJob.ts`** (editar): `runRecordingJob(payload, id, transcribe = deepInfraTranscribeSegments, diarize = configuredSpeakerDiarizer())`; extrai `full.mp3` só quando `diarize` existe e a duração ≤ 10 h, chama o provedor (heartbeat `step:'transcribing'`), alinha com `assignSpeakerKeys`, e na transação já existente (`recordingJob.ts:191-227`) lê o estado antigo, apaga/recria segmentos com `speakerKey` e grava `speakerLabels`/`speakerLabelsDropped`. Falha/timeout do provedor **não** marca `failed`.
+- **`src/collections/Recording.ts`** (editar): `speakerLabels` (array `{ speakerKey, label }`, readOnly), `speakerNames` (`text` `hasMany`, readOnly), `speakerLabelsDropped` (`checkbox`, readOnly) + hook `deriveSpeakerNames` (fallback `originalDoc` como `deriveSearchText`).
+- **`src/collections/RecordingSegment.ts`** (editar): `speakerKey` (`text`, opcional, readOnly).
+- **`src/utilities/recordings/recordingListUrl.ts`** (editar): param `person` (repetível) no `RecordingListState`, parse com `allParamValues` + trim + cap 120 + dedupe, serialize com `append`, `toggleRecordingPerson`/`removeRecordingPerson` puros (page reset) — o "adapter" da faceta mora aqui em vez de um módulo espelho de `speechOmnibox.ts` (depth check).
+- **`src/utilities/recordings/recordingListFilters.ts`** (editar): `buildRecordingListWhere` AND-e `searchText like` + `{ or: persons.map(p => ({ speakerNames: { contains: p } })) }`.
+- **`src/utilities/recordings/recordingViewModels.ts`** (editar): `RecordingSpeakerGroupViewModel` (`key`, `index`, `defaultLabel`, `label`, `isIdentified`, `segments`), `RecordingDetailViewModel.speakerGroups` + `speakerLabelsDropped`; `RecordingListItemViewModel.matchedPersons`; agrupamento por primeira aparição; caminho sem chave renderiza a lista plana C199.
+- **`src/utilities/recordings/recordingPageData.ts`** (editar): selects (`speakerNames`, `speakerLabels`, `speakerLabelsDropped`, `speakerKey`), `loadRecordingFilterOptions` (select de `speakerNames` com `pagination:false`, dedupe e sort pt-BR — molde `speechPageData.ts:85-121`), `matchedPersons` por linha (puro `contains` case-insensitive espelhando o where).
+- **`src/app/(campaign)/campanha/actions/recording.ts`** (editar): `labelRecordingSpeakerForActor({ recordingId, speakerKey, label })` — gate pelo **predicado de escrita do dono** (C202, §Dependências), valida que a chave existe em algum segmento da gravação, faz read-modify-write do array `speakerLabels` e limpa `speakerLabelsDropped` (o ato humano reconhece/revisa); 1 collection → sem transação.
+- **`src/lib/schemas/recording.ts`** (editar): `recordingSpeakerLabelRequestSchema` (id, `speakerKey` `^speaker-\d{1,3}$`, label 1..120 trim) + mensagens seguras.
+- **Rotas** (`.../acervo/gravacoes/[id]/falantes/route.ts`, novo): `POST` via `campaignJsonMutationRoute` (sem allowlist de conventions — rota wrapper, não crua), molde de `[id]/retry/route.ts`; `types.ts` ganha `RecordingSpeakerLabelResponse`; `lib/campaignPaths.ts` ganha `campaignRecordingSpeakersHref`.
+- **UI** (`src/components/campaign/recording/`): `RecordingSpeakerTranscript.tsx` (novo, client; blocos + banner âmbar + botões), `RecordingSpeakerDialog.tsx` (novo, client; molde `SpeechCutDeleteDialog` + `postCampaignJson`), `RecordingPersonFilter.tsx` (novo, client; `CampaignHeaderFilterPopover` + `useCampaignListFilterNavigation`), edições em `RecordingDetailPlayer.tsx` (renderiza grupos quando existem; senão o `` atual), `RecordingResultCard.tsx` (chip de pessoa) e `acervo/page.tsx` (hidden inputs de `person` no form de busca, linha da faceta, vazio com filtro).
+- **Manifest e2e** (`scripts/lib/e2e-affected-manifest.mjs`): **sem mudanças esperadas** — `src/utilities/ai` (linhas 261-265) já mapeia `campaignSpeechAcervo`, e `src/utilities/recordings`/`src/components/campaign/recording`/`src/lib/recording` (322-336) já cobrem os arquivos novos; confirmar no gate.
+- **Migration:** `add_recording_speakers` (abaixo).
+- **Access / Consent:** sem `Consent` novo; `utilities/access/recordings.ts` intocado neste item (C202 é o dono da ampliação de gate); a action de rótulo é a única superfície nova e usa o predicado de escrita do dono.
+- **UI:** Impeccable **C** — port do artefato + shells (`CampaignHeaderFilterPopover`, `CampaignListPendingBoundary`, `CampaignListEmptyState`, `CampaignListFooter`, `Dialog`, `Alert`, `Spinner`); shape→craft→critique→polish no ciclo.
+
+### Dados → forma (se aplicável)
+
+- **N/A por decisão da intenção** (`acervo-separacao-por-pessoa.md:59`): filtro por pessoa é recorte de conteúdo, não métrica/agregado; nenhuma confiança numérica é exposta (o produto proíbe score de "quem falou"). Nada a apresentar; nenhuma forma nova.
+
+## UI e port do design (fonte de verdade: `acervo-separacao-por-pessoa-ui-design.html`)
+
+**Coberto pelo artefato — port classe-a-classe:**
+
+1. **Cena 1 (detalhe desktop, agrupado):** grid `grid-cols-[minmax(0,1.05fr)_minmax(430px,.95fr)]`, mídia à esquerda (player + `Baixar` + banner informativo **literal** "Os falantes são agrupados automaticamente pelo áudio. A identificação de cada pessoa é feita pela equipe.") e coluna direita com heading uppercase "Transcrição por falante", hint "Clique para posicionar" e `article.speaker-block` por grupo: head (`size-7` avatar, `Falante N`/rótulo, sub "Agrupamento sem identificação"/"Identificação feita pela equipe", botão outline "Identificar falante"/"Editar identificação") e linhas `seg` (grid `3.25rem 1fr`, `data-start-seconds`, ativo com `bg-muted`, hora em `text-primary` quando ativo, texto com `SpeechHighlightParts`).
+2. **Cena 2 (diálogo):** `Dialog` com título "Identificar falante", descrição `Dê um nome ao agrupamento “Falante N”.` (sempre o rótulo default), caixa informativa "Ouça um trecho e informe quem é a pessoa. O acervo não sugere nem identifica pessoas automaticamente.", label "Nome do falante", placeholder "Digite o nome do falante", helper "O nome será aplicado a todas as falas deste agrupamento nesta gravação.", botões "Cancelar"/"Salvar identificação"; erro inline (molde retry/delete), `router.refresh()` no sucesso.
+3. **Cena 3 (lista com faceta):** mantém `RecordingsSearchForm` (`CampaignSearchInput`, C199) **com hidden inputs dos `person` ativos** (submeter a busca não pode perder o filtro) e adiciona a linha de chips (botão outline "Pessoa: {rótulo}" com X) via `CampaignHeaderFilterPopover` multi-seleção; card com o chip `{rótulo}` + "aparece nesta gravação" sob o excerpt.
+4. **Cena 4 (aviso de imprecisão):** banner âmbar com título "A separação por falante pode estar imprecisa" e corpo "O agrupamento é automático e aproximado. Ruído e falas sobrepostas podem colocar trechos no agrupamento errado. A equipe continua responsável por identificar cada falante." — **sempre visível no detalhe agrupado** (sem score de qualidade, D1/anti-goal; o artefato não define gatilho). A linha condicional de reconciliação (D6) entra **dentro desse mesmo banner** quando `speakerLabelsDropped`: "Esta gravação foi reprocessada e algumas identificações não puderam ser mantidas. Confira os falantes." — uma frase, sem componente/superfície nova.
+5. **Cena 5 (mobile 390 px):** mesmo detalhe em coluna única, head compacto do bloco (avatar + nome + botão "Identificar"/ícone de edição com `aria-label` "Editar identificação"), banner informativo curto **literal** "Agrupamento automático e aproximado. A identificação é feita pela equipe." e hint "Toque para posicionar".
+
+**Decisões de port fora do literal (baratas, revisáveis):**
+
+- **Subtítulo da página na Cena 3** ("Gravações enviadas"): mantém o subtítulo aprovado do C199 ("Busque nas falas da Câmara ou nas gravações da equipe.") + alternador; a Cena 3 é shorthand do estado da fonte e mudar o chrome regride o pin do C199 sem ganho de produto.
+- **Vazio com filtro de pessoa**: reusa `CampaignListEmptyState` com a copy da casa ("Tente outro termo, remova filtros ou limpe a busca.") — estado não desenhado, shell aprovada.
+- **Fallback sem agrupamento**: renderiza o detalhe C199 **como está** (sem copy nova de "não diarizado"). A tarefa manda disparar o designer se precisarmos de superfície nova — **não precisamos**; se surgir demanda de explicar a ausência, é trigger a (designer antes do markup), registrado como follow-up (§D8).
+- Dialog sobre grupo já identificado reusa a copy da Cena 2 com o rótulo default; o trigger é que muda ("Editar identificação").
+- `role="tablist"` inexistente aqui; o alternador C199 fica intocado.
+
+## Migration
+
+- Nome: `pnpm migrate:create add_recording_speakers` → gera coluna `recording_segment.speaker_key` (`text`), tabela `recording_speaker_labels` (`id`, `_order`, `_parent_id` FK `recording` cascade, `speaker_key`, `label`), coluna `recording.speaker_names` (`text[]`) e `recording.speaker_labels_dropped` (`boolean`). **Revisar o SQL**; sem índices novos (a faceta é `contains` sobre `text[]` em tabela pequena e o `Speech.keywords` não tem índice dedicado). Referência de forma: `20260715_163458_initial` (arrays) e `20260918_201009_add_reel`.
+- **HARD-STOP de aprovação humana explícita, mesmo em sessão `--auto`, e local-only:** criar/aplicar a migração apenas no banco local do worktree (`pnpm migrate`), **nunca** tocar banco remoto/prod e **nunca** editar migração antiga. Parar, apresentar o SQL gerado e aguardar aprovação antes de seguir para o job/UI.
+
+## Invariantes
+
+- Local API em contexto de usuário sempre com `overrideAccess: false` + `user` (lista, detalhe, action de rótulo); bypass admin só no pipeline que é dono do row, com comentário (forma `recordingJob.ts:54-56`).
+- Escrita multi-collection em transação com `req: { transactionID }` (`withPayloadTransaction`): substituição de segmentos + estado de rótulos + `searchText`/status na mesma transação; a action de rótulo é 1 collection.
+- Sem `Consent` novo; sem `Contact`; rótulo é texto; nenhuma URL pública; `/arquivo` e matriz de papéis intocados.
+- Gate fail-closed do acervo; a escrita nova usa o predicado de escrita do dono (C202) — nunca `canReadCommunicationCatalog` gateando escrita; `advisor`/`leader` negados (matriz no int/e2e).
+- Sem biometria/voiceprint/unificação entre gravações; sem score/confiança na UI; sem inferência de identidade.
+- "Edit the owner, don't twin": puros no `lib` dono da pipeline, provedor no `utilities/ai` dono, where/estado no `recordings/`, sem módulo top-level novo em `src/utilities/`.
+- Identificadores em inglês; copy/labels pt-BR; valores de URL em pt-BR só os existentes (`source=enviadas`, `falantes`).
+- `pnpm generate:types` após o schema; `pnpm gate:fast` durante; `pnpm push` como entrega; **este plano e `docs/changelog/2026-09-19-c200.md` entram no commit da entrega**.
+
+## Fases verificáveis
+
+1. **Puros + schema + migration (HARD-STOP)** (~1 dia).
+ - `lib/recordingDiarization.ts`, `lib/recordingTranscription.ts` (+full), `recordings/recordingListUrl|Filters|ViewModels|PageData`, `lib/recording.ts`, collections + hook; `pnpm generate:types`.
+ - Unit: alinhamento (sobreposição, empate, sem sobreposição→mais próximo, vazio), numeração global por primeira aparição, reconciliação (1:1 carrega; split/merge/orfão dropa + flag), `speakerNames` derivado, guard de 10 h, ffmpeg args do full, parse/serialize `person` (multi/dedupe/cap), where `contains`+q AND, `matchedPersons`.
+ - Int: matriz de papéis e persistência das três colunas novas; **validação da query da faceta `contains` em `text[]` no Payload 3.82** (precedente `Speech.keywords` e `Activity.tags`).
+ - **Migration `add_recording_speakers` — HARD-STOP humano, local-only.**
+2. **Provedor + job + ação** (~1 dia).
+ - `assemblyAiDiarize` (seam injetável na assinatura do job), job com reconciliação na transação, action/rota/schema/paths.
+ - Int: job com diarizador fake (feliz multi-chunk → `speakerKey` por segmento + labels preservadas; falha do provedor → `ready` sem agrupamento; reconciliação com fixtures antigas; chave inexistente → fallback), `labelRecordingSpeakerForActor` (papéis; chave desconhecida; label vazio/longo; limpa `speakerLabelsDropped`), sem chamada real de rede.
+3. **UI** (~1 dia).
+ - Cenas 1–5 + hidden inputs + faceta + cards; ação de rótulo no diálogo.
+ - Unit: view models dos grupos (ordem, `Falante N`, rótulo aplicado, flag), cena/copy constants; e2e cobre o markup.
+4. **Gates + GATE humano** (~0.5 dia).
+ - Estender `tests/e2e/campaignSpeechAcervo.e2e.spec.ts` (describe C199): fixture `ready` com segmentos com `speakerKey` + `speakerLabels`; detalhe renderiza blocos/"Falante N"/rótulo/aviso; lista `?person=` recorta e o card mostra o chip; fallback sem `speakerKey` mantém `data-start-seconds` do C199; advisor negado na rota nova.
+ - `pnpm gate:fast`; `pnpm test:e2e:affected`; manifest/conventions (confirmar sem mudança); **smoke manual do provedor com 1 gravação real** (chave aprovada); changelog; push.
+
+Quota: ~3–3,5 dias eng, dentro do appetite herdado; se apertar, o corte é o polish do port (a estrutura das cenas 1/2/3 fica antes do acabamento).
+
+## Rabbit holes / Não escopo (engenharia)
+
+- Voiceprint/embedding para re-associar falantes entre execuções/gravações; unificação automática de "mesma pessoa".
+- Cadastro/diretório de pessoas ou vozes; qualquer uso de `Contact`.
+- Edição/correção de transcrição, alinhamento por palavra, refino de sobreposição.
+- Backfill de diarização do acervo antigo e UI de "Diarizar" (follow-up com gatilho, §D8).
+- Multi-provedor/adapter genérico de diarização (uma interface, um provedor — sem volatilidade que justifique mais).
+- Webhook/callback do provedor (o poll do job basta; callback criaria superfície pública nova).
+- Score/confiança de falante na UI (proibido); detecção automática de "qualidade ruim".
+- Fila/worker, tempo real, streaming, cortes por falante, busca semântica (C192) e merge de fontes.
+
+## Riscos e mitigação
+
+- **Custo/chave do provedor não aprovados (GATE).** Sem chave, tudo degrada para a transcrição sem agrupamento (D7); a feature nova só liga após a aprovação — nenhum teste depende de rede.
+- **`speaker_labels` não suportado para `pt` na conta real.** Os docs oficiais da AssemblyAI listam `pt` com alta acurácia no `universal-2` e diarização nos modelos `universal-2`/`universal-3-5-pro`, mas a matriz feature×idioma não é pública: **smoke manual na fase 4** antes do rollout; falha honesta = fallback.
+- **Qualidade do agrupamento em plenária ruidosa/sobreposta.** Banner da Cena 4 sempre visível + identificação humana + nenhum claim automático; a copy do provedor recomenda ≥30 s de fala por pessoa (citada nos docs).
+- **Diarização > 10 h ou timeout de poll.** Guard de duração + deadline de 60 min com heartbeat; resultado = fallback sem agrupamento, nunca `failed`.
+- **Alinhamento errado na fronteira de turnos.** Atribuição por sobreposição máxima com desempate pelo ponto médio; unidade cobre; aviso de imprecisão cobre o resto.
+- **Reconciliação conservadora demais (rótulo perdido).** Só perde quando não há mapeamento 1:1; `speakerLabelsDropped` dispara o aviso; teste int garante carregar no caso trivial (mesmos tempos).
+- **Corrida no read-modify-write de `speakerLabels`.** Aceita para ferramenta interna de equipe única (uma gravação editada por vez na prática); se doer, collection dedicada de rótulos é o follow-up.
+- **Faceta cara conforme o catálogo cresce.** Select de `speakerNames` + dedupe é O(n) com n pequeno; gatilho: acervo > ~2 000 gravações ou opções > ~200 (pré-computar opções/`distinct`).
+- **Regressão do C199.** O caminho sem `speakerKey` mantém o markup exato do `` atual; os testes C199 (unit/int/e2e) continuam a rede.
+- **Vazamento de dados ao provedor externo.** Áudio de eventos públicos da equipe, sem PII de terceiros; delete best-effort do transcript remoto; política de retenção validada no gate.
+
+## GATE humano — provedor, custo e chave (assumido; validar antes do rollout)
+
+- **Assumido:** provedor **AssemblyAI** (`universal-2` + `speaker_labels`), custo **US$0,17/h** de áudio (US$0,0028/min) — fonte oficial: pricing AssemblyAI (Universal-2 US$0,15/h; diarização async standard +US$0,02/h) e docs de Speaker Diarization/limites (10 h, 5 GB; upload 2,2 GB).
+- **Custo projetado:** 10 h/mês ≈ US$1,70; 50 h/mês ≈ US$8,50; 200 h/mês ≈ US$34,00.
+- **Pendente de aprovação humana (mesmo com `--auto`):** criar conta/chave na AssemblyAI, definir `ASSEMBLYAI_API_KEY` no env de produção (e staging se desejado), aceitar o envio do áudio a terceiro e o orçamento acima; rodar o smoke real. **Nada disso bloqueia a implementação** (seam injetável + testes sem rede); bloqueia só o rollout.
+- **Alternativa registrada no gate:** Deepgram (US$0,0043/min = US$0,258/h, diarização incluída, pt-BR) — se a aprovação preferir chamada síncrona única; a troca exige só um novo dono em `utilities/ai/` (a interface do seam não muda).
+
+## Aceite de engenharia
+
+- [ ] Aceite de produto da intenção coberto: transcrição dividida por "Falante N" nas gravações novas com provedor; "Identificar falante" vale para todo o agrupamento; faceta "Pessoa" recorta a fonte enviadas; busca geral intocada; rótulo vazio permanece "Falante N"; nunca sugere/infere identidade; reprocessar não perde rótulo sem re-vinculação avisada; antigas não são reprocessadas automaticamente.
+- [ ] Invariantes AGENTS/engineering-standards: `overrideAccess:false` + `user`; transação multi-collection; sem `Consent`/`Contact`; `advisor`/`leader` fail-closed; sem biometria/score; sem módulo top-level novo em `utilities/`; migração nova sem editar antigas; plano + changelog no commit; `pnpm push`.
+- [ ] Testes de domínio onde access/write paths mudam: puros da diarização/reconciliação/URL/where/view models no unit; job com seam injetado + action de rótulo com matriz de papéis + query da faceta no int; markup do detalhe/liste no e2e existente.
+
+## Self-score de decision-quality
+
+**4,5/5.** As decisões caras têm alternativas honestas e rejeições ancoradas: provedor com número oficial de quatro candidatos + limitações reais (135 min da Gladia, preview da Melia, 10 h/5 GB da AssemblyAI), escopo da diarização (áudio inteiro vs chunk — o modo chunk mente e foi rejeitado), papel do provedor (não substitui o ASR dono), modelo dos rótulos com precedente de query validado (`hasMany` + `contains` no Payload 3.82), semântica da faceta, reconciliação conservadora com aviso e fallback honesto. Cabe no appetite herdado (fases somam ~3–3,5 dias, tracer nos puros+schema cedo) e reusa shells/donos existentes (`CampaignHeaderFilterPopover`, `withPayloadTransaction`, `campaignJsonMutationRoute`, moldes de `deepInfraTranscribe`, `SpeechCutDeleteDialog`, `speechListFilters`). Perde 0,5 porque duas coisas ficam fora do código e viram parada dura/validação: a **migração** (HARD-STOP explícito, local-only) e o **custo/chave do provedor** (assumido; gate humano com smoke real), além de duas escolhas de port que dependem de ratificação no gate (banner da Cena 4 sempre visível sem score; subtítulo do C199 mantido na Cena 3).
diff --git a/docs/plans/gates-gravacoes-predicados.md b/docs/plans/gates-gravacoes-predicados.md
index 02c136e3..da16acd0 100644
--- a/docs/plans/gates-gravacoes-predicados.md
+++ b/docs/plans/gates-gravacoes-predicados.md
@@ -18,6 +18,7 @@ Expor no dono (`utilities/access/recordings.ts`) um predicado de papel compartil
- `POST .../gravacoes/enviar` (`route.ts` — gate cru de sessão e origem).
- `retryRecordingForActor`, `deleteRecordingForActor` (`actions/recording.ts`).
+- `labelRecordingSpeakerForActor` (`actions/recording.ts`; absorvido do C200 #1167 — a action gateia no action-level com `canReadCommunicationCatalog` enquanto a collection usa `canUploadRecording`).
- `getRecordingStatusesForActor` é leitura: mantém `canReadCommunicationCatalog`.
Rejeitadas: duplicar o corpo dos predicados nas rotas (gêmeo); criar um quarto predicado (o dono já tem os três).
diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/falantes/route.ts b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/falantes/route.ts
new file mode 100644
index 00000000..0f5366f6
--- /dev/null
+++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/falantes/route.ts
@@ -0,0 +1,32 @@
+import { NextResponse } from 'next/server'
+
+import { labelRecordingSpeakerForActor } from '@/app/(campaign)/campanha/actions/recording'
+import {
+ RECORDING_FORBIDDEN_MESSAGE,
+ RECORDING_GENERIC_ERROR_MESSAGE,
+ RECORDING_NOT_FOUND_MESSAGE,
+ RECORDING_SPEAKER_UNKNOWN_MESSAGE,
+ recordingSpeakerLabelRequestSchema,
+} from '@/lib/schemas/recording'
+import { campaignJsonMutationRoute } from '@/utilities/campaignJsonMutationRoute'
+
+import type { RecordingSpeakerLabelResponse } from '../../types'
+
+export const dynamic = 'force-dynamic'
+
+/** C200 — the team identifies one speaker cluster of the recording (human). */
+export const POST = campaignJsonMutationRoute(
+ {
+ bodySchema: recordingSpeakerLabelRequestSchema,
+ safeMessages: [
+ RECORDING_FORBIDDEN_MESSAGE,
+ RECORDING_NOT_FOUND_MESSAGE,
+ RECORDING_SPEAKER_UNKNOWN_MESSAGE,
+ ],
+ genericMessage: RECORDING_GENERIC_ERROR_MESSAGE,
+ },
+ async (body) => {
+ const result = await labelRecordingSpeakerForActor(body)
+ return NextResponse.json({ status: 'success', ...result })
+ },
+)
diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/page.tsx b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/page.tsx
index 57f020f9..2b2b7087 100644
--- a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/page.tsx
+++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/[id]/page.tsx
@@ -116,6 +116,8 @@ export default async function RecordingDetailPage({
downloadHref={recording.downloadHref}
failureMessage={recording.failureMessage}
segments={recording.segments}
+ speakerGroups={recording.speakerGroups}
+ speakerLabelsDropped={recording.speakerLabelsDropped}
initialSeconds={initialSeconds}
/>
diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/types.ts b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/types.ts
index 7fd333db..defb74bc 100644
--- a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/types.ts
+++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/types.ts
@@ -19,3 +19,8 @@ export type RecordingDeleteResponse =
export type RecordingRetryResponse =
| { status: 'success'; recording: RecordingViewModel }
| { status: 'error'; message: string }
+
+/** Wire contract of `POST .../gravacoes/[id]/falantes` (C200) — label a cluster. */
+export type RecordingSpeakerLabelResponse =
+ | { status: 'success'; labeled: true }
+ | { status: 'error'; message: string }
diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx
index c7a26486..8bfa9187 100644
--- a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx
+++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx
@@ -5,6 +5,7 @@ import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import { AcervoSourceToggle } from '@/components/campaign/recording/AcervoSourceToggle'
+import { RecordingPersonFilter } from '@/components/campaign/recording/RecordingPersonFilter'
import { RecordingResultList } from '@/components/campaign/recording/RecordingResultList'
import { RecordingStatusRefresher } from '@/components/campaign/recording/RecordingStatusRefresher'
import { RecordingUploadDialog } from '@/components/campaign/recording/RecordingUploadDialog'
@@ -77,9 +78,19 @@ const AcervoHeader = ({ source }: { source: 'camara' | 'enviadas' }) => (
)
-const RecordingsSearchForm = ({ query }: { query?: string }) => (
+const RecordingsSearchForm = ({
+ query,
+ people,
+}: {
+ query?: string
+ people?: readonly string[]
+}) => (
) : null}
+ {grouped ? (
+
+
+
+ {RECORDING_SPEAKER_INFO_BANNER_MOBILE}
+ {RECORDING_SPEAKER_INFO_BANNER}
+
+
+ ) : null}
+
{status === 'ready' && segments.length === 0 ? (
A transcrição desta gravação está vazia — o arquivo continua disponível para download.
@@ -178,48 +204,44 @@ export const RecordingDetailPlayer = ({
return (
{mediaColumn}
-
-
-
- Transcrição
-
-
Clique para posicionar
-
-
- {segments.map((segment) => {
- const active = activeStart === segment.startSeconds
- return (
+ {grouped ? (
+
+ ) : (
+
+
+
+ Transcrição
+
+
Clique para posicionar
+
+
+ {segments.map((segment) => (
- seekTo(segment.startSeconds)}
- className={cn(
- 'grid w-full grid-cols-[3.25rem_1fr] gap-3 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
- active && 'bg-muted',
- )}
- >
-
- {segment.startLabel}
-
-
-
-
-
+
- )
- })}
-
-
+ ))}
+
+
+ )}
)
}
diff --git a/src/components/campaign/recording/RecordingPersonFilter.tsx b/src/components/campaign/recording/RecordingPersonFilter.tsx
new file mode 100644
index 00000000..aab8dcbe
--- /dev/null
+++ b/src/components/campaign/recording/RecordingPersonFilter.tsx
@@ -0,0 +1,87 @@
+'use client'
+
+import { FunnelIcon, XIcon } from 'lucide-react'
+import { useOptimistic } from 'react'
+
+import {
+ CampaignHeaderFilterPopover,
+ type CampaignHeaderFilterRow,
+} from '@/components/campaign/shared/CampaignHeaderFilterPopover'
+import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation'
+import { foldSpeakerName } from '@/lib/recordingDiarization'
+import {
+ buildRecordingListHref,
+ removeRecordingPerson,
+ toggleRecordingPerson,
+ type RecordingListState,
+} from '@/utilities/recordings/recordingListUrl'
+
+const PERSON_FILTER_ID = 'recording-filter-person'
+
+/**
+ * C200 — the "Pessoa" facet of the uploaded recordings (design scene 3): one
+ * removable chip per selected label plus the multi-select popover backed by the
+ * labels actually present. Options come from the server (`speakerNames`), never
+ * from a free-form directory — the label is text, not a `Contact`.
+ */
+export const RecordingPersonFilter = ({
+ state,
+ options,
+}: {
+ state: RecordingListState
+ options: readonly string[]
+}) => {
+ const { navigate } = useCampaignListFilterNavigation({
+ state,
+ toHref: (next) => buildRecordingListHref(next, 1),
+ })
+ const [viewState, setOptimisticState] = useOptimistic(state)
+ const selected = viewState.people ?? []
+
+ const optionRows: CampaignHeaderFilterRow[] = options.map((option) => {
+ const next = toggleRecordingPerson(viewState, option)
+ return {
+ value: option,
+ label: option,
+ href: buildRecordingListHref(next, 1),
+ selected: selected.some((person) => foldSpeakerName(person) === foldSpeakerName(option)),
+ checkbox: true,
+ onChoose: () => setOptimisticState(next),
+ }
+ })
+
+ const clear = {
+ href: buildRecordingListHref({ ...viewState, page: 1, people: undefined }, 1),
+ onChoose: () => setOptimisticState({ ...viewState, page: 1, people: undefined }),
+ }
+
+ return (
+
+ 1 ? `Pessoa: ${selected.length}` : 'Pessoa'}
+ active={selected.length > 0}
+ closeOnChoose={false}
+ emptyLabel="Nenhuma pessoa identificada nas gravações."
+ optionRows={optionRows}
+ clear={selected.length ? clear : undefined}
+ />
+
+ {selected.map((person) => (
+ navigate(removeRecordingPerson(viewState, person))}
+ aria-label={`Remover filtro de ${person}`}
+ >
+
+ Pessoa: {person}
+
+
+ ))}
+
+ )
+}
diff --git a/src/components/campaign/recording/RecordingResultCard.tsx b/src/components/campaign/recording/RecordingResultCard.tsx
index fde414e0..52889dbb 100644
--- a/src/components/campaign/recording/RecordingResultCard.tsx
+++ b/src/components/campaign/recording/RecordingResultCard.tsx
@@ -1,4 +1,4 @@
-import { ChevronRightIcon, VideoIcon } from 'lucide-react'
+import { ChevronRightIcon, UserIcon, VideoIcon } from 'lucide-react'
import Link from 'next/link'
import { RecordingStatusBadge } from '@/components/campaign/recording/RecordingStatusBadge'
@@ -65,6 +65,21 @@ export const RecordingResultCard = ({ recording }: { recording: RecordingListIte
{STATE_COPY[recording.status]}
)}
+
+ {recording.matchedPersons.length > 0 ? (
+
+ {recording.matchedPersons.map((person) => (
+
+
+ {person}
+
+ ))}
+ aparece nesta gravação
+
+ ) : null}
{
+ const router = useRouter()
+ const [open, setOpen] = useState(false)
+ const [label, setLabel] = useState(group.label ?? '')
+ const [error, setError] = useState(null)
+ const [submitting, setSubmitting] = useState(false)
+
+ const handleOpenChange = (next: boolean) => {
+ setOpen(next)
+ if (next) {
+ setLabel(group.label ?? '')
+ setError(null)
+ }
+ }
+
+ const submit = async () => {
+ const trimmed = label.trim()
+ if (!trimmed) {
+ setError(RECORDING_SPEAKER_LABEL_REQUIRED_MESSAGE)
+ return
+ }
+ if (trimmed.length > RECORDING_SPEAKER_LABEL_MAX_LENGTH) {
+ setError(RECORDING_SPEAKER_LABEL_LONG_MESSAGE)
+ return
+ }
+
+ setSubmitting(true)
+ setError(null)
+ try {
+ const { ok, payload } = await postCampaignJson(
+ campaignRecordingSpeakersHref(recordingId),
+ { recordingId, speakerKey: group.key, label: trimmed },
+ )
+ if (!ok || payload.status !== 'success') {
+ setError(payload.status === 'error' ? payload.message : SAVE_ERROR_MESSAGE)
+ return
+ }
+ setOpen(false)
+ router.refresh()
+ } catch {
+ setError(SAVE_ERROR_MESSAGE)
+ } finally {
+ setSubmitting(false)
+ }
+ }
+
+ return (
+
+
+
+
+
+ {group.isIdentified ? RECORDING_SPEAKER_EDIT_ACTION : RECORDING_SPEAKER_IDENTIFY_ACTION}
+
+ {/* Mobile keeps the short verb; an identified group is icon-only. */}
+ {group.isIdentified ? null : Identificar }
+
+
+
+
+
+ {RECORDING_SPEAKER_DIALOG_TITLE}
+ Dê um nome ao agrupamento “{group.defaultLabel}”.
+
+
+
+
+
+ {RECORDING_SPEAKER_DIALOG_INTRO}
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/campaign/recording/RecordingSpeakerTranscript.tsx b/src/components/campaign/recording/RecordingSpeakerTranscript.tsx
new file mode 100644
index 00000000..1dfca065
--- /dev/null
+++ b/src/components/campaign/recording/RecordingSpeakerTranscript.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import { InfoIcon, UserIcon } from 'lucide-react'
+
+import { RecordingSpeakerDialog } from '@/components/campaign/recording/RecordingSpeakerDialog'
+import { RecordingTranscriptSegmentButton } from '@/components/campaign/recording/RecordingTranscriptSegmentButton'
+import {
+ RECORDING_SPEAKER_BANNER_BODY,
+ RECORDING_SPEAKER_BANNER_DROPPED,
+ RECORDING_SPEAKER_BANNER_TITLE,
+ RECORDING_SPEAKER_GROUP_IDENTIFIED,
+ RECORDING_SPEAKER_GROUP_UNIDENTIFIED,
+} from '@/lib/recording'
+import { cn } from '@/lib/utils'
+import type { RecordingSpeakerGroupViewModel } from '@/utilities/recordings/recordingViewModels'
+
+type RecordingSpeakerTranscriptProps = {
+ recordingId: number
+ groups: readonly RecordingSpeakerGroupViewModel[]
+ labelsDropped: boolean
+ /** `?t=` search hit or the segment under the player's current time. */
+ activeStart: number | null
+ onSeek: (seconds: number) => void
+}
+
+/**
+ * C200 — the grouped transcript of the recording detail (design scenes 1, 4 and
+ * 5): anonymous clusters in first-appearance order, the always-visible
+ * imprecision warning (the grouping is automatic; the team identifies) and the
+ * "Identificar falante" affordance per group.
+ */
+export const RecordingSpeakerTranscript = ({
+ recordingId,
+ groups,
+ labelsDropped,
+ activeStart,
+ onSeek,
+}: RecordingSpeakerTranscriptProps) => (
+
+
+
+
+
{RECORDING_SPEAKER_BANNER_TITLE}
+
{RECORDING_SPEAKER_BANNER_BODY}
+ {labelsDropped ? (
+
+ {RECORDING_SPEAKER_BANNER_DROPPED}
+
+ ) : null}
+
+
+
+
+
+ Transcrição por falante
+
+
+ Toque para posicionar
+ Clique para posicionar
+
+
+
+
+ {groups.map((group) => (
+
+
+
+
+
+
+
+
+ {group.label ?? group.defaultLabel}
+
+
+ {group.isIdentified
+ ? RECORDING_SPEAKER_GROUP_IDENTIFIED
+ : RECORDING_SPEAKER_GROUP_UNIDENTIFIED}
+
+
+
+
+
+
+ {group.segments.map((segment) => (
+
+ ))}
+
+ ))}
+
+
+)
diff --git a/src/components/campaign/recording/RecordingTranscriptSegmentButton.tsx b/src/components/campaign/recording/RecordingTranscriptSegmentButton.tsx
new file mode 100644
index 00000000..a0d4cab9
--- /dev/null
+++ b/src/components/campaign/recording/RecordingTranscriptSegmentButton.tsx
@@ -0,0 +1,51 @@
+'use client'
+
+import { SpeechHighlightParts } from '@/components/campaign/speech/SpeechHighlightParts'
+import { cn } from '@/lib/utils'
+import type { RecordingDetailSegmentViewModel } from '@/utilities/recordings/recordingViewModels'
+
+/**
+ * C200 — the clickable transcript line shared by the plain C199 list and the
+ * grouped C200 blocks: seeks the player to the segment and renders the search
+ * highlight. `variant` keeps the two approved paddings/sizes without a twin.
+ */
+export const RecordingTranscriptSegmentButton = ({
+ segment,
+ active,
+ variant = 'plain',
+ onSeek,
+}: {
+ segment: RecordingDetailSegmentViewModel
+ active: boolean
+ variant?: 'plain' | 'grouped'
+ onSeek: (seconds: number) => void
+}) => (
+ onSeek(segment.startSeconds)}
+ className={cn(
+ 'grid w-full grid-cols-[3.25rem_1fr] gap-3 text-left transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
+ variant === 'grouped' ? 'px-3 py-2.5' : 'rounded-lg px-2 py-1.5',
+ active && 'bg-muted',
+ )}
+ >
+
+ {segment.startLabel}
+
+
+
+
+
+)
diff --git a/src/lib/campaignPaths.ts b/src/lib/campaignPaths.ts
index 9d686958..3305e219 100644
--- a/src/lib/campaignPaths.ts
+++ b/src/lib/campaignPaths.ts
@@ -73,4 +73,8 @@ export const campaignRecordingRetryHref = (id: number): string =>
export const campaignRecordingDeleteHref = (id: number): string =>
`${campaignRecordingDetailHref(id)}/apagar`
+/** C200 — human identification of one speaker cluster of a recording. */
+export const campaignRecordingSpeakersHref = (id: number): string =>
+ `${campaignRecordingDetailHref(id)}/falantes`
+
export const CAMPAIGN_UPDATES_HREF = '/campanha/atualizacoes' as const
diff --git a/src/lib/recording.ts b/src/lib/recording.ts
index a2e3da47..eed402a4 100644
--- a/src/lib/recording.ts
+++ b/src/lib/recording.ts
@@ -30,6 +30,11 @@ export const isRecordingStatus = (value: unknown): value is RecordingStatus =>
/** One owner for the persisted field limits (collection, schema and dialog). */
export const RECORDING_TITLE_MAX_LENGTH = 200
+/** C200 — the human label the team types for one speaker cluster. */
+export const RECORDING_SPEAKER_LABEL_MAX_LENGTH = 120
+export const RECORDING_SPEAKER_LABEL_REQUIRED_MESSAGE = 'Informe o nome do falante.'
+export const RECORDING_SPEAKER_LABEL_LONG_MESSAGE = `O nome do falante deve ter até ${RECORDING_SPEAKER_LABEL_MAX_LENGTH} caracteres.`
+
/** Upload ceiling: hours of plenary are the real case, 4 GiB is the guard. */
export const RECORDING_MAX_BYTES = 4 * 1024 * 1024 * 1024
export const RECORDING_MAX_SIZE_LABEL = '4 GB'
@@ -90,6 +95,31 @@ export const canServeRecordingMedia = (status: RecordingStatus): boolean => stat
/** The retry button only applies to a failed transcription. */
export const canRetryRecording = (status: RecordingStatus): boolean => status === 'failed'
+/** C200 — dialog copy of the human identification step (design scenes 2/5). */
+export const RECORDING_SPEAKER_DIALOG_TITLE = 'Identificar falante'
+export const RECORDING_SPEAKER_DIALOG_INTRO =
+ 'Ouça um trecho e informe quem é a pessoa. O acervo não sugere nem identifica pessoas automaticamente.'
+export const RECORDING_SPEAKER_DIALOG_HELP =
+ 'O nome será aplicado a todas as falas deste agrupamento nesta gravação.'
+export const RECORDING_SPEAKER_LABEL_FIELD_LABEL = 'Nome do falante'
+export const RECORDING_SPEAKER_LABEL_PLACEHOLDER = 'Digite o nome do falante'
+export const RECORDING_SPEAKER_SAVE_LABEL = 'Salvar identificação'
+
+/** C200 — the transcript banner of the grouped detail (design scenes 1/4/5). */
+export const RECORDING_SPEAKER_INFO_BANNER =
+ 'Os falantes são agrupados automaticamente pelo áudio. A identificação de cada pessoa é feita pela equipe.'
+export const RECORDING_SPEAKER_INFO_BANNER_MOBILE =
+ 'Agrupamento automático e aproximado. A identificação é feita pela equipe.'
+export const RECORDING_SPEAKER_BANNER_TITLE = 'A separação por falante pode estar imprecisa'
+export const RECORDING_SPEAKER_BANNER_BODY =
+ 'O agrupamento é automático e aproximado. Ruído e falas sobrepostas podem colocar trechos no agrupamento errado. A equipe continua responsável por identificar cada falante.'
+export const RECORDING_SPEAKER_BANNER_DROPPED =
+ 'Esta gravação foi reprocessada e algumas identificações não puderam ser mantidas. Confira os falantes.'
+export const RECORDING_SPEAKER_GROUP_UNIDENTIFIED = 'Agrupamento sem identificação'
+export const RECORDING_SPEAKER_GROUP_IDENTIFIED = 'Identificação feita pela equipe'
+export const RECORDING_SPEAKER_IDENTIFY_ACTION = 'Identificar falante'
+export const RECORDING_SPEAKER_EDIT_ACTION = 'Editar identificação'
+
/** Errors of the upload form (client validation). Wire messages live in schemas. */
export const RECORDING_FILE_REQUIRED_MESSAGE = 'Escolha um arquivo de vídeo para enviar.'
export const RECORDING_TITLE_REQUIRED_MESSAGE = 'Informe um título para a gravação.'
diff --git a/src/lib/recordingDiarization.ts b/src/lib/recordingDiarization.ts
new file mode 100644
index 00000000..91b79ce1
--- /dev/null
+++ b/src/lib/recordingDiarization.ts
@@ -0,0 +1,241 @@
+/**
+ * C200 — pure speaker-grouping rules of an uploaded recording. The diarization
+ * provider returns anonymous turns (acoustic clusters); this module assigns
+ * each transcript segment to the turn it overlaps and numbers the clusters
+ * globally by first appearance. Identification is human: nothing here matches a
+ * voice to a person — the labels are text the team types.
+ *
+ * No I/O and no `server-only`: the job, the view models and the unit tests
+ * share this module. `reconcileSpeakerLabels` is the reprocessing contract: a
+ * new run cannot re-associate a voice (no biometrics), so a label survives only
+ * when an old cluster maps 1:1 to a new one by time overlap; the rest is
+ * reported as dropped so the detail can warn instead of silently forgetting.
+ */
+
+/** One anonymous speaker turn as the provider returns it, in absolute seconds. */
+export type DiarizedTurn = {
+ speaker: string
+ startSeconds: number
+ endSeconds: number
+}
+
+/** A segment with the cluster key it was assigned to; null if no turn covers it. */
+export type SpeakerKeyed = {
+ speakerKey: string | null
+}
+
+/** A label the team typed for one cluster key of one recording. */
+export type RecordingSpeakerLabel = {
+ speakerKey: string
+ label: string
+}
+
+/** A span used by the alignment/reconciliation math (segment or cluster). */
+export type TimedSpan = {
+ startSeconds: number
+ endSeconds: number
+}
+
+/**
+ * Overlap in seconds between two spans; zero when they do not touch. A
+ * degenerate span (end before start) is clamped to its start, never negative.
+ */
+const overlapSeconds = (a: TimedSpan, b: TimedSpan): number =>
+ Math.max(0, Math.min(a.endSeconds, b.endSeconds) - Math.max(a.startSeconds, b.startSeconds))
+
+/** Gap in seconds between two spans; zero when they overlap or touch. */
+const gapSeconds = (a: TimedSpan, b: TimedSpan): number =>
+ Math.max(0, Math.max(a.startSeconds, b.startSeconds) - Math.min(a.endSeconds, b.endSeconds))
+
+/** Closest boundary distance used to break overlap ties by midpoint proximity. */
+const midpointDistance = (a: TimedSpan, b: TimedSpan): number =>
+ Math.abs((a.startSeconds + a.endSeconds) / 2 - (b.startSeconds + b.endSeconds) / 2)
+
+/**
+ * Picks the turn of one segment: the largest overlap wins, ties go to the turn
+ * whose midpoint sits closest to the segment's, and a segment that overlaps no
+ * turn at all falls back to the nearest turn by gap (the audio is contiguous,
+ * so an isolated segment belongs to the closest voice). Invalid turns are
+ * ignored by the caller.
+ */
+const pickTurn = (segment: TimedSpan, turns: readonly DiarizedTurn[]): number => {
+ let best = -1
+ let bestOverlap = 0
+ let bestDistance = Number.POSITIVE_INFINITY
+ for (const [turnIndex, turn] of turns.entries()) {
+ const overlap = overlapSeconds(segment, turn)
+ const distance = midpointDistance(segment, turn)
+ const wins =
+ overlap > bestOverlap || (overlap > 0 && overlap === bestOverlap && distance < bestDistance)
+ if (wins) {
+ best = turnIndex
+ bestOverlap = overlap
+ bestDistance = distance
+ }
+ }
+ if (bestOverlap > 0) return best
+
+ let nearest = 0
+ let nearestGap = Number.POSITIVE_INFINITY
+ for (const [turnIndex, turn] of turns.entries()) {
+ const gap = gapSeconds(segment, turn)
+ if (gap < nearestGap) {
+ nearest = turnIndex
+ nearestGap = gap
+ }
+ }
+ return nearest
+}
+
+/**
+ * Assigns each segment a `speaker-N` key following the acoustic turns. The
+ * numbering is global to the recording and follows the first appearance of the
+ * cluster in segment order — two runs over the same audio produce the same
+ * keys, and a segment with no usable turn keeps a null key (the detail renders
+ * the plain transcript then). Pure: tests do not touch the provider.
+ */
+export const assignSpeakerKeys = (
+ segments: readonly Segment[],
+ turns: readonly DiarizedTurn[],
+): (Segment & SpeakerKeyed)[] => {
+ const validTurns = turns.filter(
+ (turn) =>
+ typeof turn.speaker === 'string' &&
+ turn.speaker.length > 0 &&
+ Number.isFinite(turn.startSeconds) &&
+ Number.isFinite(turn.endSeconds),
+ )
+ if (validTurns.length === 0) {
+ return segments.map((segment) => ({ ...segment, speakerKey: null }))
+ }
+
+ const clusterKeys = new Map()
+ const keyOfTurn = (turn: DiarizedTurn): string => {
+ const existing = clusterKeys.get(turn.speaker)
+ if (existing) return existing
+ const key = `speaker-${clusterKeys.size + 1}`
+ clusterKeys.set(turn.speaker, key)
+ return key
+ }
+
+ return segments.map((segment) => {
+ const turn = validTurns[pickTurn(segment, validTurns)]
+ return { ...segment, speakerKey: turn ? keyOfTurn(turn) : null }
+ })
+}
+
+/** First-appearance order of the keys in a keyed segment list. */
+export const speakerKeysInOrder = (segments: readonly SpeakerKeyed[]): string[] => {
+ const keys: string[] = []
+ const seen = new Set()
+ for (const segment of segments) {
+ if (!segment.speakerKey || seen.has(segment.speakerKey)) continue
+ seen.add(segment.speakerKey)
+ keys.push(segment.speakerKey)
+ }
+ return keys
+}
+
+/**
+ * Reprocessing contract (D6): a previous label is kept only on a mutual 1:1
+ * time-overlap match — one old cluster maps to exactly one new cluster and that
+ * new cluster receives exactly one old label. A split (one old cluster became
+ * two) or a merge (two old clusters became one) is uncertainty, so the affected
+ * labels are dropped and reported instead of naming the wrong voice. Output
+ * labels follow the new segment order.
+ */
+export const reconcileSpeakerLabels = ({
+ previousLabels,
+ previousSegments,
+ nextSegments,
+}: {
+ previousLabels: readonly RecordingSpeakerLabel[]
+ previousSegments: readonly (TimedSpan & SpeakerKeyed)[]
+ nextSegments: readonly (TimedSpan & SpeakerKeyed)[]
+}): { labels: RecordingSpeakerLabel[]; dropped: boolean } => {
+ if (previousLabels.length === 0) return { labels: [], dropped: false }
+
+ const nextKeys = speakerKeysInOrder(nextSegments)
+ if (nextKeys.length === 0) return { labels: [], dropped: true }
+
+ const previousKeys = speakerKeysInOrder(previousSegments)
+ const overlapByPair = new Map()
+ for (const previous of previousSegments) {
+ if (!previous.speakerKey) continue
+ for (const next of nextSegments) {
+ if (!next.speakerKey) continue
+ const pair = `${previous.speakerKey}\u0000${next.speakerKey}`
+ overlapByPair.set(pair, (overlapByPair.get(pair) ?? 0) + overlapSeconds(previous, next))
+ }
+ }
+
+ const labelByPreviousKey = new Map(previousLabels.map((entry) => [entry.speakerKey, entry.label]))
+ const claimantsOfNextKey = (nextKey: string): string[] =>
+ previousKeys.filter(
+ (previousKey) => (overlapByPair.get(`${previousKey}\u0000${nextKey}`) ?? 0) > 0,
+ )
+
+ const kept = new Map()
+ for (const nextKey of nextKeys) {
+ const claimants = claimantsOfNextKey(nextKey)
+ const [previousKey] = claimants
+ if (claimants.length !== 1 || !previousKey) continue
+ const label = labelByPreviousKey.get(previousKey)
+ if (!label) continue
+ // Mutual uniqueness: the same old cluster must not also map elsewhere.
+ const matchesOfPrevious = nextKeys.filter(
+ (candidate) => (overlapByPair.get(`${previousKey}\u0000${candidate}`) ?? 0) > 0,
+ )
+ if (matchesOfPrevious.length !== 1) continue
+ kept.set(nextKey, label)
+ }
+
+ const labels = [...kept.entries()].map(([speakerKey, label]) => ({ speakerKey, label }))
+ return { labels, dropped: previousLabels.length > labels.length }
+}
+
+/**
+ * Distinct labels of a recording, case-insensitive and trimmed — the
+ * denormalized `speakerNames` used by the "Pessoa" facet. Order of first
+ * appearance; empty labels never count.
+ */
+export const speakerNamesFromLabels = (
+ labels: readonly RecordingSpeakerLabel[] | null | undefined,
+): string[] => {
+ const names: string[] = []
+ const seen = new Set()
+ for (const entry of labels ?? []) {
+ const name = typeof entry?.label === 'string' ? entry.label.trim() : ''
+ if (!name) continue
+ const folded = foldSpeakerName(name)
+ if (seen.has(folded)) continue
+ seen.add(folded)
+ names.push(name)
+ }
+ return names
+}
+
+/** The default public name of one cluster: always numbered, never a person. */
+export const recordingSpeakerDefaultLabel = (index: number): string => `Falante ${index}`
+
+/**
+ * Case-fold used by every speaker-name comparison: the facet query is
+ * case-insensitive (ILIKE), so parsing, dedupe and chip matching share ONE
+ * fold to never disagree.
+ */
+export const foldSpeakerName = (value: string): string => value.toLocaleLowerCase('pt-BR')
+
+/**
+ * Case-insensitive containment of one selected "Pessoa" term in one label —
+ * the same match the facet query runs (`contains` → ILIKE), so the card chip
+ * and the result set never disagree.
+ */
+export const speakerNameMatches = (name: string, filter: string): boolean =>
+ foldSpeakerName(name).includes(foldSpeakerName(filter))
+
+/**
+ * Provider ceiling for one diarization call (AssemblyAI accepts up to 10 h per
+ * file). Longer recordings skip the step and keep the honest single-block
+ * transcript instead of guessing speakers.
+ */
+export const RECORDING_DIARIZATION_MAX_SECONDS = 10 * 60 * 60
diff --git a/src/lib/recordingTranscription.ts b/src/lib/recordingTranscription.ts
index 084c9b15..6703f29c 100644
--- a/src/lib/recordingTranscription.ts
+++ b/src/lib/recordingTranscription.ts
@@ -34,17 +34,11 @@ export type RecordingTranscriptSegment = {
}
/**
- * Extracts a mono 16 kHz MP3 and splits it into fixed chunks in one ffmpeg
- * pass. Args are returned as an array — never a shell string; `outputPattern`
- * carries the `%03d` sequence ffmpeg replaces per chunk.
+ * The shared extraction prefix of both ffmpeg passes: mono 16 kHz MP3, at the
+ * bitrate the ASR provider and the diarization provider both accept. Args are
+ * returned as an array — never a shell string.
*/
-export const buildRecordingAudioFfmpegArgs = ({
- inputPath,
- outputPattern,
-}: {
- inputPath: string
- outputPattern: string
-}): string[] => [
+const recordingAudioArgs = (inputPath: string): string[] => [
'-nostdin',
'-hide_banner',
'-y',
@@ -59,6 +53,20 @@ export const buildRecordingAudioFfmpegArgs = ({
'libmp3lame',
'-b:a',
RECORDING_AUDIO_BITRATE,
+]
+
+/**
+ * Extracts a mono 16 kHz MP3 and splits it into fixed chunks in one ffmpeg
+ * pass. `outputPattern` carries the `%03d` sequence ffmpeg replaces per chunk.
+ */
+export const buildRecordingAudioFfmpegArgs = ({
+ inputPath,
+ outputPattern,
+}: {
+ inputPath: string
+ outputPattern: string
+}): string[] => [
+ ...recordingAudioArgs(inputPath),
'-f',
'segment',
'-segment_time',
@@ -68,6 +76,20 @@ export const buildRecordingAudioFfmpegArgs = ({
outputPattern,
]
+/**
+ * Extracts the whole recording as one mono 16 kHz MP3 (C200): the diarization
+ * provider needs the entire audio in a single call so the speaker numbering is
+ * global — a per-chunk run would renumber speakers that never relate across
+ * chunks. Same audio parameters as the chunked pass.
+ */
+export const buildRecordingFullAudioFfmpegArgs = ({
+ inputPath,
+ outputPath,
+}: {
+ inputPath: string
+ outputPath: string
+}): string[] => [...recordingAudioArgs(inputPath), outputPath]
+
/**
* Merges every chunk into one ordered transcript: each segment is shifted by
* its chunk offset, empty text is dropped and a degenerate span (end before
diff --git a/src/lib/schemas/recording.ts b/src/lib/schemas/recording.ts
index 201fef58..e574776c 100644
--- a/src/lib/schemas/recording.ts
+++ b/src/lib/schemas/recording.ts
@@ -1,6 +1,12 @@
import { z } from 'zod'
-import { RECORDING_TITLE_MAX_LENGTH, RECORDING_TITLE_REQUIRED_MESSAGE } from '@/lib/recording'
+import {
+ RECORDING_SPEAKER_LABEL_LONG_MESSAGE,
+ RECORDING_SPEAKER_LABEL_MAX_LENGTH,
+ RECORDING_SPEAKER_LABEL_REQUIRED_MESSAGE,
+ RECORDING_TITLE_MAX_LENGTH,
+ RECORDING_TITLE_REQUIRED_MESSAGE,
+} from '@/lib/recording'
import { trimmedOptionalText } from '@/lib/schemas/primitives'
/** The acervo gate refused the actor — one literal shared with the vertical. */
@@ -49,6 +55,23 @@ export const recordingRetryRequestSchema = z.object({
recordingId: z.number().int().positive(),
})
+/** C200 — the team names one acoustic cluster of a recording. */
+export const RECORDING_SPEAKER_UNKNOWN_MESSAGE =
+ 'Este agrupamento de falantes não existe mais nesta gravação.'
+
+export const recordingSpeakerLabelRequestSchema = z.object({
+ recordingId: z.number().int().positive(),
+ speakerKey: z
+ .string()
+ .trim()
+ .regex(/^speaker-\d{1,3}$/, RECORDING_SPEAKER_UNKNOWN_MESSAGE),
+ label: z
+ .string()
+ .trim()
+ .min(1, RECORDING_SPEAKER_LABEL_REQUIRED_MESSAGE)
+ .max(RECORDING_SPEAKER_LABEL_MAX_LENGTH, RECORDING_SPEAKER_LABEL_LONG_MESSAGE),
+})
+
/** Delete one recording (row + segments + private media). */
export const recordingDeleteRequestSchema = z.object({
recordingId: z.number().int().positive(),
diff --git a/src/migrations/20260919_032729_add_recording_speakers.json b/src/migrations/20260919_032729_add_recording_speakers.json
new file mode 100644
index 00000000..57fe3c41
--- /dev/null
+++ b/src/migrations/20260919_032729_add_recording_speakers.json
@@ -0,0 +1,16760 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.users_roles": {
+ "name": "users_roles",
+ "schema": "",
+ "columns": {
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "enum_users_roles",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "users_roles_order_idx": {
+ "name": "users_roles_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_roles_parent_idx": {
+ "name": "users_roles_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "users_roles_parent_fk": {
+ "name": "users_roles_parent_fk",
+ "tableFrom": "users_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users_sessions": {
+ "name": "users_sessions",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "users_sessions_order_idx": {
+ "name": "users_sessions_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_sessions_parent_id_idx": {
+ "name": "users_sessions_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "users_sessions_parent_id_fk": {
+ "name": "users_sessions_parent_id_fk",
+ "tableFrom": "users_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reset_password_token": {
+ "name": "reset_password_token",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reset_password_expiration": {
+ "name": "reset_password_expiration",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hash": {
+ "name": "hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "login_attempts": {
+ "name": "login_attempts",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "lock_until": {
+ "name": "lock_until",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "users_updated_at_idx": {
+ "name": "users_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_created_at_idx": {
+ "name": "users_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_email_idx": {
+ "name": "users_email_idx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_user_sessions": {
+ "name": "campaign_user_sessions",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "campaign_user_sessions_order_idx": {
+ "name": "campaign_user_sessions_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_sessions_parent_id_idx": {
+ "name": "campaign_user_sessions_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_user_sessions_parent_id_fk": {
+ "name": "campaign_user_sessions_parent_id_fk",
+ "tableFrom": "campaign_user_sessions",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_user": {
+ "name": "campaign_user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_id": {
+ "name": "avatar_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum_campaign_user_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'leader'"
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "enum_campaign_user_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'carteira'"
+ },
+ "editing": {
+ "name": "editing",
+ "type": "enum_campaign_user_editing",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'carteira'"
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reset_password_token": {
+ "name": "reset_password_token",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reset_password_expiration": {
+ "name": "reset_password_expiration",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hash": {
+ "name": "hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "login_attempts": {
+ "name": "login_attempts",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "lock_until": {
+ "name": "lock_until",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "campaign_user_contact_idx": {
+ "name": "campaign_user_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_avatar_idx": {
+ "name": "campaign_user_avatar_idx",
+ "columns": [
+ {
+ "expression": "avatar_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_phone_idx": {
+ "name": "campaign_user_phone_idx",
+ "columns": [
+ {
+ "expression": "phone",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_updated_at_idx": {
+ "name": "campaign_user_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_created_at_idx": {
+ "name": "campaign_user_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_email_idx": {
+ "name": "campaign_user_email_idx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_user_username_idx": {
+ "name": "campaign_user_username_idx",
+ "columns": [
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_user_contact_id_contact_id_fk": {
+ "name": "campaign_user_contact_id_contact_id_fk",
+ "tableFrom": "campaign_user",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_user_avatar_id_media_id_fk": {
+ "name": "campaign_user_avatar_id_media_id_fk",
+ "tableFrom": "campaign_user",
+ "tableTo": "media",
+ "columnsFrom": [
+ "avatar_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_web_authn_credential": {
+ "name": "campaign_web_authn_credential",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "transports": {
+ "name": "transports",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_label": {
+ "name": "device_label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "campaign_web_authn_credential_user_idx": {
+ "name": "campaign_web_authn_credential_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_web_authn_credential_credential_id_idx": {
+ "name": "campaign_web_authn_credential_credential_id_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_web_authn_credential_updated_at_idx": {
+ "name": "campaign_web_authn_credential_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_web_authn_credential_created_at_idx": {
+ "name": "campaign_web_authn_credential_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_web_authn_credential_user_id_campaign_user_id_fk": {
+ "name": "campaign_web_authn_credential_user_id_campaign_user_id_fk",
+ "tableFrom": "campaign_web_authn_credential",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "recipient_id": {
+ "name": "recipient_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "enum_notification_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "read_at": {
+ "name": "read_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "notification_recipient_idx": {
+ "name": "notification_recipient_idx",
+ "columns": [
+ {
+ "expression": "recipient_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "notification_type_idx": {
+ "name": "notification_type_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "notification_municipality_idx": {
+ "name": "notification_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "notification_read_at_idx": {
+ "name": "notification_read_at_idx",
+ "columns": [
+ {
+ "expression": "read_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "notification_updated_at_idx": {
+ "name": "notification_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "notification_created_at_idx": {
+ "name": "notification_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "notification_recipient_id_campaign_user_id_fk": {
+ "name": "notification_recipient_id_campaign_user_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "recipient_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "notification_municipality_id_municipality_id_fk": {
+ "name": "notification_municipality_id_municipality_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_subscription": {
+ "name": "push_subscription",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p256dh": {
+ "name": "p256dh",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth": {
+ "name": "auth",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiration_time": {
+ "name": "expiration_time",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consent_content_hash": {
+ "name": "consent_content_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consented_at": {
+ "name": "consented_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_subscription_user_idx": {
+ "name": "push_subscription_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_subscription_endpoint_idx": {
+ "name": "push_subscription_endpoint_idx",
+ "columns": [
+ {
+ "expression": "endpoint",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_subscription_consent_idx": {
+ "name": "push_subscription_consent_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_subscription_updated_at_idx": {
+ "name": "push_subscription_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_subscription_created_at_idx": {
+ "name": "push_subscription_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_subscription_user_id_campaign_user_id_fk": {
+ "name": "push_subscription_user_id_campaign_user_id_fk",
+ "tableFrom": "push_subscription",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "push_subscription_consent_id_consent_id_fk": {
+ "name": "push_subscription_consent_id_consent_id_fk",
+ "tableFrom": "push_subscription",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_invite": {
+ "name": "campaign_invite",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "enum_campaign_invite_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "campaign_invite_token_hash_idx": {
+ "name": "campaign_invite_token_hash_idx",
+ "columns": [
+ {
+ "expression": "token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_leadership_idx": {
+ "name": "campaign_invite_leadership_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_kind_idx": {
+ "name": "campaign_invite_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_expires_at_idx": {
+ "name": "campaign_invite_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_used_at_idx": {
+ "name": "campaign_invite_used_at_idx",
+ "columns": [
+ {
+ "expression": "used_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_revoked_at_idx": {
+ "name": "campaign_invite_revoked_at_idx",
+ "columns": [
+ {
+ "expression": "revoked_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_created_by_idx": {
+ "name": "campaign_invite_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_updated_at_idx": {
+ "name": "campaign_invite_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_invite_created_at_idx": {
+ "name": "campaign_invite_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_kind_idx": {
+ "name": "leadership_kind_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_invite_leadership_id_leadership_id_fk": {
+ "name": "campaign_invite_leadership_id_leadership_id_fk",
+ "tableFrom": "campaign_invite",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_invite_created_by_id_campaign_user_id_fk": {
+ "name": "campaign_invite_created_by_id_campaign_user_id_fk",
+ "tableFrom": "campaign_invite",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_vote_summary_snapshot": {
+ "name": "campaign_vote_summary_snapshot",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "day": {
+ "name": "day",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_key": {
+ "name": "scope_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'statewide'"
+ },
+ "staff_vote_total_central": {
+ "name": "staff_vote_total_central",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "campaign_vote_summary_snapshot_day_idx": {
+ "name": "campaign_vote_summary_snapshot_day_idx",
+ "columns": [
+ {
+ "expression": "day",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_vote_summary_snapshot_scope_key_idx": {
+ "name": "campaign_vote_summary_snapshot_scope_key_idx",
+ "columns": [
+ {
+ "expression": "scope_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_vote_summary_snapshot_updated_at_idx": {
+ "name": "campaign_vote_summary_snapshot_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_vote_summary_snapshot_created_at_idx": {
+ "name": "campaign_vote_summary_snapshot_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "day_scopeKey_idx": {
+ "name": "day_scopeKey_idx",
+ "columns": [
+ {
+ "expression": "day",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scope_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality_strengths": {
+ "name": "municipality_strengths",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "municipality_strengths_order_idx": {
+ "name": "municipality_strengths_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_strengths_parent_id_idx": {
+ "name": "municipality_strengths_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_strengths_parent_id_fk": {
+ "name": "municipality_strengths_parent_id_fk",
+ "tableFrom": "municipality_strengths",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality_risks": {
+ "name": "municipality_risks",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "municipality_risks_order_idx": {
+ "name": "municipality_risks_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_risks_parent_id_idx": {
+ "name": "municipality_risks_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_risks_parent_id_fk": {
+ "name": "municipality_risks_parent_id_fk",
+ "tableFrom": "municipality_risks",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality": {
+ "name": "municipality",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "enum_municipality_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ibge_code": {
+ "name": "ibge_code",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tse_city_code": {
+ "name": "tse_city_code",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "zone_number": {
+ "name": "zone_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "enum_municipality_priority",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'normal'"
+ },
+ "engagement_level": {
+ "name": "engagement_level",
+ "type": "enum_municipality_engagement_level",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "level_note": {
+ "name": "level_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "level_changed_at": {
+ "name": "level_changed_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_votes_pessimistic": {
+ "name": "expected_votes_pessimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_votes_central": {
+ "name": "expected_votes_central",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_votes_optimistic": {
+ "name": "expected_votes_optimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "political_trend_status": {
+ "name": "political_trend_status",
+ "type": "enum_municipality_political_trend_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "political_trend_note": {
+ "name": "political_trend_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "political_trend_recorded_by_id": {
+ "name": "political_trend_recorded_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "political_trend_recorded_at": {
+ "name": "political_trend_recorded_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dobradinha_notes": {
+ "name": "dobradinha_notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_steps": {
+ "name": "next_steps",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "budget_notes": {
+ "name": "budget_notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_update_at": {
+ "name": "last_update_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "municipality_name_idx": {
+ "name": "municipality_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_slug_idx": {
+ "name": "municipality_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_kind_idx": {
+ "name": "municipality_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_city_idx": {
+ "name": "municipality_city_idx",
+ "columns": [
+ {
+ "expression": "city",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_region_idx": {
+ "name": "municipality_region_idx",
+ "columns": [
+ {
+ "expression": "region",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_ibge_code_idx": {
+ "name": "municipality_ibge_code_idx",
+ "columns": [
+ {
+ "expression": "ibge_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_tse_city_code_idx": {
+ "name": "municipality_tse_city_code_idx",
+ "columns": [
+ {
+ "expression": "tse_city_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_zone_number_idx": {
+ "name": "municipality_zone_number_idx",
+ "columns": [
+ {
+ "expression": "zone_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_priority_idx": {
+ "name": "municipality_priority_idx",
+ "columns": [
+ {
+ "expression": "priority",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_engagement_level_idx": {
+ "name": "municipality_engagement_level_idx",
+ "columns": [
+ {
+ "expression": "engagement_level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_level_changed_at_idx": {
+ "name": "municipality_level_changed_at_idx",
+ "columns": [
+ {
+ "expression": "level_changed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_political_trend_political_trend_recorded_by_idx": {
+ "name": "municipality_political_trend_political_trend_recorded_by_idx",
+ "columns": [
+ {
+ "expression": "political_trend_recorded_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_last_update_at_idx": {
+ "name": "municipality_last_update_at_idx",
+ "columns": [
+ {
+ "expression": "last_update_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_updated_at_idx": {
+ "name": "municipality_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_created_at_idx": {
+ "name": "municipality_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_political_trend_recorded_by_id_campaign_user_id_fk": {
+ "name": "municipality_political_trend_recorded_by_id_campaign_user_id_fk",
+ "tableFrom": "municipality",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "political_trend_recorded_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality_rels": {
+ "name": "municipality_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_deputy_id": {
+ "name": "state_deputy_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "municipality_rels_order_idx": {
+ "name": "municipality_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_rels_parent_idx": {
+ "name": "municipality_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_rels_path_idx": {
+ "name": "municipality_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_rels_campaign_user_id_idx": {
+ "name": "municipality_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_rels_state_deputy_id_idx": {
+ "name": "municipality_rels_state_deputy_id_idx",
+ "columns": [
+ {
+ "expression": "state_deputy_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_rels_parent_fk": {
+ "name": "municipality_rels_parent_fk",
+ "tableFrom": "municipality_rels",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "municipality_rels_campaign_user_fk": {
+ "name": "municipality_rels_campaign_user_fk",
+ "tableFrom": "municipality_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "municipality_rels_state_deputy_fk": {
+ "name": "municipality_rels_state_deputy_fk",
+ "tableFrom": "municipality_rels",
+ "tableTo": "state_deputy",
+ "columnsFrom": [
+ "state_deputy_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.leadership": {
+ "name": "leadership",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "exclusive": {
+ "name": "exclusive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "support_status": {
+ "name": "support_status",
+ "type": "enum_leadership_support_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'a_abordar'"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_content_hash": {
+ "name": "consent_content_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consented_at": {
+ "name": "consented_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "leadership_contact_idx": {
+ "name": "leadership_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_exclusive_idx": {
+ "name": "leadership_exclusive_idx",
+ "columns": [
+ {
+ "expression": "exclusive",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_support_status_idx": {
+ "name": "leadership_support_status_idx",
+ "columns": [
+ {
+ "expression": "support_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_user_idx": {
+ "name": "leadership_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_consent_idx": {
+ "name": "leadership_consent_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_created_by_idx": {
+ "name": "leadership_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_updated_at_idx": {
+ "name": "leadership_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_created_at_idx": {
+ "name": "leadership_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "leadership_contact_id_contact_id_fk": {
+ "name": "leadership_contact_id_contact_id_fk",
+ "tableFrom": "leadership",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "leadership_user_id_campaign_user_id_fk": {
+ "name": "leadership_user_id_campaign_user_id_fk",
+ "tableFrom": "leadership",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "leadership_consent_id_consent_id_fk": {
+ "name": "leadership_consent_id_consent_id_fk",
+ "tableFrom": "leadership",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "leadership_created_by_id_campaign_user_id_fk": {
+ "name": "leadership_created_by_id_campaign_user_id_fk",
+ "tableFrom": "leadership",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.leadership_rels": {
+ "name": "leadership_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_deputy_id": {
+ "name": "state_deputy_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "leadership_rels_order_idx": {
+ "name": "leadership_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_parent_idx": {
+ "name": "leadership_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_path_idx": {
+ "name": "leadership_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_municipality_id_idx": {
+ "name": "leadership_rels_municipality_id_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_organization_id_idx": {
+ "name": "leadership_rels_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_state_deputy_id_idx": {
+ "name": "leadership_rels_state_deputy_id_idx",
+ "columns": [
+ {
+ "expression": "state_deputy_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_rels_campaign_user_id_idx": {
+ "name": "leadership_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "leadership_rels_parent_fk": {
+ "name": "leadership_rels_parent_fk",
+ "tableFrom": "leadership_rels",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "leadership_rels_municipality_fk": {
+ "name": "leadership_rels_municipality_fk",
+ "tableFrom": "leadership_rels",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "leadership_rels_organization_fk": {
+ "name": "leadership_rels_organization_fk",
+ "tableFrom": "leadership_rels",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "leadership_rels_state_deputy_fk": {
+ "name": "leadership_rels_state_deputy_fk",
+ "tableFrom": "leadership_rels",
+ "tableTo": "state_deputy",
+ "columnsFrom": [
+ "state_deputy_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "leadership_rels_campaign_user_fk": {
+ "name": "leadership_rels_campaign_user_fk",
+ "tableFrom": "leadership_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "enum_organization_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notes": {
+ "name": "notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "organization_name_idx": {
+ "name": "organization_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_slug_idx": {
+ "name": "organization_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_kind_idx": {
+ "name": "organization_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_created_by_idx": {
+ "name": "organization_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_updated_at_idx": {
+ "name": "organization_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_created_at_idx": {
+ "name": "organization_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_created_by_id_campaign_user_id_fk": {
+ "name": "organization_created_by_id_campaign_user_id_fk",
+ "tableFrom": "organization",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_rels": {
+ "name": "organization_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "organization_rels_order_idx": {
+ "name": "organization_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_rels_parent_idx": {
+ "name": "organization_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_rels_path_idx": {
+ "name": "organization_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organization_rels_municipality_id_idx": {
+ "name": "organization_rels_municipality_id_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_rels_parent_fk": {
+ "name": "organization_rels_parent_fk",
+ "tableFrom": "organization_rels",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_rels_municipality_fk": {
+ "name": "organization_rels_municipality_fk",
+ "tableFrom": "organization_rels",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.state_deputy": {
+ "name": "state_deputy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "party": {
+ "name": "party",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ballot_name": {
+ "name": "ballot_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "state_deputy_contact_idx": {
+ "name": "state_deputy_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_slug_idx": {
+ "name": "state_deputy_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_party_idx": {
+ "name": "state_deputy_party_idx",
+ "columns": [
+ {
+ "expression": "party",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_created_by_idx": {
+ "name": "state_deputy_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_updated_at_idx": {
+ "name": "state_deputy_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_created_at_idx": {
+ "name": "state_deputy_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "state_deputy_contact_id_contact_id_fk": {
+ "name": "state_deputy_contact_id_contact_id_fk",
+ "tableFrom": "state_deputy",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "state_deputy_created_by_id_campaign_user_id_fk": {
+ "name": "state_deputy_created_by_id_campaign_user_id_fk",
+ "tableFrom": "state_deputy",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.state_deputy_rels": {
+ "name": "state_deputy_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "state_deputy_rels_order_idx": {
+ "name": "state_deputy_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_rels_parent_idx": {
+ "name": "state_deputy_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_rels_path_idx": {
+ "name": "state_deputy_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "state_deputy_rels_campaign_user_id_idx": {
+ "name": "state_deputy_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "state_deputy_rels_parent_fk": {
+ "name": "state_deputy_rels_parent_fk",
+ "tableFrom": "state_deputy_rels",
+ "tableTo": "state_deputy",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "state_deputy_rels_campaign_user_fk": {
+ "name": "state_deputy_rels_campaign_user_fk",
+ "tableFrom": "state_deputy_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vote_pledge": {
+ "name": "vote_pledge",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_votes": {
+ "name": "declared_votes",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_at": {
+ "name": "declared_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "declared_by_id": {
+ "name": "declared_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_votes_pessimistic": {
+ "name": "estimated_votes_pessimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_votes_central": {
+ "name": "estimated_votes_central",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_votes_optimistic": {
+ "name": "estimated_votes_optimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimate_note": {
+ "name": "estimate_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_by_id": {
+ "name": "estimated_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_at": {
+ "name": "estimated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "vote_pledge_leadership_idx": {
+ "name": "vote_pledge_leadership_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_municipality_idx": {
+ "name": "vote_pledge_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_declared_votes_idx": {
+ "name": "vote_pledge_declared_votes_idx",
+ "columns": [
+ {
+ "expression": "declared_votes",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_declared_by_idx": {
+ "name": "vote_pledge_declared_by_idx",
+ "columns": [
+ {
+ "expression": "declared_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_estimated_by_idx": {
+ "name": "vote_pledge_estimated_by_idx",
+ "columns": [
+ {
+ "expression": "estimated_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_updated_at_idx": {
+ "name": "vote_pledge_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "vote_pledge_created_at_idx": {
+ "name": "vote_pledge_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_municipality_idx": {
+ "name": "leadership_municipality_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vote_pledge_leadership_id_leadership_id_fk": {
+ "name": "vote_pledge_leadership_id_leadership_id_fk",
+ "tableFrom": "vote_pledge",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "vote_pledge_municipality_id_municipality_id_fk": {
+ "name": "vote_pledge_municipality_id_municipality_id_fk",
+ "tableFrom": "vote_pledge",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "vote_pledge_declared_by_id_campaign_user_id_fk": {
+ "name": "vote_pledge_declared_by_id_campaign_user_id_fk",
+ "tableFrom": "vote_pledge",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "declared_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "vote_pledge_estimated_by_id_campaign_user_id_fk": {
+ "name": "vote_pledge_estimated_by_id_campaign_user_id_fk",
+ "tableFrom": "vote_pledge",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "estimated_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._vote_pledge_v": {
+ "name": "_vote_pledge_v",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_leadership_id": {
+ "name": "version_leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_municipality_id": {
+ "name": "version_municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_declared_votes": {
+ "name": "version_declared_votes",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_declared_at": {
+ "name": "version_declared_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_declared_by_id": {
+ "name": "version_declared_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimated_votes_pessimistic": {
+ "name": "version_estimated_votes_pessimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimated_votes_central": {
+ "name": "version_estimated_votes_central",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimated_votes_optimistic": {
+ "name": "version_estimated_votes_optimistic",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimate_note": {
+ "name": "version_estimate_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimated_by_id": {
+ "name": "version_estimated_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_estimated_at": {
+ "name": "version_estimated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_updated_at": {
+ "name": "version_updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_created_at": {
+ "name": "version_created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "_vote_pledge_v_parent_idx": {
+ "name": "_vote_pledge_v_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_leadership_idx": {
+ "name": "_vote_pledge_v_version_version_leadership_idx",
+ "columns": [
+ {
+ "expression": "version_leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_municipality_idx": {
+ "name": "_vote_pledge_v_version_version_municipality_idx",
+ "columns": [
+ {
+ "expression": "version_municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_declared_votes_idx": {
+ "name": "_vote_pledge_v_version_version_declared_votes_idx",
+ "columns": [
+ {
+ "expression": "version_declared_votes",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_declared_by_idx": {
+ "name": "_vote_pledge_v_version_version_declared_by_idx",
+ "columns": [
+ {
+ "expression": "version_declared_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_estimated_by_idx": {
+ "name": "_vote_pledge_v_version_version_estimated_by_idx",
+ "columns": [
+ {
+ "expression": "version_estimated_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_updated_at_idx": {
+ "name": "_vote_pledge_v_version_version_updated_at_idx",
+ "columns": [
+ {
+ "expression": "version_updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_version_version_created_at_idx": {
+ "name": "_vote_pledge_v_version_version_created_at_idx",
+ "columns": [
+ {
+ "expression": "version_created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_created_at_idx": {
+ "name": "_vote_pledge_v_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_vote_pledge_v_updated_at_idx": {
+ "name": "_vote_pledge_v_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "version_leadership_version_municipality_idx": {
+ "name": "version_leadership_version_municipality_idx",
+ "columns": [
+ {
+ "expression": "version_leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "version_municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "_vote_pledge_v_parent_id_vote_pledge_id_fk": {
+ "name": "_vote_pledge_v_parent_id_vote_pledge_id_fk",
+ "tableFrom": "_vote_pledge_v",
+ "tableTo": "vote_pledge",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_vote_pledge_v_version_leadership_id_leadership_id_fk": {
+ "name": "_vote_pledge_v_version_leadership_id_leadership_id_fk",
+ "tableFrom": "_vote_pledge_v",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "version_leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_vote_pledge_v_version_municipality_id_municipality_id_fk": {
+ "name": "_vote_pledge_v_version_municipality_id_municipality_id_fk",
+ "tableFrom": "_vote_pledge_v",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "version_municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_vote_pledge_v_version_declared_by_id_campaign_user_id_fk": {
+ "name": "_vote_pledge_v_version_declared_by_id_campaign_user_id_fk",
+ "tableFrom": "_vote_pledge_v",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "version_declared_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_vote_pledge_v_version_estimated_by_id_campaign_user_id_fk": {
+ "name": "_vote_pledge_v_version_estimated_by_id_campaign_user_id_fk",
+ "tableFrom": "_vote_pledge_v",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "version_estimated_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.allocation_decision": {
+ "name": "allocation_decision",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pattern_id": {
+ "name": "pattern_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "enum_allocation_decision_outcome",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rationale": {
+ "name": "rationale",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alternative_reading": {
+ "name": "alternative_reading",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decided_by_id": {
+ "name": "decided_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "allocation_decision_municipality_idx": {
+ "name": "allocation_decision_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "allocation_decision_pattern_id_idx": {
+ "name": "allocation_decision_pattern_id_idx",
+ "columns": [
+ {
+ "expression": "pattern_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "allocation_decision_decided_by_idx": {
+ "name": "allocation_decision_decided_by_idx",
+ "columns": [
+ {
+ "expression": "decided_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "allocation_decision_updated_at_idx": {
+ "name": "allocation_decision_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "allocation_decision_created_at_idx": {
+ "name": "allocation_decision_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "allocation_decision_municipality_id_municipality_id_fk": {
+ "name": "allocation_decision_municipality_id_municipality_id_fk",
+ "tableFrom": "allocation_decision",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "allocation_decision_decided_by_id_campaign_user_id_fk": {
+ "name": "allocation_decision_decided_by_id_campaign_user_id_fk",
+ "tableFrom": "allocation_decision",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "decided_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_demand_status_history": {
+ "name": "campaign_demand_status_history",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_campaign_demand_status_history_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "campaign_demand_status_history_order_idx": {
+ "name": "campaign_demand_status_history_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_status_history_parent_id_idx": {
+ "name": "campaign_demand_status_history_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_status_history_author_idx": {
+ "name": "campaign_demand_status_history_author_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_demand_status_history_author_id_campaign_user_id_fk": {
+ "name": "campaign_demand_status_history_author_id_campaign_user_id_fk",
+ "tableFrom": "campaign_demand_status_history",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_status_history_parent_id_fk": {
+ "name": "campaign_demand_status_history_parent_id_fk",
+ "tableFrom": "campaign_demand_status_history",
+ "tableTo": "campaign_demand",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_demand": {
+ "name": "campaign_demand",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "enum_campaign_demand_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "activity_id": {
+ "name": "activity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_campaign_demand_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'aberta'"
+ },
+ "decision_note": {
+ "name": "decision_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_by_id": {
+ "name": "decided_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost": {
+ "name": "cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "campaign_demand_title_idx": {
+ "name": "campaign_demand_title_idx",
+ "columns": [
+ {
+ "expression": "title",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_slug_idx": {
+ "name": "campaign_demand_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_kind_idx": {
+ "name": "campaign_demand_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_municipality_idx": {
+ "name": "campaign_demand_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_activity_idx": {
+ "name": "campaign_demand_activity_idx",
+ "columns": [
+ {
+ "expression": "activity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_leadership_idx": {
+ "name": "campaign_demand_leadership_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_status_idx": {
+ "name": "campaign_demand_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_decided_by_idx": {
+ "name": "campaign_demand_decided_by_idx",
+ "columns": [
+ {
+ "expression": "decided_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_created_by_idx": {
+ "name": "campaign_demand_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_updated_at_idx": {
+ "name": "campaign_demand_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_created_at_idx": {
+ "name": "campaign_demand_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_demand_municipality_id_municipality_id_fk": {
+ "name": "campaign_demand_municipality_id_municipality_id_fk",
+ "tableFrom": "campaign_demand",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_activity_id_activity_id_fk": {
+ "name": "campaign_demand_activity_id_activity_id_fk",
+ "tableFrom": "campaign_demand",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "activity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_leadership_id_leadership_id_fk": {
+ "name": "campaign_demand_leadership_id_leadership_id_fk",
+ "tableFrom": "campaign_demand",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_decided_by_id_campaign_user_id_fk": {
+ "name": "campaign_demand_decided_by_id_campaign_user_id_fk",
+ "tableFrom": "campaign_demand",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "decided_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_created_by_id_campaign_user_id_fk": {
+ "name": "campaign_demand_created_by_id_campaign_user_id_fk",
+ "tableFrom": "campaign_demand",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_demand_rels": {
+ "name": "campaign_demand_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "campaign_demand_rels_order_idx": {
+ "name": "campaign_demand_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_rels_parent_idx": {
+ "name": "campaign_demand_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_rels_path_idx": {
+ "name": "campaign_demand_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_rels_campaign_user_id_idx": {
+ "name": "campaign_demand_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "campaign_demand_rels_media_id_idx": {
+ "name": "campaign_demand_rels_media_id_idx",
+ "columns": [
+ {
+ "expression": "media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "campaign_demand_rels_parent_fk": {
+ "name": "campaign_demand_rels_parent_fk",
+ "tableFrom": "campaign_demand_rels",
+ "tableTo": "campaign_demand",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_rels_campaign_user_fk": {
+ "name": "campaign_demand_rels_campaign_user_fk",
+ "tableFrom": "campaign_demand_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "campaign_demand_rels_media_fk": {
+ "name": "campaign_demand_rels_media_fk",
+ "tableFrom": "campaign_demand_rels",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.supporter": {
+ "name": "supporter",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_intention": {
+ "name": "vote_intention",
+ "type": "enum_supporter_vote_intention",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_content_hash": {
+ "name": "consent_content_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consented_at": {
+ "name": "consented_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_intention_consent_id": {
+ "name": "vote_intention_consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_intention_consent_content_hash": {
+ "name": "vote_intention_consent_content_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_intention_consented_at": {
+ "name": "vote_intention_consented_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "enum_supporter_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'manual'"
+ },
+ "consent_note": {
+ "name": "consent_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "supporter_contact_idx": {
+ "name": "supporter_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_municipality_idx": {
+ "name": "supporter_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_vote_intention_idx": {
+ "name": "supporter_vote_intention_idx",
+ "columns": [
+ {
+ "expression": "vote_intention",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_consent_idx": {
+ "name": "supporter_consent_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_vote_intention_consent_idx": {
+ "name": "supporter_vote_intention_consent_idx",
+ "columns": [
+ {
+ "expression": "vote_intention_consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_source_idx": {
+ "name": "supporter_source_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_created_by_idx": {
+ "name": "supporter_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_updated_at_idx": {
+ "name": "supporter_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_created_at_idx": {
+ "name": "supporter_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "supporter_contact_id_contact_id_fk": {
+ "name": "supporter_contact_id_contact_id_fk",
+ "tableFrom": "supporter",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "supporter_municipality_id_municipality_id_fk": {
+ "name": "supporter_municipality_id_municipality_id_fk",
+ "tableFrom": "supporter",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "supporter_consent_id_consent_id_fk": {
+ "name": "supporter_consent_id_consent_id_fk",
+ "tableFrom": "supporter",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "supporter_vote_intention_consent_id_consent_id_fk": {
+ "name": "supporter_vote_intention_consent_id_consent_id_fk",
+ "tableFrom": "supporter",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "vote_intention_consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "supporter_created_by_id_campaign_user_id_fk": {
+ "name": "supporter_created_by_id_campaign_user_id_fk",
+ "tableFrom": "supporter",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.supporter_import_batch": {
+ "name": "supporter_import_batch",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "batch_id": {
+ "name": "batch_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_id": {
+ "name": "actor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rows": {
+ "name": "rows",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "supporter_import_batch_batch_id_idx": {
+ "name": "supporter_import_batch_batch_id_idx",
+ "columns": [
+ {
+ "expression": "batch_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_import_batch_actor_idx": {
+ "name": "supporter_import_batch_actor_idx",
+ "columns": [
+ {
+ "expression": "actor_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_import_batch_expires_at_idx": {
+ "name": "supporter_import_batch_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_import_batch_updated_at_idx": {
+ "name": "supporter_import_batch_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "supporter_import_batch_created_at_idx": {
+ "name": "supporter_import_batch_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "supporter_import_batch_actor_id_campaign_user_id_fk": {
+ "name": "supporter_import_batch_actor_id_campaign_user_id_fk",
+ "tableFrom": "supporter_import_batch",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality_update_comments": {
+ "name": "municipality_update_comments",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "municipality_update_comments_order_idx": {
+ "name": "municipality_update_comments_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_comments_parent_id_idx": {
+ "name": "municipality_update_comments_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_comments_author_idx": {
+ "name": "municipality_update_comments_author_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_update_comments_author_id_campaign_user_id_fk": {
+ "name": "municipality_update_comments_author_id_campaign_user_id_fk",
+ "tableFrom": "municipality_update_comments",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "municipality_update_comments_parent_id_fk": {
+ "name": "municipality_update_comments_parent_id_fk",
+ "tableFrom": "municipality_update_comments",
+ "tableTo": "municipality_update",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.municipality_update": {
+ "name": "municipality_update",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "polarity": {
+ "name": "polarity",
+ "type": "enum_municipality_update_polarity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'neutra'"
+ },
+ "urgent": {
+ "name": "urgent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "adversary_signal": {
+ "name": "adversary_signal",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "body": {
+ "name": "body",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_volunteers": {
+ "name": "active_volunteers",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "new_supports": {
+ "name": "new_supports",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "responsible_id": {
+ "name": "responsible_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_by_id": {
+ "name": "resolved_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "municipality_update_municipality_idx": {
+ "name": "municipality_update_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_author_idx": {
+ "name": "municipality_update_author_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_polarity_idx": {
+ "name": "municipality_update_polarity_idx",
+ "columns": [
+ {
+ "expression": "polarity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_urgent_idx": {
+ "name": "municipality_update_urgent_idx",
+ "columns": [
+ {
+ "expression": "urgent",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_adversary_signal_idx": {
+ "name": "municipality_update_adversary_signal_idx",
+ "columns": [
+ {
+ "expression": "adversary_signal",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_responsible_idx": {
+ "name": "municipality_update_responsible_idx",
+ "columns": [
+ {
+ "expression": "responsible_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_resolved_by_idx": {
+ "name": "municipality_update_resolved_by_idx",
+ "columns": [
+ {
+ "expression": "resolved_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_resolved_at_idx": {
+ "name": "municipality_update_resolved_at_idx",
+ "columns": [
+ {
+ "expression": "resolved_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_updated_at_idx": {
+ "name": "municipality_update_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "municipality_update_created_at_idx": {
+ "name": "municipality_update_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "municipality_update_municipality_id_municipality_id_fk": {
+ "name": "municipality_update_municipality_id_municipality_id_fk",
+ "tableFrom": "municipality_update",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "municipality_update_author_id_campaign_user_id_fk": {
+ "name": "municipality_update_author_id_campaign_user_id_fk",
+ "tableFrom": "municipality_update",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "municipality_update_responsible_id_campaign_user_id_fk": {
+ "name": "municipality_update_responsible_id_campaign_user_id_fk",
+ "tableFrom": "municipality_update",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "responsible_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "municipality_update_resolved_by_id_campaign_user_id_fk": {
+ "name": "municipality_update_resolved_by_id_campaign_user_id_fk",
+ "tableFrom": "municipality_update",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "resolved_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity_tasks": {
+ "name": "activity_tasks",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsible_id": {
+ "name": "responsible_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "done": {
+ "name": "done",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "done_at": {
+ "name": "done_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "activity_tasks_order_idx": {
+ "name": "activity_tasks_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_tasks_parent_id_idx": {
+ "name": "activity_tasks_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_tasks_responsible_idx": {
+ "name": "activity_tasks_responsible_idx",
+ "columns": [
+ {
+ "expression": "responsible_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_tasks_responsible_id_contact_id_fk": {
+ "name": "activity_tasks_responsible_id_contact_id_fk",
+ "tableFrom": "activity_tasks",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "responsible_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "activity_tasks_parent_id_fk": {
+ "name": "activity_tasks_parent_id_fk",
+ "tableFrom": "activity_tasks",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity_updates": {
+ "name": "activity_updates",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "activity_updates_order_idx": {
+ "name": "activity_updates_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_updates_parent_id_idx": {
+ "name": "activity_updates_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_updates_author_idx": {
+ "name": "activity_updates_author_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_updates_author_id_campaign_user_id_fk": {
+ "name": "activity_updates_author_id_campaign_user_id_fk",
+ "tableFrom": "activity_updates",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "activity_updates_parent_id_fk": {
+ "name": "activity_updates_parent_id_fk",
+ "tableFrom": "activity_updates",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity": {
+ "name": "activity",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_activity_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'confirmado'"
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deputy_present": {
+ "name": "deputy_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "all_day": {
+ "name": "all_day",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "public_event": {
+ "name": "public_event",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "start_at": {
+ "name": "start_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "end_at": {
+ "name": "end_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "locality": {
+ "name": "locality",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "task_total": {
+ "name": "task_total",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "task_done_count": {
+ "name": "task_done_count",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "result_summary": {
+ "name": "result_summary",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result_recorded_by_id": {
+ "name": "result_recorded_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result_recorded_at": {
+ "name": "result_recorded_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_mirrored_change_at": {
+ "name": "last_mirrored_change_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "google_event_id": {
+ "name": "google_event_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "google_calendar_id": {
+ "name": "google_calendar_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "activity_title_idx": {
+ "name": "activity_title_idx",
+ "columns": [
+ {
+ "expression": "title",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_slug_idx": {
+ "name": "activity_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_status_idx": {
+ "name": "activity_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_deputy_present_idx": {
+ "name": "activity_deputy_present_idx",
+ "columns": [
+ {
+ "expression": "deputy_present",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_all_day_idx": {
+ "name": "activity_all_day_idx",
+ "columns": [
+ {
+ "expression": "all_day",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_public_event_idx": {
+ "name": "activity_public_event_idx",
+ "columns": [
+ {
+ "expression": "public_event",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_start_at_idx": {
+ "name": "activity_start_at_idx",
+ "columns": [
+ {
+ "expression": "start_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_municipality_idx": {
+ "name": "activity_municipality_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_result_recorded_by_idx": {
+ "name": "activity_result_recorded_by_idx",
+ "columns": [
+ {
+ "expression": "result_recorded_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_google_event_id_idx": {
+ "name": "activity_google_event_id_idx",
+ "columns": [
+ {
+ "expression": "google_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_created_by_idx": {
+ "name": "activity_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_updated_at_idx": {
+ "name": "activity_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_created_at_idx": {
+ "name": "activity_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_municipality_id_municipality_id_fk": {
+ "name": "activity_municipality_id_municipality_id_fk",
+ "tableFrom": "activity",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "activity_result_recorded_by_id_campaign_user_id_fk": {
+ "name": "activity_result_recorded_by_id_campaign_user_id_fk",
+ "tableFrom": "activity",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "result_recorded_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "activity_created_by_id_campaign_user_id_fk": {
+ "name": "activity_created_by_id_campaign_user_id_fk",
+ "tableFrom": "activity",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity_texts": {
+ "name": "activity_texts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "activity_texts_order_parent": {
+ "name": "activity_texts_order_parent",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_texts_text_idx": {
+ "name": "activity_texts_text_idx",
+ "columns": [
+ {
+ "expression": "text",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_texts_parent_fk": {
+ "name": "activity_texts_parent_fk",
+ "tableFrom": "activity_texts",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity_rels": {
+ "name": "activity_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_deputy_id": {
+ "name": "state_deputy_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "activity_rels_order_idx": {
+ "name": "activity_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_parent_idx": {
+ "name": "activity_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_path_idx": {
+ "name": "activity_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_organization_id_idx": {
+ "name": "activity_rels_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_campaign_user_id_idx": {
+ "name": "activity_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_leadership_id_idx": {
+ "name": "activity_rels_leadership_id_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_state_deputy_id_idx": {
+ "name": "activity_rels_state_deputy_id_idx",
+ "columns": [
+ {
+ "expression": "state_deputy_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_rels_media_id_idx": {
+ "name": "activity_rels_media_id_idx",
+ "columns": [
+ {
+ "expression": "media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_rels_parent_fk": {
+ "name": "activity_rels_parent_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_rels_organization_fk": {
+ "name": "activity_rels_organization_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_rels_campaign_user_fk": {
+ "name": "activity_rels_campaign_user_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_rels_leadership_fk": {
+ "name": "activity_rels_leadership_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_rels_state_deputy_fk": {
+ "name": "activity_rels_state_deputy_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "state_deputy",
+ "columnsFrom": [
+ "state_deputy_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_rels_media_fk": {
+ "name": "activity_rels_media_fk",
+ "tableFrom": "activity_rels",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_topics": {
+ "name": "speech_topics",
+ "schema": "",
+ "columns": {
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "enum_speech_topics",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "speech_topics_order_idx": {
+ "name": "speech_topics_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_topics_parent_idx": {
+ "name": "speech_topics_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_topics_value_idx": {
+ "name": "speech_topics_value_idx",
+ "columns": [
+ {
+ "expression": "value",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_topics_parent_fk": {
+ "name": "speech_topics_parent_fk",
+ "tableFrom": "speech_topics",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_scopes": {
+ "name": "speech_scopes",
+ "schema": "",
+ "columns": {
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "enum_speech_scopes",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "speech_scopes_order_idx": {
+ "name": "speech_scopes_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_scopes_parent_idx": {
+ "name": "speech_scopes_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_scopes_value_idx": {
+ "name": "speech_scopes_value_idx",
+ "columns": [
+ {
+ "expression": "value",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_scopes_parent_fk": {
+ "name": "speech_scopes_parent_fk",
+ "tableFrom": "speech_scopes",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech": {
+ "name": "speech",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "source_key": {
+ "name": "source_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "speech_at": {
+ "name": "speech_at",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "legislature": {
+ "name": "legislature",
+ "type": "enum_speech_legislature",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phase": {
+ "name": "phase",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "official_transcript": {
+ "name": "official_transcript",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "official_text_url": {
+ "name": "official_text_url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "search_text": {
+ "name": "search_text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_start_at": {
+ "name": "event_start_at",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_end_at": {
+ "name": "event_end_at",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "youtube_url": {
+ "name": "youtube_url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "presiding_officer": {
+ "name": "presiding_officer",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "audio_id": {
+ "name": "audio_id",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "excerpt_t_ms": {
+ "name": "excerpt_t_ms",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vod_playback_url": {
+ "name": "vod_playback_url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vod_download_url": {
+ "name": "vod_download_url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "classified_by": {
+ "name": "classified_by",
+ "type": "enum_speech_classified_by",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'gazetteer'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "speech_source_key_idx": {
+ "name": "speech_source_key_idx",
+ "columns": [
+ {
+ "expression": "source_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_speech_at_idx": {
+ "name": "speech_speech_at_idx",
+ "columns": [
+ {
+ "expression": "speech_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_year_idx": {
+ "name": "speech_year_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_legislature_idx": {
+ "name": "speech_legislature_idx",
+ "columns": [
+ {
+ "expression": "legislature",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_phase_idx": {
+ "name": "speech_phase_idx",
+ "columns": [
+ {
+ "expression": "phase",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_event_id_idx": {
+ "name": "speech_event_id_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_audio_id_idx": {
+ "name": "speech_audio_id_idx",
+ "columns": [
+ {
+ "expression": "audio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_excerpt_t_ms_idx": {
+ "name": "speech_excerpt_t_ms_idx",
+ "columns": [
+ {
+ "expression": "excerpt_t_ms",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_classified_by_idx": {
+ "name": "speech_classified_by_idx",
+ "columns": [
+ {
+ "expression": "classified_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_updated_at_idx": {
+ "name": "speech_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_created_at_idx": {
+ "name": "speech_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_texts": {
+ "name": "speech_texts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "speech_texts_order_parent": {
+ "name": "speech_texts_order_parent",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_texts_parent_fk": {
+ "name": "speech_texts_parent_fk",
+ "tableFrom": "speech_texts",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_rels": {
+ "name": "speech_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "speech_rels_order_idx": {
+ "name": "speech_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_rels_parent_idx": {
+ "name": "speech_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_rels_path_idx": {
+ "name": "speech_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_rels_municipality_id_idx": {
+ "name": "speech_rels_municipality_id_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_rels_parent_fk": {
+ "name": "speech_rels_parent_fk",
+ "tableFrom": "speech_rels",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "speech_rels_municipality_fk": {
+ "name": "speech_rels_municipality_fk",
+ "tableFrom": "speech_rels",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_segment": {
+ "name": "speech_segment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "speech_id": {
+ "name": "speech_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "start_seconds": {
+ "name": "start_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_seconds": {
+ "name": "end_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "search_text": {
+ "name": "search_text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "speech_segment_speech_idx": {
+ "name": "speech_segment_speech_idx",
+ "columns": [
+ {
+ "expression": "speech_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_segment_updated_at_idx": {
+ "name": "speech_segment_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_segment_created_at_idx": {
+ "name": "speech_segment_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_segment_speech_id_speech_id_fk": {
+ "name": "speech_segment_speech_id_speech_id_fk",
+ "tableFrom": "speech_segment",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "speech_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.speech_cut": {
+ "name": "speech_cut",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "speech_id": {
+ "name": "speech_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start_seconds": {
+ "name": "start_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_seconds": {
+ "name": "end_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_speech_cut_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'processing'"
+ },
+ "step": {
+ "name": "step",
+ "type": "enum_speech_cut_step",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "speech_cut_speech_idx": {
+ "name": "speech_cut_speech_idx",
+ "columns": [
+ {
+ "expression": "speech_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_cut_status_idx": {
+ "name": "speech_cut_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_cut_media_idx": {
+ "name": "speech_cut_media_idx",
+ "columns": [
+ {
+ "expression": "media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_cut_created_by_idx": {
+ "name": "speech_cut_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_cut_updated_at_idx": {
+ "name": "speech_cut_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "speech_cut_created_at_idx": {
+ "name": "speech_cut_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "speech_cut_speech_id_speech_id_fk": {
+ "name": "speech_cut_speech_id_speech_id_fk",
+ "tableFrom": "speech_cut",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "speech_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "speech_cut_media_id_media_id_fk": {
+ "name": "speech_cut_media_id_media_id_fk",
+ "tableFrom": "speech_cut",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "speech_cut_created_by_id_campaign_user_id_fk": {
+ "name": "speech_cut_created_by_id_campaign_user_id_fk",
+ "tableFrom": "speech_cut",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reel": {
+ "name": "reel",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "enum_reel_feature",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_hash": {
+ "name": "source_hash",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_reel_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'draft'"
+ },
+ "video_id": {
+ "name": "video_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "video_with_audio_id": {
+ "name": "video_with_audio_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "narration_audio_id": {
+ "name": "narration_audio_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "captions_id": {
+ "name": "captions_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cover_id": {
+ "name": "cover_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "reel_source_hash_idx": {
+ "name": "reel_source_hash_idx",
+ "columns": [
+ {
+ "expression": "source_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_status_idx": {
+ "name": "reel_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_video_idx": {
+ "name": "reel_video_idx",
+ "columns": [
+ {
+ "expression": "video_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_video_with_audio_idx": {
+ "name": "reel_video_with_audio_idx",
+ "columns": [
+ {
+ "expression": "video_with_audio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_narration_audio_idx": {
+ "name": "reel_narration_audio_idx",
+ "columns": [
+ {
+ "expression": "narration_audio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_captions_idx": {
+ "name": "reel_captions_idx",
+ "columns": [
+ {
+ "expression": "captions_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_cover_idx": {
+ "name": "reel_cover_idx",
+ "columns": [
+ {
+ "expression": "cover_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_created_by_idx": {
+ "name": "reel_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_updated_at_idx": {
+ "name": "reel_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_created_at_idx": {
+ "name": "reel_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "reel_video_id_reel_media_id_fk": {
+ "name": "reel_video_id_reel_media_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "video_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "reel_video_with_audio_id_reel_media_id_fk": {
+ "name": "reel_video_with_audio_id_reel_media_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "video_with_audio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "reel_narration_audio_id_reel_media_id_fk": {
+ "name": "reel_narration_audio_id_reel_media_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "narration_audio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "reel_captions_id_reel_media_id_fk": {
+ "name": "reel_captions_id_reel_media_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "captions_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "reel_cover_id_reel_media_id_fk": {
+ "name": "reel_cover_id_reel_media_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "cover_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "reel_created_by_id_campaign_user_id_fk": {
+ "name": "reel_created_by_id_campaign_user_id_fk",
+ "tableFrom": "reel",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reel_media": {
+ "name": "reel_media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "alt": {
+ "name": "alt",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail_u_r_l": {
+ "name": "thumbnail_u_r_l",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filesize": {
+ "name": "filesize",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_x": {
+ "name": "focal_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_y": {
+ "name": "focal_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "reel_media_updated_at_idx": {
+ "name": "reel_media_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_media_created_at_idx": {
+ "name": "reel_media_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reel_media_filename_idx": {
+ "name": "reel_media_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recording_speaker_labels": {
+ "name": "recording_speaker_labels",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "speaker_key": {
+ "name": "speaker_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "recording_speaker_labels_order_idx": {
+ "name": "recording_speaker_labels_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_speaker_labels_parent_id_idx": {
+ "name": "recording_speaker_labels_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "recording_speaker_labels_parent_id_fk": {
+ "name": "recording_speaker_labels_parent_id_fk",
+ "tableFrom": "recording_speaker_labels",
+ "tableTo": "recording",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recording": {
+ "name": "recording",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "recorded_at": {
+ "name": "recorded_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_recording_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'uploading'"
+ },
+ "step": {
+ "name": "step",
+ "type": "enum_recording_step",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "search_text": {
+ "name": "search_text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "speaker_labels_dropped": {
+ "name": "speaker_labels_dropped",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "recording_recorded_at_idx": {
+ "name": "recording_recorded_at_idx",
+ "columns": [
+ {
+ "expression": "recorded_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_status_idx": {
+ "name": "recording_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_media_idx": {
+ "name": "recording_media_idx",
+ "columns": [
+ {
+ "expression": "media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_created_by_idx": {
+ "name": "recording_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_updated_at_idx": {
+ "name": "recording_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_created_at_idx": {
+ "name": "recording_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "recording_media_id_recording_media_id_fk": {
+ "name": "recording_media_id_recording_media_id_fk",
+ "tableFrom": "recording",
+ "tableTo": "recording_media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "recording_created_by_id_campaign_user_id_fk": {
+ "name": "recording_created_by_id_campaign_user_id_fk",
+ "tableFrom": "recording",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recording_texts": {
+ "name": "recording_texts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "recording_texts_order_parent": {
+ "name": "recording_texts_order_parent",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "recording_texts_parent_fk": {
+ "name": "recording_texts_parent_fk",
+ "tableFrom": "recording_texts",
+ "tableTo": "recording",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recording_media": {
+ "name": "recording_media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "alt": {
+ "name": "alt",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail_u_r_l": {
+ "name": "thumbnail_u_r_l",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filesize": {
+ "name": "filesize",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_x": {
+ "name": "focal_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_y": {
+ "name": "focal_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "recording_media_updated_at_idx": {
+ "name": "recording_media_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_media_created_at_idx": {
+ "name": "recording_media_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_media_filename_idx": {
+ "name": "recording_media_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.recording_segment": {
+ "name": "recording_segment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "recording_id": {
+ "name": "recording_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "speaker_key": {
+ "name": "speaker_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start_seconds": {
+ "name": "start_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_seconds": {
+ "name": "end_seconds",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "search_text": {
+ "name": "search_text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "recording_segment_recording_idx": {
+ "name": "recording_segment_recording_idx",
+ "columns": [
+ {
+ "expression": "recording_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_segment_updated_at_idx": {
+ "name": "recording_segment_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "recording_segment_created_at_idx": {
+ "name": "recording_segment_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "recording_segment_recording_id_recording_id_fk": {
+ "name": "recording_segment_recording_id_recording_id_fk",
+ "tableFrom": "recording_segment",
+ "tableTo": "recording",
+ "columnsFrom": [
+ "recording_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.calendar_feed": {
+ "name": "calendar_feed",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "secret_slug": {
+ "name": "secret_slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filter_municipality_id": {
+ "name": "filter_municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filter_deputy_present": {
+ "name": "filter_deputy_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "filter_tag": {
+ "name": "filter_tag",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "calendar_feed_secret_slug_idx": {
+ "name": "calendar_feed_secret_slug_idx",
+ "columns": [
+ {
+ "expression": "secret_slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_feed_filter_municipality_idx": {
+ "name": "calendar_feed_filter_municipality_idx",
+ "columns": [
+ {
+ "expression": "filter_municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_feed_revoked_at_idx": {
+ "name": "calendar_feed_revoked_at_idx",
+ "columns": [
+ {
+ "expression": "revoked_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_feed_created_by_idx": {
+ "name": "calendar_feed_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_feed_updated_at_idx": {
+ "name": "calendar_feed_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_feed_created_at_idx": {
+ "name": "calendar_feed_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "calendar_feed_filter_municipality_id_municipality_id_fk": {
+ "name": "calendar_feed_filter_municipality_id_municipality_id_fk",
+ "tableFrom": "calendar_feed",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "filter_municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "calendar_feed_created_by_id_campaign_user_id_fk": {
+ "name": "calendar_feed_created_by_id_campaign_user_id_fk",
+ "tableFrom": "calendar_feed",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.google_calendar_sync": {
+ "name": "google_calendar_sync",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "calendar_id": {
+ "name": "calendar_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_refresh_token": {
+ "name": "oauth_refresh_token",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_scope": {
+ "name": "oauth_scope",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_connected_at": {
+ "name": "oauth_connected_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_error_at": {
+ "name": "oauth_error_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_error": {
+ "name": "oauth_error",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disabled_at": {
+ "name": "disabled_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_synced_at": {
+ "name": "last_synced_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_success_at": {
+ "name": "last_success_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error_at": {
+ "name": "last_error_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_channel_id": {
+ "name": "push_channel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_channel_resource_id": {
+ "name": "push_channel_resource_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_channel_expires_at": {
+ "name": "push_channel_expires_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_channel_secret": {
+ "name": "push_channel_secret",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_channel_error": {
+ "name": "push_channel_error",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_seen_event_ids": {
+ "name": "last_seen_event_ids",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "google_calendar_sync_calendar_id_idx": {
+ "name": "google_calendar_sync_calendar_id_idx",
+ "columns": [
+ {
+ "expression": "calendar_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "google_calendar_sync_disabled_at_idx": {
+ "name": "google_calendar_sync_disabled_at_idx",
+ "columns": [
+ {
+ "expression": "disabled_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "google_calendar_sync_updated_at_idx": {
+ "name": "google_calendar_sync_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "google_calendar_sync_created_at_idx": {
+ "name": "google_calendar_sync_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.election_tally": {
+ "name": "election_tally",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "office": {
+ "name": "office",
+ "type": "enum_election_tally_office",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "turn": {
+ "name": "turn",
+ "type": "enum_election_tally_turn",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'BA'"
+ },
+ "city_code": {
+ "name": "city_code",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city_name": {
+ "name": "city_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "zone_number": {
+ "name": "zone_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aptos": {
+ "name": "aptos",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "comparecimento": {
+ "name": "comparecimento",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "abstencoes": {
+ "name": "abstencoes",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_validos": {
+ "name": "votos_validos",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_nominais_validos": {
+ "name": "votos_nominais_validos",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_legenda": {
+ "name": "votos_legenda",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_branco": {
+ "name": "votos_branco",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_nulo": {
+ "name": "votos_nulo",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "votos_anulados": {
+ "name": "votos_anulados",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "winner_candidate_number": {
+ "name": "winner_candidate_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_candidate_name": {
+ "name": "winner_candidate_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_votes": {
+ "name": "winner_votes",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_coalition": {
+ "name": "winner_coalition",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_party": {
+ "name": "winner_party",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "election_tally_year_idx": {
+ "name": "election_tally_year_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_office_idx": {
+ "name": "election_tally_office_idx",
+ "columns": [
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_turn_idx": {
+ "name": "election_tally_turn_idx",
+ "columns": [
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_state_idx": {
+ "name": "election_tally_state_idx",
+ "columns": [
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_city_code_idx": {
+ "name": "election_tally_city_code_idx",
+ "columns": [
+ {
+ "expression": "city_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_city_name_idx": {
+ "name": "election_tally_city_name_idx",
+ "columns": [
+ {
+ "expression": "city_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_zone_number_idx": {
+ "name": "election_tally_zone_number_idx",
+ "columns": [
+ {
+ "expression": "zone_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_updated_at_idx": {
+ "name": "election_tally_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_tally_created_at_idx": {
+ "name": "election_tally_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "year_office_turn_state_cityCode_zoneNumber_idx": {
+ "name": "year_office_turn_state_cityCode_zoneNumber_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "city_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "zone_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.election_candidate_vote": {
+ "name": "election_candidate_vote",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "office": {
+ "name": "office",
+ "type": "enum_election_candidate_vote_office",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "turn": {
+ "name": "turn",
+ "type": "enum_election_candidate_vote_turn",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'BA'"
+ },
+ "city_code": {
+ "name": "city_code",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "city_name": {
+ "name": "city_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "zone_number": {
+ "name": "zone_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "candidate_number": {
+ "name": "candidate_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "candidate_name": {
+ "name": "candidate_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "coalition": {
+ "name": "coalition",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "party": {
+ "name": "party",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_type": {
+ "name": "vote_type",
+ "type": "enum_election_candidate_vote_vote_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nominal'"
+ },
+ "votes": {
+ "name": "votes",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "election_candidate_vote_year_idx": {
+ "name": "election_candidate_vote_year_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_office_idx": {
+ "name": "election_candidate_vote_office_idx",
+ "columns": [
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_turn_idx": {
+ "name": "election_candidate_vote_turn_idx",
+ "columns": [
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_state_idx": {
+ "name": "election_candidate_vote_state_idx",
+ "columns": [
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_city_code_idx": {
+ "name": "election_candidate_vote_city_code_idx",
+ "columns": [
+ {
+ "expression": "city_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_city_name_idx": {
+ "name": "election_candidate_vote_city_name_idx",
+ "columns": [
+ {
+ "expression": "city_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_zone_number_idx": {
+ "name": "election_candidate_vote_zone_number_idx",
+ "columns": [
+ {
+ "expression": "zone_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_candidate_number_idx": {
+ "name": "election_candidate_vote_candidate_number_idx",
+ "columns": [
+ {
+ "expression": "candidate_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_vote_type_idx": {
+ "name": "election_candidate_vote_vote_type_idx",
+ "columns": [
+ {
+ "expression": "vote_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_updated_at_idx": {
+ "name": "election_candidate_vote_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_vote_created_at_idx": {
+ "name": "election_candidate_vote_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "compound_index_idx": {
+ "name": "compound_index_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "city_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "zone_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "candidate_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "vote_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.election_candidate": {
+ "name": "election_candidate",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "office": {
+ "name": "office",
+ "type": "enum_election_candidate_office",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "turn": {
+ "name": "turn",
+ "type": "enum_election_candidate_turn",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'BA'"
+ },
+ "candidate_number": {
+ "name": "candidate_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "urna_name": {
+ "name": "urna_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "complete_name": {
+ "name": "complete_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "party": {
+ "name": "party",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coalition": {
+ "name": "coalition",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "candidate_status": {
+ "name": "candidate_status",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "elected": {
+ "name": "elected",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "elected_by": {
+ "name": "elected_by",
+ "type": "enum_election_candidate_elected_by",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_votes_state": {
+ "name": "total_votes_state",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "identity_key": {
+ "name": "identity_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "running_again2026": {
+ "name": "running_again2026",
+ "type": "enum_election_candidate_running_again2026",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'desconhecido'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "election_candidate_year_idx": {
+ "name": "election_candidate_year_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_office_idx": {
+ "name": "election_candidate_office_idx",
+ "columns": [
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_turn_idx": {
+ "name": "election_candidate_turn_idx",
+ "columns": [
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_state_idx": {
+ "name": "election_candidate_state_idx",
+ "columns": [
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_candidate_number_idx": {
+ "name": "election_candidate_candidate_number_idx",
+ "columns": [
+ {
+ "expression": "candidate_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_elected_idx": {
+ "name": "election_candidate_elected_idx",
+ "columns": [
+ {
+ "expression": "elected",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_identity_key_idx": {
+ "name": "election_candidate_identity_key_idx",
+ "columns": [
+ {
+ "expression": "identity_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_updated_at_idx": {
+ "name": "election_candidate_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "election_candidate_created_at_idx": {
+ "name": "election_candidate_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "year_office_turn_state_candidateNumber_idx": {
+ "name": "year_office_turn_state_candidateNumber_idx",
+ "columns": [
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "office",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "turn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "candidate_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.media": {
+ "name": "media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "alt": {
+ "name": "alt",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail_u_r_l": {
+ "name": "thumbnail_u_r_l",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filesize": {
+ "name": "filesize",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_x": {
+ "name": "focal_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_y": {
+ "name": "focal_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "media_updated_at_idx": {
+ "name": "media_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_created_at_idx": {
+ "name": "media_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_filename_idx": {
+ "name": "media_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.petition": {
+ "name": "petition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "body": {
+ "name": "body",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "form_title": {
+ "name": "form_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "form_subtitle": {
+ "name": "form_subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "form_consent_id": {
+ "name": "form_consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tracking_facebook_pixel_id": {
+ "name": "tracking_facebook_pixel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "_status": {
+ "name": "_status",
+ "type": "enum_petition_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ }
+ },
+ "indexes": {
+ "petition_form_form_consent_idx": {
+ "name": "petition_form_form_consent_idx",
+ "columns": [
+ {
+ "expression": "form_consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "petition_updated_at_idx": {
+ "name": "petition_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "petition_created_at_idx": {
+ "name": "petition_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "petition__status_idx": {
+ "name": "petition__status_idx",
+ "columns": [
+ {
+ "expression": "_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "petition_form_consent_id_consent_id_fk": {
+ "name": "petition_form_consent_id_consent_id_fk",
+ "tableFrom": "petition",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "form_consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._petition_v": {
+ "name": "_petition_v",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_title": {
+ "name": "version_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_subtitle": {
+ "name": "version_subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_enabled": {
+ "name": "version_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "version_body": {
+ "name": "version_body",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_form_title": {
+ "name": "version_form_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_form_subtitle": {
+ "name": "version_form_subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_form_consent_id": {
+ "name": "version_form_consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_tracking_facebook_pixel_id": {
+ "name": "version_tracking_facebook_pixel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_updated_at": {
+ "name": "version_updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_created_at": {
+ "name": "version_created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version__status": {
+ "name": "version__status",
+ "type": "enum__petition_v_version_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "latest": {
+ "name": "latest",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "_petition_v_parent_idx": {
+ "name": "_petition_v_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_version_form_version_form_consent_idx": {
+ "name": "_petition_v_version_form_version_form_consent_idx",
+ "columns": [
+ {
+ "expression": "version_form_consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_version_version_updated_at_idx": {
+ "name": "_petition_v_version_version_updated_at_idx",
+ "columns": [
+ {
+ "expression": "version_updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_version_version_created_at_idx": {
+ "name": "_petition_v_version_version_created_at_idx",
+ "columns": [
+ {
+ "expression": "version_created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_version_version__status_idx": {
+ "name": "_petition_v_version_version__status_idx",
+ "columns": [
+ {
+ "expression": "version__status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_created_at_idx": {
+ "name": "_petition_v_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_updated_at_idx": {
+ "name": "_petition_v_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_petition_v_latest_idx": {
+ "name": "_petition_v_latest_idx",
+ "columns": [
+ {
+ "expression": "latest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "_petition_v_parent_id_petition_id_fk": {
+ "name": "_petition_v_parent_id_petition_id_fk",
+ "tableFrom": "_petition_v",
+ "tableTo": "petition",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_petition_v_version_form_consent_id_consent_id_fk": {
+ "name": "_petition_v_version_form_consent_id_consent_id_fk",
+ "tableFrom": "_petition_v",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "version_form_consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.contact_phones": {
+ "name": "contact_phones",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "contact_phones_order_idx": {
+ "name": "contact_phones_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "contact_phones_parent_id_idx": {
+ "name": "contact_phones_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "contact_phones_value_idx": {
+ "name": "contact_phones_value_idx",
+ "columns": [
+ {
+ "expression": "value",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "contact_phones_parent_id_fk": {
+ "name": "contact_phones_parent_id_fk",
+ "tableFrom": "contact_phones",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.contact": {
+ "name": "contact",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gender": {
+ "name": "gender",
+ "type": "enum_contact_gender",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "enum_contact_state",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postal_code": {
+ "name": "postal_code",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "contact_updated_at_idx": {
+ "name": "contact_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "contact_created_at_idx": {
+ "name": "contact_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.consent": {
+ "name": "consent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "consent_key_idx": {
+ "name": "consent_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "consent_updated_at_idx": {
+ "name": "consent_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "consent_created_at_idx": {
+ "name": "consent_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.signature": {
+ "name": "signature",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "petition_id": {
+ "name": "petition_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "comment": {
+ "name": "comment",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "signature_contact_idx": {
+ "name": "signature_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "signature_petition_idx": {
+ "name": "signature_petition_idx",
+ "columns": [
+ {
+ "expression": "petition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "signature_consent_idx": {
+ "name": "signature_consent_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "signature_updated_at_idx": {
+ "name": "signature_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "signature_created_at_idx": {
+ "name": "signature_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "signature_contact_id_contact_id_fk": {
+ "name": "signature_contact_id_contact_id_fk",
+ "tableFrom": "signature",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "signature_petition_id_petition_id_fk": {
+ "name": "signature_petition_id_petition_id_fk",
+ "tableFrom": "signature",
+ "tableTo": "petition",
+ "columnsFrom": [
+ "petition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "signature_consent_id_consent_id_fk": {
+ "name": "signature_consent_id_consent_id_fk",
+ "tableFrom": "signature",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.subscription": {
+ "name": "subscription",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "comment": {
+ "name": "comment",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_level": {
+ "name": "campaign_level",
+ "type": "enum_subscription_campaign_level",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "subscription_contact_idx": {
+ "name": "subscription_contact_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "subscription_consent_idx": {
+ "name": "subscription_consent_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "subscription_updated_at_idx": {
+ "name": "subscription_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "subscription_created_at_idx": {
+ "name": "subscription_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "subscription_contact_id_contact_id_fk": {
+ "name": "subscription_contact_id_contact_id_fk",
+ "tableFrom": "subscription",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "subscription_consent_id_consent_id_fk": {
+ "name": "subscription_consent_id_consent_id_fk",
+ "tableFrom": "subscription",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.post": {
+ "name": "post",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "enum_post_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cover_image_id": {
+ "name": "cover_image_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_date": {
+ "name": "published_date",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "body": {
+ "name": "body",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "_status": {
+ "name": "_status",
+ "type": "enum_post_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ }
+ },
+ "indexes": {
+ "post_slug_idx": {
+ "name": "post_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_category_idx": {
+ "name": "post_category_idx",
+ "columns": [
+ {
+ "expression": "category_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_cover_image_idx": {
+ "name": "post_cover_image_idx",
+ "columns": [
+ {
+ "expression": "cover_image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_updated_at_idx": {
+ "name": "post_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_created_at_idx": {
+ "name": "post_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post__status_idx": {
+ "name": "post__status_idx",
+ "columns": [
+ {
+ "expression": "_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "post_category_id_tag_id_fk": {
+ "name": "post_category_id_tag_id_fk",
+ "tableFrom": "post",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "post_cover_image_id_media_id_fk": {
+ "name": "post_cover_image_id_media_id_fk",
+ "tableFrom": "post",
+ "tableTo": "media",
+ "columnsFrom": [
+ "cover_image_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.post_rels": {
+ "name": "post_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "post_rels_order_idx": {
+ "name": "post_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_rels_parent_idx": {
+ "name": "post_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_rels_path_idx": {
+ "name": "post_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "post_rels_tag_id_idx": {
+ "name": "post_rels_tag_id_idx",
+ "columns": [
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "post_rels_parent_fk": {
+ "name": "post_rels_parent_fk",
+ "tableFrom": "post_rels",
+ "tableTo": "post",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "post_rels_tag_fk": {
+ "name": "post_rels_tag_fk",
+ "tableFrom": "post_rels",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._post_v": {
+ "name": "_post_v",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_title": {
+ "name": "version_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_slug": {
+ "name": "version_slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_type": {
+ "name": "version_type",
+ "type": "enum__post_v_version_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_category_id": {
+ "name": "version_category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_subtitle": {
+ "name": "version_subtitle",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_cover_image_id": {
+ "name": "version_cover_image_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_published_date": {
+ "name": "version_published_date",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_body": {
+ "name": "version_body",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_updated_at": {
+ "name": "version_updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_created_at": {
+ "name": "version_created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version__status": {
+ "name": "version__status",
+ "type": "enum__post_v_version_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "latest": {
+ "name": "latest",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "_post_v_parent_idx": {
+ "name": "_post_v_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version_slug_idx": {
+ "name": "_post_v_version_version_slug_idx",
+ "columns": [
+ {
+ "expression": "version_slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version_category_idx": {
+ "name": "_post_v_version_version_category_idx",
+ "columns": [
+ {
+ "expression": "version_category_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version_cover_image_idx": {
+ "name": "_post_v_version_version_cover_image_idx",
+ "columns": [
+ {
+ "expression": "version_cover_image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version_updated_at_idx": {
+ "name": "_post_v_version_version_updated_at_idx",
+ "columns": [
+ {
+ "expression": "version_updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version_created_at_idx": {
+ "name": "_post_v_version_version_created_at_idx",
+ "columns": [
+ {
+ "expression": "version_created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_version_version__status_idx": {
+ "name": "_post_v_version_version__status_idx",
+ "columns": [
+ {
+ "expression": "version__status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_created_at_idx": {
+ "name": "_post_v_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_updated_at_idx": {
+ "name": "_post_v_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_latest_idx": {
+ "name": "_post_v_latest_idx",
+ "columns": [
+ {
+ "expression": "latest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "_post_v_parent_id_post_id_fk": {
+ "name": "_post_v_parent_id_post_id_fk",
+ "tableFrom": "_post_v",
+ "tableTo": "post",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_post_v_version_category_id_tag_id_fk": {
+ "name": "_post_v_version_category_id_tag_id_fk",
+ "tableFrom": "_post_v",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "version_category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "_post_v_version_cover_image_id_media_id_fk": {
+ "name": "_post_v_version_cover_image_id_media_id_fk",
+ "tableFrom": "_post_v",
+ "tableTo": "media",
+ "columnsFrom": [
+ "version_cover_image_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._post_v_rels": {
+ "name": "_post_v_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "_post_v_rels_order_idx": {
+ "name": "_post_v_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_rels_parent_idx": {
+ "name": "_post_v_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_rels_path_idx": {
+ "name": "_post_v_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_post_v_rels_tag_id_idx": {
+ "name": "_post_v_rels_tag_id_idx",
+ "columns": [
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "_post_v_rels_parent_fk": {
+ "name": "_post_v_rels_parent_fk",
+ "tableFrom": "_post_v_rels",
+ "tableTo": "_post_v",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "_post_v_rels_tag_fk": {
+ "name": "_post_v_rels_tag_fk",
+ "tableFrom": "_post_v_rels",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tag": {
+ "name": "tag",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hidden": {
+ "name": "hidden",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tag_slug_idx": {
+ "name": "tag_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tag_updated_at_idx": {
+ "name": "tag_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tag_created_at_idx": {
+ "name": "tag_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.exports": {
+ "name": "exports",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "format": {
+ "name": "format",
+ "type": "enum_exports_format",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'csv'"
+ },
+ "limit": {
+ "name": "limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "page": {
+ "name": "page",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "sort": {
+ "name": "sort",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "enum_exports_sort_order",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "drafts": {
+ "name": "drafts",
+ "type": "enum_exports_drafts",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'yes'"
+ },
+ "collection_slug": {
+ "name": "collection_slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'signature'"
+ },
+ "where": {
+ "name": "where",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail_u_r_l": {
+ "name": "thumbnail_u_r_l",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filesize": {
+ "name": "filesize",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_x": {
+ "name": "focal_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_y": {
+ "name": "focal_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "exports_updated_at_idx": {
+ "name": "exports_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "exports_created_at_idx": {
+ "name": "exports_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "exports_filename_idx": {
+ "name": "exports_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.exports_texts": {
+ "name": "exports_texts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "exports_texts_order_parent": {
+ "name": "exports_texts_order_parent",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "exports_texts_parent_fk": {
+ "name": "exports_texts_parent_fk",
+ "tableFrom": "exports_texts",
+ "tableTo": "exports",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.imports": {
+ "name": "imports",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "collection_slug": {
+ "name": "collection_slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "import_mode": {
+ "name": "import_mode",
+ "type": "enum_imports_import_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "match_field": {
+ "name": "match_field",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'id'"
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_imports_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "summary_imported": {
+ "name": "summary_imported",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_updated": {
+ "name": "summary_updated",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_total": {
+ "name": "summary_total",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_issues": {
+ "name": "summary_issues",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_issue_details": {
+ "name": "summary_issue_details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail_u_r_l": {
+ "name": "thumbnail_u_r_l",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filesize": {
+ "name": "filesize",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_x": {
+ "name": "focal_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "focal_y": {
+ "name": "focal_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "imports_updated_at_idx": {
+ "name": "imports_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "imports_created_at_idx": {
+ "name": "imports_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "imports_filename_idx": {
+ "name": "imports_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_kv": {
+ "name": "payload_kv",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "payload_kv_key_idx": {
+ "name": "payload_kv_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_jobs_log": {
+ "name": "payload_jobs_log",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "executed_at": {
+ "name": "executed_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "task_slug": {
+ "name": "task_slug",
+ "type": "enum_payload_jobs_log_task_slug",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "task_i_d": {
+ "name": "task_i_d",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "input": {
+ "name": "input",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output": {
+ "name": "output",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "enum_payload_jobs_log_state",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error": {
+ "name": "error",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "payload_jobs_log_order_idx": {
+ "name": "payload_jobs_log_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_log_parent_id_idx": {
+ "name": "payload_jobs_log_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "payload_jobs_log_parent_id_fk": {
+ "name": "payload_jobs_log_parent_id_fk",
+ "tableFrom": "payload_jobs_log",
+ "tableTo": "payload_jobs",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_jobs": {
+ "name": "payload_jobs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "input": {
+ "name": "input",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_tried": {
+ "name": "total_tried",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "has_error": {
+ "name": "has_error",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "error": {
+ "name": "error",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "task_slug": {
+ "name": "task_slug",
+ "type": "enum_payload_jobs_task_slug",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "queue": {
+ "name": "queue",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'default'"
+ },
+ "wait_until": {
+ "name": "wait_until",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing": {
+ "name": "processing",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "payload_jobs_completed_at_idx": {
+ "name": "payload_jobs_completed_at_idx",
+ "columns": [
+ {
+ "expression": "completed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_total_tried_idx": {
+ "name": "payload_jobs_total_tried_idx",
+ "columns": [
+ {
+ "expression": "total_tried",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_has_error_idx": {
+ "name": "payload_jobs_has_error_idx",
+ "columns": [
+ {
+ "expression": "has_error",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_task_slug_idx": {
+ "name": "payload_jobs_task_slug_idx",
+ "columns": [
+ {
+ "expression": "task_slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_queue_idx": {
+ "name": "payload_jobs_queue_idx",
+ "columns": [
+ {
+ "expression": "queue",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_wait_until_idx": {
+ "name": "payload_jobs_wait_until_idx",
+ "columns": [
+ {
+ "expression": "wait_until",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_processing_idx": {
+ "name": "payload_jobs_processing_idx",
+ "columns": [
+ {
+ "expression": "processing",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_updated_at_idx": {
+ "name": "payload_jobs_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_jobs_created_at_idx": {
+ "name": "payload_jobs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_locked_documents": {
+ "name": "payload_locked_documents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "global_slug": {
+ "name": "global_slug",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "payload_locked_documents_global_slug_idx": {
+ "name": "payload_locked_documents_global_slug_idx",
+ "columns": [
+ {
+ "expression": "global_slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_updated_at_idx": {
+ "name": "payload_locked_documents_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_created_at_idx": {
+ "name": "payload_locked_documents_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_locked_documents_rels": {
+ "name": "payload_locked_documents_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "users_id": {
+ "name": "users_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_web_authn_credential_id": {
+ "name": "campaign_web_authn_credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_subscription_id": {
+ "name": "push_subscription_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_invite_id": {
+ "name": "campaign_invite_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_vote_summary_snapshot_id": {
+ "name": "campaign_vote_summary_snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "municipality_id": {
+ "name": "municipality_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_deputy_id": {
+ "name": "state_deputy_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vote_pledge_id": {
+ "name": "vote_pledge_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allocation_decision_id": {
+ "name": "allocation_decision_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_demand_id": {
+ "name": "campaign_demand_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "supporter_id": {
+ "name": "supporter_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "supporter_import_batch_id": {
+ "name": "supporter_import_batch_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "municipality_update_id": {
+ "name": "municipality_update_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "activity_id": {
+ "name": "activity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "speech_id": {
+ "name": "speech_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "speech_segment_id": {
+ "name": "speech_segment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "speech_cut_id": {
+ "name": "speech_cut_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reel_id": {
+ "name": "reel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reel_media_id": {
+ "name": "reel_media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recording_id": {
+ "name": "recording_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recording_media_id": {
+ "name": "recording_media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recording_segment_id": {
+ "name": "recording_segment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "calendar_feed_id": {
+ "name": "calendar_feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "google_calendar_sync_id": {
+ "name": "google_calendar_sync_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "election_tally_id": {
+ "name": "election_tally_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "election_candidate_vote_id": {
+ "name": "election_candidate_vote_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "election_candidate_id": {
+ "name": "election_candidate_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "petition_id": {
+ "name": "petition_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contact_id": {
+ "name": "contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_id": {
+ "name": "consent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signature_id": {
+ "name": "signature_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscription_id": {
+ "name": "subscription_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_id": {
+ "name": "post_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "payload_locked_documents_rels_order_idx": {
+ "name": "payload_locked_documents_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_parent_idx": {
+ "name": "payload_locked_documents_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_path_idx": {
+ "name": "payload_locked_documents_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_users_id_idx": {
+ "name": "payload_locked_documents_rels_users_id_idx",
+ "columns": [
+ {
+ "expression": "users_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_campaign_user_id_idx": {
+ "name": "payload_locked_documents_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_campaign_web_authn_credent_idx": {
+ "name": "payload_locked_documents_rels_campaign_web_authn_credent_idx",
+ "columns": [
+ {
+ "expression": "campaign_web_authn_credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_notification_id_idx": {
+ "name": "payload_locked_documents_rels_notification_id_idx",
+ "columns": [
+ {
+ "expression": "notification_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_push_subscription_id_idx": {
+ "name": "payload_locked_documents_rels_push_subscription_id_idx",
+ "columns": [
+ {
+ "expression": "push_subscription_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_campaign_invite_id_idx": {
+ "name": "payload_locked_documents_rels_campaign_invite_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_invite_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_campaign_vote_summary_snap_idx": {
+ "name": "payload_locked_documents_rels_campaign_vote_summary_snap_idx",
+ "columns": [
+ {
+ "expression": "campaign_vote_summary_snapshot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_municipality_id_idx": {
+ "name": "payload_locked_documents_rels_municipality_id_idx",
+ "columns": [
+ {
+ "expression": "municipality_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_leadership_id_idx": {
+ "name": "payload_locked_documents_rels_leadership_id_idx",
+ "columns": [
+ {
+ "expression": "leadership_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_organization_id_idx": {
+ "name": "payload_locked_documents_rels_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_state_deputy_id_idx": {
+ "name": "payload_locked_documents_rels_state_deputy_id_idx",
+ "columns": [
+ {
+ "expression": "state_deputy_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_vote_pledge_id_idx": {
+ "name": "payload_locked_documents_rels_vote_pledge_id_idx",
+ "columns": [
+ {
+ "expression": "vote_pledge_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_allocation_decision_id_idx": {
+ "name": "payload_locked_documents_rels_allocation_decision_id_idx",
+ "columns": [
+ {
+ "expression": "allocation_decision_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_campaign_demand_id_idx": {
+ "name": "payload_locked_documents_rels_campaign_demand_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_demand_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_supporter_id_idx": {
+ "name": "payload_locked_documents_rels_supporter_id_idx",
+ "columns": [
+ {
+ "expression": "supporter_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_supporter_import_batch_id_idx": {
+ "name": "payload_locked_documents_rels_supporter_import_batch_id_idx",
+ "columns": [
+ {
+ "expression": "supporter_import_batch_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_municipality_update_id_idx": {
+ "name": "payload_locked_documents_rels_municipality_update_id_idx",
+ "columns": [
+ {
+ "expression": "municipality_update_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_activity_id_idx": {
+ "name": "payload_locked_documents_rels_activity_id_idx",
+ "columns": [
+ {
+ "expression": "activity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_speech_id_idx": {
+ "name": "payload_locked_documents_rels_speech_id_idx",
+ "columns": [
+ {
+ "expression": "speech_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_speech_segment_id_idx": {
+ "name": "payload_locked_documents_rels_speech_segment_id_idx",
+ "columns": [
+ {
+ "expression": "speech_segment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_speech_cut_id_idx": {
+ "name": "payload_locked_documents_rels_speech_cut_id_idx",
+ "columns": [
+ {
+ "expression": "speech_cut_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_reel_id_idx": {
+ "name": "payload_locked_documents_rels_reel_id_idx",
+ "columns": [
+ {
+ "expression": "reel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_reel_media_id_idx": {
+ "name": "payload_locked_documents_rels_reel_media_id_idx",
+ "columns": [
+ {
+ "expression": "reel_media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_recording_id_idx": {
+ "name": "payload_locked_documents_rels_recording_id_idx",
+ "columns": [
+ {
+ "expression": "recording_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_recording_media_id_idx": {
+ "name": "payload_locked_documents_rels_recording_media_id_idx",
+ "columns": [
+ {
+ "expression": "recording_media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_recording_segment_id_idx": {
+ "name": "payload_locked_documents_rels_recording_segment_id_idx",
+ "columns": [
+ {
+ "expression": "recording_segment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_calendar_feed_id_idx": {
+ "name": "payload_locked_documents_rels_calendar_feed_id_idx",
+ "columns": [
+ {
+ "expression": "calendar_feed_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_google_calendar_sync_id_idx": {
+ "name": "payload_locked_documents_rels_google_calendar_sync_id_idx",
+ "columns": [
+ {
+ "expression": "google_calendar_sync_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_election_tally_id_idx": {
+ "name": "payload_locked_documents_rels_election_tally_id_idx",
+ "columns": [
+ {
+ "expression": "election_tally_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_election_candidate_vote_id_idx": {
+ "name": "payload_locked_documents_rels_election_candidate_vote_id_idx",
+ "columns": [
+ {
+ "expression": "election_candidate_vote_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_election_candidate_id_idx": {
+ "name": "payload_locked_documents_rels_election_candidate_id_idx",
+ "columns": [
+ {
+ "expression": "election_candidate_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_media_id_idx": {
+ "name": "payload_locked_documents_rels_media_id_idx",
+ "columns": [
+ {
+ "expression": "media_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_petition_id_idx": {
+ "name": "payload_locked_documents_rels_petition_id_idx",
+ "columns": [
+ {
+ "expression": "petition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_contact_id_idx": {
+ "name": "payload_locked_documents_rels_contact_id_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_consent_id_idx": {
+ "name": "payload_locked_documents_rels_consent_id_idx",
+ "columns": [
+ {
+ "expression": "consent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_signature_id_idx": {
+ "name": "payload_locked_documents_rels_signature_id_idx",
+ "columns": [
+ {
+ "expression": "signature_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_subscription_id_idx": {
+ "name": "payload_locked_documents_rels_subscription_id_idx",
+ "columns": [
+ {
+ "expression": "subscription_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_post_id_idx": {
+ "name": "payload_locked_documents_rels_post_id_idx",
+ "columns": [
+ {
+ "expression": "post_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_locked_documents_rels_tag_id_idx": {
+ "name": "payload_locked_documents_rels_tag_id_idx",
+ "columns": [
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "payload_locked_documents_rels_parent_fk": {
+ "name": "payload_locked_documents_rels_parent_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "payload_locked_documents",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_users_fk": {
+ "name": "payload_locked_documents_rels_users_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "users_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_campaign_user_fk": {
+ "name": "payload_locked_documents_rels_campaign_user_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_campaign_web_authn_credenti_fk": {
+ "name": "payload_locked_documents_rels_campaign_web_authn_credenti_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "campaign_web_authn_credential",
+ "columnsFrom": [
+ "campaign_web_authn_credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_notification_fk": {
+ "name": "payload_locked_documents_rels_notification_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "notification",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_push_subscription_fk": {
+ "name": "payload_locked_documents_rels_push_subscription_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "push_subscription",
+ "columnsFrom": [
+ "push_subscription_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_campaign_invite_fk": {
+ "name": "payload_locked_documents_rels_campaign_invite_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "campaign_invite",
+ "columnsFrom": [
+ "campaign_invite_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_campaign_vote_summary_snaps_fk": {
+ "name": "payload_locked_documents_rels_campaign_vote_summary_snaps_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "campaign_vote_summary_snapshot",
+ "columnsFrom": [
+ "campaign_vote_summary_snapshot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_municipality_fk": {
+ "name": "payload_locked_documents_rels_municipality_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "municipality",
+ "columnsFrom": [
+ "municipality_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_leadership_fk": {
+ "name": "payload_locked_documents_rels_leadership_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "leadership",
+ "columnsFrom": [
+ "leadership_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_organization_fk": {
+ "name": "payload_locked_documents_rels_organization_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_state_deputy_fk": {
+ "name": "payload_locked_documents_rels_state_deputy_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "state_deputy",
+ "columnsFrom": [
+ "state_deputy_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_vote_pledge_fk": {
+ "name": "payload_locked_documents_rels_vote_pledge_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "vote_pledge",
+ "columnsFrom": [
+ "vote_pledge_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_allocation_decision_fk": {
+ "name": "payload_locked_documents_rels_allocation_decision_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "allocation_decision",
+ "columnsFrom": [
+ "allocation_decision_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_campaign_demand_fk": {
+ "name": "payload_locked_documents_rels_campaign_demand_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "campaign_demand",
+ "columnsFrom": [
+ "campaign_demand_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_supporter_fk": {
+ "name": "payload_locked_documents_rels_supporter_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "supporter",
+ "columnsFrom": [
+ "supporter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_supporter_import_batch_fk": {
+ "name": "payload_locked_documents_rels_supporter_import_batch_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "supporter_import_batch",
+ "columnsFrom": [
+ "supporter_import_batch_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_municipality_update_fk": {
+ "name": "payload_locked_documents_rels_municipality_update_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "municipality_update",
+ "columnsFrom": [
+ "municipality_update_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_activity_fk": {
+ "name": "payload_locked_documents_rels_activity_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "activity",
+ "columnsFrom": [
+ "activity_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_speech_fk": {
+ "name": "payload_locked_documents_rels_speech_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "speech",
+ "columnsFrom": [
+ "speech_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_speech_segment_fk": {
+ "name": "payload_locked_documents_rels_speech_segment_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "speech_segment",
+ "columnsFrom": [
+ "speech_segment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_speech_cut_fk": {
+ "name": "payload_locked_documents_rels_speech_cut_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "speech_cut",
+ "columnsFrom": [
+ "speech_cut_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_reel_fk": {
+ "name": "payload_locked_documents_rels_reel_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "reel",
+ "columnsFrom": [
+ "reel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_reel_media_fk": {
+ "name": "payload_locked_documents_rels_reel_media_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "reel_media",
+ "columnsFrom": [
+ "reel_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_recording_fk": {
+ "name": "payload_locked_documents_rels_recording_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "recording",
+ "columnsFrom": [
+ "recording_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_recording_media_fk": {
+ "name": "payload_locked_documents_rels_recording_media_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "recording_media",
+ "columnsFrom": [
+ "recording_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_recording_segment_fk": {
+ "name": "payload_locked_documents_rels_recording_segment_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "recording_segment",
+ "columnsFrom": [
+ "recording_segment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_calendar_feed_fk": {
+ "name": "payload_locked_documents_rels_calendar_feed_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "calendar_feed",
+ "columnsFrom": [
+ "calendar_feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_google_calendar_sync_fk": {
+ "name": "payload_locked_documents_rels_google_calendar_sync_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "google_calendar_sync",
+ "columnsFrom": [
+ "google_calendar_sync_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_election_tally_fk": {
+ "name": "payload_locked_documents_rels_election_tally_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "election_tally",
+ "columnsFrom": [
+ "election_tally_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_election_candidate_vote_fk": {
+ "name": "payload_locked_documents_rels_election_candidate_vote_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "election_candidate_vote",
+ "columnsFrom": [
+ "election_candidate_vote_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_election_candidate_fk": {
+ "name": "payload_locked_documents_rels_election_candidate_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "election_candidate",
+ "columnsFrom": [
+ "election_candidate_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_media_fk": {
+ "name": "payload_locked_documents_rels_media_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_petition_fk": {
+ "name": "payload_locked_documents_rels_petition_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "petition",
+ "columnsFrom": [
+ "petition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_contact_fk": {
+ "name": "payload_locked_documents_rels_contact_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_consent_fk": {
+ "name": "payload_locked_documents_rels_consent_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "consent",
+ "columnsFrom": [
+ "consent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_signature_fk": {
+ "name": "payload_locked_documents_rels_signature_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "signature",
+ "columnsFrom": [
+ "signature_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_subscription_fk": {
+ "name": "payload_locked_documents_rels_subscription_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "subscription",
+ "columnsFrom": [
+ "subscription_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_post_fk": {
+ "name": "payload_locked_documents_rels_post_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "post",
+ "columnsFrom": [
+ "post_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_locked_documents_rels_tag_fk": {
+ "name": "payload_locked_documents_rels_tag_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_preferences": {
+ "name": "payload_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "payload_preferences_key_idx": {
+ "name": "payload_preferences_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_updated_at_idx": {
+ "name": "payload_preferences_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_created_at_idx": {
+ "name": "payload_preferences_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_preferences_rels": {
+ "name": "payload_preferences_rels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "users_id": {
+ "name": "users_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "campaign_user_id": {
+ "name": "campaign_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "payload_preferences_rels_order_idx": {
+ "name": "payload_preferences_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_rels_parent_idx": {
+ "name": "payload_preferences_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_rels_path_idx": {
+ "name": "payload_preferences_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_rels_users_id_idx": {
+ "name": "payload_preferences_rels_users_id_idx",
+ "columns": [
+ {
+ "expression": "users_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_preferences_rels_campaign_user_id_idx": {
+ "name": "payload_preferences_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "payload_preferences_rels_parent_fk": {
+ "name": "payload_preferences_rels_parent_fk",
+ "tableFrom": "payload_preferences_rels",
+ "tableTo": "payload_preferences",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_preferences_rels_users_fk": {
+ "name": "payload_preferences_rels_users_fk",
+ "tableFrom": "payload_preferences_rels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "users_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "payload_preferences_rels_campaign_user_fk": {
+ "name": "payload_preferences_rels_campaign_user_fk",
+ "tableFrom": "payload_preferences_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.payload_migrations": {
+ "name": "payload_migrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "batch": {
+ "name": "batch",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "payload_migrations_updated_at_idx": {
+ "name": "payload_migrations_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "payload_migrations_created_at_idx": {
+ "name": "payload_migrations_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.site_settings_social_links": {
+ "name": "site_settings_social_links",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "enum_site_settings_social_links_platform",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "site_settings_social_links_order_idx": {
+ "name": "site_settings_social_links_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "site_settings_social_links_parent_id_idx": {
+ "name": "site_settings_social_links_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "site_settings_social_links_parent_id_fk": {
+ "name": "site_settings_social_links_parent_id_fk",
+ "tableFrom": "site_settings_social_links",
+ "tableTo": "site_settings",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.site_settings": {
+ "name": "site_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "header_title": {
+ "name": "header_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tracking_facebook_pixel_id": {
+ "name": "tracking_facebook_pixel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "_status": {
+ "name": "_status",
+ "type": "enum_site_settings_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "site_settings__status_idx": {
+ "name": "site_settings__status_idx",
+ "columns": [
+ {
+ "expression": "_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._site_settings_v_version_social_links": {
+ "name": "_site_settings_v_version_social_links",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "enum__site_settings_v_version_social_links_platform",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "_uuid": {
+ "name": "_uuid",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "_site_settings_v_version_social_links_order_idx": {
+ "name": "_site_settings_v_version_social_links_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_site_settings_v_version_social_links_parent_id_idx": {
+ "name": "_site_settings_v_version_social_links_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "_site_settings_v_version_social_links_parent_id_fk": {
+ "name": "_site_settings_v_version_social_links_parent_id_fk",
+ "tableFrom": "_site_settings_v_version_social_links",
+ "tableTo": "_site_settings_v",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public._site_settings_v": {
+ "name": "_site_settings_v",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "version_header_title": {
+ "name": "version_header_title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_tracking_facebook_pixel_id": {
+ "name": "version_tracking_facebook_pixel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version__status": {
+ "name": "version__status",
+ "type": "enum__site_settings_v_version_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'draft'"
+ },
+ "version_updated_at": {
+ "name": "version_updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version_created_at": {
+ "name": "version_created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "latest": {
+ "name": "latest",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autosave": {
+ "name": "autosave",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "_site_settings_v_version_version__status_idx": {
+ "name": "_site_settings_v_version_version__status_idx",
+ "columns": [
+ {
+ "expression": "version__status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_site_settings_v_created_at_idx": {
+ "name": "_site_settings_v_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_site_settings_v_updated_at_idx": {
+ "name": "_site_settings_v_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_site_settings_v_latest_idx": {
+ "name": "_site_settings_v_latest_idx",
+ "columns": [
+ {
+ "expression": "latest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "_site_settings_v_autosave_idx": {
+ "name": "_site_settings_v_autosave_idx",
+ "columns": [
+ {
+ "expression": "autosave",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.home": {
+ "name": "home",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "home_image_idx": {
+ "name": "home_image_idx",
+ "columns": [
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "home_image_id_media_id_fk": {
+ "name": "home_image_id_media_id_fk",
+ "tableFrom": "home",
+ "tableTo": "media",
+ "columnsFrom": [
+ "image_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.metadata_keywords": {
+ "name": "metadata_keywords",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "keyword": {
+ "name": "keyword",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "metadata_keywords_order_idx": {
+ "name": "metadata_keywords_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "metadata_keywords_parent_id_idx": {
+ "name": "metadata_keywords_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "metadata_keywords_parent_id_fk": {
+ "name": "metadata_keywords_parent_id_fk",
+ "tableFrom": "metadata_keywords",
+ "tableTo": "metadata",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.metadata": {
+ "name": "metadata",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "open_graph_site_name": {
+ "name": "open_graph_site_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "open_graph_description": {
+ "name": "open_graph_description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "twitter_creator": {
+ "name": "twitter_creator",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "twitter_description": {
+ "name": "twitter_description",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "metadata_image_idx": {
+ "name": "metadata_image_idx",
+ "columns": [
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "metadata_image_id_media_id_fk": {
+ "name": "metadata_image_id_media_id_fk",
+ "tableFrom": "metadata",
+ "tableTo": "media",
+ "columnsFrom": [
+ "image_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.privacy_policy": {
+ "name": "privacy_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "body": {
+ "name": "body",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.campaign_goals": {
+ "name": "campaign_goals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "state_goal": {
+ "name": "state_goal",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 150000
+ },
+ "margin": {
+ "name": "margin",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_year": {
+ "name": "base_year",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.social_feed_settings_excluded_items": {
+ "name": "social_feed_settings_excluded_items",
+ "schema": "",
+ "columns": {
+ "_order": {
+ "name": "_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "_parent_id": {
+ "name": "_parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "varchar",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "enum_social_feed_settings_excluded_items_platform",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "item_id": {
+ "name": "item_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "social_feed_settings_excluded_items_order_idx": {
+ "name": "social_feed_settings_excluded_items_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "social_feed_settings_excluded_items_parent_id_idx": {
+ "name": "social_feed_settings_excluded_items_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "social_feed_settings_excluded_items_parent_id_fk": {
+ "name": "social_feed_settings_excluded_items_parent_id_fk",
+ "tableFrom": "social_feed_settings_excluded_items",
+ "tableTo": "social_feed_settings",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.social_feed_settings": {
+ "name": "social_feed_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "youtube_enabled": {
+ "name": "youtube_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "youtube_api_key": {
+ "name": "youtube_api_key",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "youtube_channel_id": {
+ "name": "youtube_channel_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "youtube_max_items": {
+ "name": "youtube_max_items",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "instagram_enabled": {
+ "name": "instagram_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "instagram_access_token": {
+ "name": "instagram_access_token",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_user_id": {
+ "name": "instagram_user_id",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_max_items": {
+ "name": "instagram_max_items",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "youtube_feed_snapshot": {
+ "name": "youtube_feed_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_feed_snapshot": {
+ "name": "instagram_feed_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_sync_status": {
+ "name": "instagram_sync_status",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.enum_users_roles": {
+ "name": "enum_users_roles",
+ "schema": "public",
+ "values": [
+ "admin",
+ "editor"
+ ]
+ },
+ "public.enum_campaign_user_role": {
+ "name": "enum_campaign_user_role",
+ "schema": "public",
+ "values": [
+ "coordinator",
+ "advisor",
+ "candidate",
+ "communicator",
+ "leader"
+ ]
+ },
+ "public.enum_campaign_user_visibility": {
+ "name": "enum_campaign_user_visibility",
+ "schema": "public",
+ "values": [
+ "carteira",
+ "tudo"
+ ]
+ },
+ "public.enum_campaign_user_editing": {
+ "name": "enum_campaign_user_editing",
+ "schema": "public",
+ "values": [
+ "carteira",
+ "tudo",
+ "somente_leitura"
+ ]
+ },
+ "public.enum_notification_type": {
+ "name": "enum_notification_type",
+ "schema": "public",
+ "values": [
+ "municipality_update",
+ "new_supporter",
+ "activity_attention",
+ "invite_accepted"
+ ]
+ },
+ "public.enum_campaign_invite_kind": {
+ "name": "enum_campaign_invite_kind",
+ "schema": "public",
+ "values": [
+ "login",
+ "autopreenchimento"
+ ]
+ },
+ "public.enum_municipality_kind": {
+ "name": "enum_municipality_kind",
+ "schema": "public",
+ "values": [
+ "municipio",
+ "zona"
+ ]
+ },
+ "public.enum_municipality_priority": {
+ "name": "enum_municipality_priority",
+ "schema": "public",
+ "values": [
+ "alta",
+ "normal"
+ ]
+ },
+ "public.enum_municipality_engagement_level": {
+ "name": "enum_municipality_engagement_level",
+ "schema": "public",
+ "values": [
+ "n0",
+ "n1",
+ "n2",
+ "n3",
+ "n4"
+ ]
+ },
+ "public.enum_municipality_political_trend_status": {
+ "name": "enum_municipality_political_trend_status",
+ "schema": "public",
+ "values": [
+ "favoravel",
+ "neutra",
+ "desfavoravel"
+ ]
+ },
+ "public.enum_leadership_support_status": {
+ "name": "enum_leadership_support_status",
+ "schema": "public",
+ "values": [
+ "engajado",
+ "a_abordar",
+ "em_disputa",
+ "lembranca",
+ "negativo"
+ ]
+ },
+ "public.enum_organization_kind": {
+ "name": "enum_organization_kind",
+ "schema": "public",
+ "values": [
+ "sindicato",
+ "associacao",
+ "religioso",
+ "movimento",
+ "categoria_profissional",
+ "outro"
+ ]
+ },
+ "public.enum_allocation_decision_outcome": {
+ "name": "enum_allocation_decision_outcome",
+ "schema": "public",
+ "values": [
+ "aceita",
+ "descarta",
+ "movimento",
+ "adiada"
+ ]
+ },
+ "public.enum_campaign_demand_status_history_status": {
+ "name": "enum_campaign_demand_status_history_status",
+ "schema": "public",
+ "values": [
+ "aberta",
+ "em_analise",
+ "escalada",
+ "aprovada",
+ "rejeitada"
+ ]
+ },
+ "public.enum_campaign_demand_kind": {
+ "name": "enum_campaign_demand_kind",
+ "schema": "public",
+ "values": [
+ "material",
+ "servico",
+ "transporte",
+ "alimentacao",
+ "infraestrutura",
+ "espaco",
+ "equipamento",
+ "pessoal_apoio",
+ "outro"
+ ]
+ },
+ "public.enum_campaign_demand_status": {
+ "name": "enum_campaign_demand_status",
+ "schema": "public",
+ "values": [
+ "aberta",
+ "em_analise",
+ "escalada",
+ "aprovada",
+ "rejeitada"
+ ]
+ },
+ "public.enum_supporter_vote_intention": {
+ "name": "enum_supporter_vote_intention",
+ "schema": "public",
+ "values": [
+ "certo",
+ "tende_a_certo",
+ "indeciso",
+ "outro"
+ ]
+ },
+ "public.enum_supporter_source": {
+ "name": "enum_supporter_source",
+ "schema": "public",
+ "values": [
+ "import_csv",
+ "manual",
+ "lideranca",
+ "convite",
+ "evento"
+ ]
+ },
+ "public.enum_municipality_update_polarity": {
+ "name": "enum_municipality_update_polarity",
+ "schema": "public",
+ "values": [
+ "boa",
+ "neutra",
+ "ruim"
+ ]
+ },
+ "public.enum_activity_status": {
+ "name": "enum_activity_status",
+ "schema": "public",
+ "values": [
+ "confirmado",
+ "realizado",
+ "cancelado"
+ ]
+ },
+ "public.enum_speech_topics": {
+ "name": "enum_speech_topics",
+ "schema": "public",
+ "values": [
+ "saude",
+ "educacao",
+ "cultura",
+ "esporte",
+ "seguranca-publica",
+ "meio-ambiente",
+ "economia-trabalho",
+ "direitos-humanos",
+ "infraestrutura",
+ "ciencia-tecnologia",
+ "politica-instituicoes",
+ "agricultura",
+ "habitacao-cidades",
+ "comunicacao-midia",
+ "igualdade-racial",
+ "mulheres-genero",
+ "juventude",
+ "pessoa-deficiencia"
+ ]
+ },
+ "public.enum_speech_scopes": {
+ "name": "enum_speech_scopes",
+ "schema": "public",
+ "values": [
+ "bahia",
+ "brasil",
+ "internacional"
+ ]
+ },
+ "public.enum_speech_legislature": {
+ "name": "enum_speech_legislature",
+ "schema": "public",
+ "values": [
+ "54",
+ "55",
+ "56",
+ "57"
+ ]
+ },
+ "public.enum_speech_classified_by": {
+ "name": "enum_speech_classified_by",
+ "schema": "public",
+ "values": [
+ "gazetteer",
+ "llm",
+ "manual"
+ ]
+ },
+ "public.enum_speech_cut_status": {
+ "name": "enum_speech_cut_status",
+ "schema": "public",
+ "values": [
+ "processing",
+ "published",
+ "unpublished",
+ "failed"
+ ]
+ },
+ "public.enum_speech_cut_step": {
+ "name": "enum_speech_cut_step",
+ "schema": "public",
+ "values": [
+ "resolving",
+ "cutting",
+ "metadata",
+ "publishing"
+ ]
+ },
+ "public.enum_reel_feature": {
+ "name": "enum_reel_feature",
+ "schema": "public",
+ "values": [
+ "cards"
+ ]
+ },
+ "public.enum_reel_status": {
+ "name": "enum_reel_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published",
+ "unpublished"
+ ]
+ },
+ "public.enum_recording_status": {
+ "name": "enum_recording_status",
+ "schema": "public",
+ "values": [
+ "uploading",
+ "processing",
+ "ready",
+ "failed"
+ ]
+ },
+ "public.enum_recording_step": {
+ "name": "enum_recording_step",
+ "schema": "public",
+ "values": [
+ "extracting",
+ "transcribing",
+ "saving"
+ ]
+ },
+ "public.enum_election_tally_office": {
+ "name": "enum_election_tally_office",
+ "schema": "public",
+ "values": [
+ "presidente",
+ "governador",
+ "deputado_federal",
+ "deputado_estadual"
+ ]
+ },
+ "public.enum_election_tally_turn": {
+ "name": "enum_election_tally_turn",
+ "schema": "public",
+ "values": [
+ "1",
+ "2"
+ ]
+ },
+ "public.enum_election_candidate_vote_office": {
+ "name": "enum_election_candidate_vote_office",
+ "schema": "public",
+ "values": [
+ "presidente",
+ "governador",
+ "deputado_federal",
+ "deputado_estadual"
+ ]
+ },
+ "public.enum_election_candidate_vote_turn": {
+ "name": "enum_election_candidate_vote_turn",
+ "schema": "public",
+ "values": [
+ "1",
+ "2"
+ ]
+ },
+ "public.enum_election_candidate_vote_vote_type": {
+ "name": "enum_election_candidate_vote_vote_type",
+ "schema": "public",
+ "values": [
+ "nominal",
+ "legenda"
+ ]
+ },
+ "public.enum_election_candidate_office": {
+ "name": "enum_election_candidate_office",
+ "schema": "public",
+ "values": [
+ "presidente",
+ "governador",
+ "deputado_federal",
+ "deputado_estadual"
+ ]
+ },
+ "public.enum_election_candidate_turn": {
+ "name": "enum_election_candidate_turn",
+ "schema": "public",
+ "values": [
+ "1",
+ "2"
+ ]
+ },
+ "public.enum_election_candidate_elected_by": {
+ "name": "enum_election_candidate_elected_by",
+ "schema": "public",
+ "values": [
+ "QP",
+ "média",
+ "2º turno"
+ ]
+ },
+ "public.enum_election_candidate_running_again2026": {
+ "name": "enum_election_candidate_running_again2026",
+ "schema": "public",
+ "values": [
+ "sim",
+ "nao",
+ "desconhecido"
+ ]
+ },
+ "public.enum_petition_status": {
+ "name": "enum_petition_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum__petition_v_version_status": {
+ "name": "enum__petition_v_version_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum_contact_gender": {
+ "name": "enum_contact_gender",
+ "schema": "public",
+ "values": [
+ "feminino",
+ "masculino",
+ "outro",
+ "nao_informado"
+ ]
+ },
+ "public.enum_contact_state": {
+ "name": "enum_contact_state",
+ "schema": "public",
+ "values": [
+ "AC",
+ "AL",
+ "AM",
+ "AP",
+ "BA",
+ "CE",
+ "DF",
+ "ES",
+ "GO",
+ "MA",
+ "MG",
+ "MS",
+ "MT",
+ "PA",
+ "PB",
+ "PE",
+ "PI",
+ "PR",
+ "RJ",
+ "RN",
+ "RO",
+ "RR",
+ "RS",
+ "SC",
+ "SE",
+ "SP",
+ "TO"
+ ]
+ },
+ "public.enum_subscription_campaign_level": {
+ "name": "enum_subscription_campaign_level",
+ "schema": "public",
+ "values": [
+ "time",
+ "esporadico"
+ ]
+ },
+ "public.enum_post_type": {
+ "name": "enum_post_type",
+ "schema": "public",
+ "values": [
+ "noticia",
+ "campanha",
+ "artigo",
+ "evento"
+ ]
+ },
+ "public.enum_post_status": {
+ "name": "enum_post_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum__post_v_version_type": {
+ "name": "enum__post_v_version_type",
+ "schema": "public",
+ "values": [
+ "noticia",
+ "campanha",
+ "artigo",
+ "evento"
+ ]
+ },
+ "public.enum__post_v_version_status": {
+ "name": "enum__post_v_version_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum_exports_format": {
+ "name": "enum_exports_format",
+ "schema": "public",
+ "values": [
+ "csv",
+ "json"
+ ]
+ },
+ "public.enum_exports_sort_order": {
+ "name": "enum_exports_sort_order",
+ "schema": "public",
+ "values": [
+ "asc",
+ "desc"
+ ]
+ },
+ "public.enum_exports_drafts": {
+ "name": "enum_exports_drafts",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no"
+ ]
+ },
+ "public.enum_imports_import_mode": {
+ "name": "enum_imports_import_mode",
+ "schema": "public",
+ "values": [
+ "create",
+ "update",
+ "upsert"
+ ]
+ },
+ "public.enum_imports_status": {
+ "name": "enum_imports_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "completed",
+ "partial",
+ "failed"
+ ]
+ },
+ "public.enum_payload_jobs_log_task_slug": {
+ "name": "enum_payload_jobs_log_task_slug",
+ "schema": "public",
+ "values": [
+ "inline",
+ "createCollectionExport",
+ "createCollectionImport",
+ "schedulePublish"
+ ]
+ },
+ "public.enum_payload_jobs_log_state": {
+ "name": "enum_payload_jobs_log_state",
+ "schema": "public",
+ "values": [
+ "failed",
+ "succeeded"
+ ]
+ },
+ "public.enum_payload_jobs_task_slug": {
+ "name": "enum_payload_jobs_task_slug",
+ "schema": "public",
+ "values": [
+ "inline",
+ "createCollectionExport",
+ "createCollectionImport",
+ "schedulePublish"
+ ]
+ },
+ "public.enum_site_settings_social_links_platform": {
+ "name": "enum_site_settings_social_links_platform",
+ "schema": "public",
+ "values": [
+ "instagram",
+ "facebook",
+ "youtube",
+ "whatsapp"
+ ]
+ },
+ "public.enum_site_settings_status": {
+ "name": "enum_site_settings_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum__site_settings_v_version_social_links_platform": {
+ "name": "enum__site_settings_v_version_social_links_platform",
+ "schema": "public",
+ "values": [
+ "instagram",
+ "facebook",
+ "youtube",
+ "whatsapp"
+ ]
+ },
+ "public.enum__site_settings_v_version_status": {
+ "name": "enum__site_settings_v_version_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published"
+ ]
+ },
+ "public.enum_social_feed_settings_excluded_items_platform": {
+ "name": "enum_social_feed_settings_excluded_items_platform",
+ "schema": "public",
+ "values": [
+ "youtube",
+ "instagram"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "id": "61a1f946-2455-495c-8629-2fa15e4151e6",
+ "prevId": "00000000-0000-0000-0000-000000000000"
+}
\ No newline at end of file
diff --git a/src/migrations/20260919_032729_add_recording_speakers.ts b/src/migrations/20260919_032729_add_recording_speakers.ts
new file mode 100644
index 00000000..b1f1463a
--- /dev/null
+++ b/src/migrations/20260919_032729_add_recording_speakers.ts
@@ -0,0 +1,51 @@
+import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
+
+/**
+ * C200 — speaker grouping of the uploaded recordings: the anonymous cluster key
+ * on each segment, the human labels on the recording (one row per identified
+ * cluster) and the flag a reprocessing sets when a label could not be
+ * re-associated. `recording_texts` is Payload's join table for the derived
+ * `speakerNames` (`hasMany text`).
+ *
+ * The generated diff also carried `reel.source_hash` because the C199 snapshot
+ * (20260919_003440) was taken before the sibling `20260919_002605` landed — the
+ * column already exists in every environment, so that SQL was removed here by
+ * hand. The snapshot JSON of THIS migration is current, so the phantom diff
+ * does not come back.
+ */
+export async function up({ db, payload, req }: MigrateUpArgs): Promise {
+ await db.execute(sql`
+ CREATE TABLE "recording_speaker_labels" (
+ "_order" integer NOT NULL,
+ "_parent_id" integer NOT NULL,
+ "id" varchar PRIMARY KEY NOT NULL,
+ "speaker_key" varchar NOT NULL,
+ "label" varchar NOT NULL
+ );
+
+ CREATE TABLE "recording_texts" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "order" integer NOT NULL,
+ "parent_id" integer NOT NULL,
+ "path" varchar NOT NULL,
+ "text" varchar
+ );
+
+ ALTER TABLE "recording" ADD COLUMN "speaker_labels_dropped" boolean;
+ ALTER TABLE "recording_segment" ADD COLUMN "speaker_key" varchar;
+ ALTER TABLE "recording_speaker_labels" ADD CONSTRAINT "recording_speaker_labels_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."recording"("id") ON DELETE cascade ON UPDATE no action;
+ ALTER TABLE "recording_texts" ADD CONSTRAINT "recording_texts_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."recording"("id") ON DELETE cascade ON UPDATE no action;
+ CREATE INDEX "recording_speaker_labels_order_idx" ON "recording_speaker_labels" USING btree ("_order");
+ CREATE INDEX "recording_speaker_labels_parent_id_idx" ON "recording_speaker_labels" USING btree ("_parent_id");
+ CREATE INDEX "recording_texts_order_parent" ON "recording_texts" USING btree ("order","parent_id");`)
+}
+
+export async function down({ db, payload, req }: MigrateDownArgs): Promise {
+ await db.execute(sql`
+ ALTER TABLE "recording_speaker_labels" DISABLE ROW LEVEL SECURITY;
+ ALTER TABLE "recording_texts" DISABLE ROW LEVEL SECURITY;
+ DROP TABLE "recording_speaker_labels" CASCADE;
+ DROP TABLE "recording_texts" CASCADE;
+ ALTER TABLE "recording" DROP COLUMN "speaker_labels_dropped";
+ ALTER TABLE "recording_segment" DROP COLUMN "speaker_key";`)
+}
diff --git a/src/migrations/index.ts b/src/migrations/index.ts
index d84eb49e..ba7f40fe 100644
--- a/src/migrations/index.ts
+++ b/src/migrations/index.ts
@@ -75,6 +75,7 @@ import * as migration_20260915_142058_activity_google_import_link from './202609
import * as migration_20260918_201009_add_reel from './20260918_201009_add_reel';
import * as migration_20260919_002605_add_reel_source_hash from './20260919_002605_add_reel_source_hash';
import * as migration_20260919_003440_add_recording from './20260919_003440_add_recording';
+import * as migration_20260919_032729_add_recording_speakers from './20260919_032729_add_recording_speakers';
export const migrations = [
{
@@ -455,11 +456,16 @@ export const migrations = [
{
up: migration_20260919_002605_add_reel_source_hash.up,
down: migration_20260919_002605_add_reel_source_hash.down,
- name: '20260919_002605_add_reel_source_hash'
+ name: '20260919_002605_add_reel_source_hash',
},
{
up: migration_20260919_003440_add_recording.up,
down: migration_20260919_003440_add_recording.down,
- name: '20260919_003440_add_recording'
+ name: '20260919_003440_add_recording',
+ },
+ {
+ up: migration_20260919_032729_add_recording_speakers.up,
+ down: migration_20260919_032729_add_recording_speakers.down,
+ name: '20260919_032729_add_recording_speakers'
},
];
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 9f83e547..241cc3db 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -1175,6 +1175,24 @@ export interface Recording {
* Concatenação normalizada dos segmentos (sem acentos, minúsculas).
*/
searchText?: string | null;
+ /**
+ * Rótulos humanos por agrupamento acústico (chave → nome). Identificação sempre humana; o acervo nunca sugere ou infere pessoas.
+ */
+ speakerLabels?:
+ | {
+ speakerKey: string;
+ label: string;
+ id?: string | null;
+ }[]
+ | null;
+ /**
+ * Derivado dos rótulos (sem duplicatas, sem diferenciar maiúsculas) para a faceta "Pessoa".
+ */
+ speakerNames?: string[] | null;
+ /**
+ * Ligado quando um reprocessamento reordenou os agrupamentos e algum rótulo não pôde ser re-vinculado.
+ */
+ speakerLabelsDropped?: boolean | null;
/**
* Motivo interno da falha; nunca vai à pessoa com o detalhe cru.
*/
@@ -1215,6 +1233,10 @@ export interface RecordingSegment {
id: number;
recording: number | Recording;
order: number;
+ /**
+ * Chave do agrupamento acústico ("Falante N") do trecho; vazio sem diarização.
+ */
+ speakerKey?: string | null;
startSeconds: number;
endSeconds: number;
text: string;
@@ -2425,6 +2447,15 @@ export interface RecordingSelect {
media?: T;
durationSeconds?: T;
searchText?: T;
+ speakerLabels?:
+ | T
+ | {
+ speakerKey?: T;
+ label?: T;
+ id?: T;
+ };
+ speakerNames?: T;
+ speakerLabelsDropped?: T;
error?: T;
createdBy?: T;
updatedAt?: T;
@@ -2455,6 +2486,7 @@ export interface RecordingMediaSelect {
export interface RecordingSegmentSelect {
recording?: T;
order?: T;
+ speakerKey?: T;
startSeconds?: T;
endSeconds?: T;
text?: T;
diff --git a/src/utilities/ai/assemblyAiDiarize.ts b/src/utilities/ai/assemblyAiDiarize.ts
new file mode 100644
index 00000000..cfc6ba0c
--- /dev/null
+++ b/src/utilities/ai/assemblyAiDiarize.ts
@@ -0,0 +1,178 @@
+import 'server-only'
+
+import { openAsBlob } from 'node:fs'
+
+import type { DiarizedTurn } from '@/lib/recordingDiarization'
+
+/**
+ * C200 — the diarization provider: AssemblyAI's async pre-recorded API with
+ * `speaker_labels` (acoustic clustering only). The recording is sent as ONE
+ * file so the speaker numbering is global; the voice is never matched to a
+ * person and the account-level speaker identification feature is deliberately
+ * never enabled — naming is human (design scenes 1/2).
+ *
+ * Never throws: every failure becomes `{ ok: false }` and the transcription job
+ * degrades to the honest single-block transcript (no speaker grouping) instead
+ * of failing the recording. The remote transcript/audio is deleted best-effort
+ * after the read.
+ *
+ * Provider notes (verified 2026-09-19): Universal-2 at US$0,15/h + async
+ * diarization add-on US$0,02/h; up to 10 h / 5 GB per file; `speech_models` is
+ * the plural request field (the singular is deprecated).
+ */
+
+export const ASSEMBLYAI_UPLOAD_URL = 'https://api.assemblyai.com/v2/upload'
+export const ASSEMBLYAI_TRANSCRIPT_URL = 'https://api.assemblyai.com/v2/transcript'
+const ASSEMBLYAI_MODEL = 'universal-2'
+const ASSEMBLYAI_LANGUAGE = 'pt'
+
+const UPLOAD_TIMEOUT_MS = 10 * 60_000
+const POLL_REQUEST_TIMEOUT_MS = 30_000
+const DIARIZATION_DEADLINE_MS = 60 * 60_000
+const POLL_INTERVAL_MS = 5_000
+
+const DIARIZE_ERROR = 'Não foi possível separar os falantes da gravação.'
+
+type DiarizeRequestOptions = {
+ /** Called after each poll — lets the job keep its heartbeat alive. */
+ onProgress?: () => Promise | void
+ /** Poll cadence; only tests change it (default 5 s). */
+ pollIntervalMs?: number
+}
+
+type DiarizeResult =
+ | { ok: true; turns: DiarizedTurn[] }
+ | { ok: false; error: string; status: number }
+
+/** The seam the transcription job consumes; tests inject a fake. */
+export type SpeakerDiarizer = (
+ file: Blob,
+ options?: DiarizeRequestOptions,
+) => Promise
+
+const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms))
+
+const authHeaders = (apiKey: string, json = false): Record => ({
+ authorization: apiKey,
+ ...(json ? { 'content-type': 'application/json' } : {}),
+})
+
+/** `A`→`speaker-A`… provider cluster ids stay opaque; the pure module numbers them. */
+const normalizeUtterances = (json: unknown): DiarizedTurn[] => {
+ const data = json && typeof json === 'object' ? (json as Record) : {}
+ const utterances = Array.isArray(data.utterances) ? data.utterances : []
+ const turns: DiarizedTurn[] = []
+ for (const raw of utterances) {
+ const utterance = raw && typeof raw === 'object' ? (raw as Record) : {}
+ const speaker = typeof utterance.speaker === 'string' ? utterance.speaker.trim() : ''
+ const startMs = Number(utterance.start)
+ const endMs = Number(utterance.end)
+ if (!speaker || !Number.isFinite(startMs) || !Number.isFinite(endMs)) continue
+ turns.push({
+ speaker,
+ startSeconds: Math.max(0, startMs / 1000),
+ endSeconds: Math.max(0, endMs / 1000),
+ })
+ }
+ return turns
+}
+
+/** Best-effort delete of the remote transcript (privacy hygiene, never fatal). */
+const deleteRemoteTranscript = async (apiKey: string, transcriptId: string): Promise => {
+ try {
+ await fetch(`${ASSEMBLYAI_TRANSCRIPT_URL}/${transcriptId}`, {
+ method: 'DELETE',
+ headers: authHeaders(apiKey),
+ signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),
+ })
+ } catch {
+ // Best-effort: a failed delete never affects the recording.
+ }
+}
+
+/**
+ * Uploads the audio, submits the diarization transcript and polls until it is
+ * ready. `file` is a Blob opened from the extracted MP3 (the job owns the temp
+ * file); the provider call itself keeps no copy after the best-effort delete.
+ */
+export const assemblyAiDiarize: SpeakerDiarizer = async (
+ file,
+ { onProgress, pollIntervalMs = POLL_INTERVAL_MS } = {},
+) => {
+ const apiKey = process.env.ASSEMBLYAI_API_KEY
+ if (!apiKey) {
+ return { ok: false, error: 'Separação de falantes está indisponível no momento.', status: 503 }
+ }
+
+ let transcriptId: string | null = null
+ try {
+ const uploadResponse = await fetch(ASSEMBLYAI_UPLOAD_URL, {
+ method: 'POST',
+ headers: authHeaders(apiKey),
+ body: file,
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS),
+ })
+ if (!uploadResponse.ok) return { ok: false, error: DIARIZE_ERROR, status: 502 }
+
+ const uploaded = (await uploadResponse.json()) as { upload_url?: unknown }
+ const uploadUrl = typeof uploaded.upload_url === 'string' ? uploaded.upload_url : ''
+ if (!uploadUrl) return { ok: false, error: DIARIZE_ERROR, status: 502 }
+
+ const createResponse = await fetch(ASSEMBLYAI_TRANSCRIPT_URL, {
+ method: 'POST',
+ headers: authHeaders(apiKey, true),
+ body: JSON.stringify({
+ audio_url: uploadUrl,
+ language_code: ASSEMBLYAI_LANGUAGE,
+ speaker_labels: true,
+ speech_models: [ASSEMBLYAI_MODEL],
+ }),
+ signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),
+ })
+ if (!createResponse.ok) return { ok: false, error: DIARIZE_ERROR, status: 502 }
+
+ const created = (await createResponse.json()) as { id?: unknown; status?: unknown }
+ transcriptId = typeof created.id === 'string' ? created.id : null
+ if (!transcriptId) return { ok: false, error: DIARIZE_ERROR, status: 502 }
+
+ const deadline = Date.now() + DIARIZATION_DEADLINE_MS
+ for (;;) {
+ await sleep(pollIntervalMs)
+ await onProgress?.()
+
+ const pollResponse = await fetch(`${ASSEMBLYAI_TRANSCRIPT_URL}/${transcriptId}`, {
+ headers: authHeaders(apiKey),
+ signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),
+ })
+ if (!pollResponse.ok) return { ok: false, error: DIARIZE_ERROR, status: 502 }
+
+ const transcript = (await pollResponse.json()) as { status?: unknown }
+ if (transcript.status === 'completed') {
+ await deleteRemoteTranscript(apiKey, transcriptId)
+ return { ok: true, turns: normalizeUtterances(transcript) }
+ }
+ if (transcript.status === 'error') {
+ await deleteRemoteTranscript(apiKey, transcriptId)
+ return { ok: false, error: DIARIZE_ERROR, status: 502 }
+ }
+ if (Date.now() > deadline) {
+ await deleteRemoteTranscript(apiKey, transcriptId)
+ return { ok: false, error: DIARIZE_ERROR, status: 504 }
+ }
+ }
+ } catch {
+ if (transcriptId) await deleteRemoteTranscript(apiKey, transcriptId)
+ return { ok: false, error: DIARIZE_ERROR, status: 502 }
+ }
+}
+
+/**
+ * The configured provider, or null when the key is absent. The job treats null
+ * as "no diarization step": the transcript is still saved, without groups.
+ */
+export const configuredSpeakerDiarizer = (): SpeakerDiarizer | null =>
+ process.env.ASSEMBLYAI_API_KEY ? assemblyAiDiarize : null
+
+/** Opens the extracted MP3 as a Blob without loading hours of audio in memory. */
+export const diarizeInputBlob = (path: string): Promise =>
+ openAsBlob(path, { type: 'audio/mpeg' })
diff --git a/src/utilities/recordings/recordingJob.ts b/src/utilities/recordings/recordingJob.ts
index 62439d68..4d2c00ec 100644
--- a/src/utilities/recordings/recordingJob.ts
+++ b/src/utilities/recordings/recordingJob.ts
@@ -10,13 +10,24 @@ import {
RECORDING_MEDIA_SLUG,
type RecordingStep,
} from '@/lib/recording'
+import {
+ assignSpeakerKeys,
+ reconcileSpeakerLabels,
+ RECORDING_DIARIZATION_MAX_SECONDS,
+} from '@/lib/recordingDiarization'
import {
buildRecordingAudioFfmpegArgs,
+ buildRecordingFullAudioFfmpegArgs,
mergeChunkTranscriptions,
RECORDING_AUDIO_CHUNK_SECONDS,
recordingSearchText,
} from '@/lib/recordingTranscription'
import type { Recording } from '@/payload-types'
+import {
+ configuredSpeakerDiarizer,
+ diarizeInputBlob,
+ type SpeakerDiarizer,
+} from '@/utilities/ai/assemblyAiDiarize'
import {
deepInfraTranscribeSegments,
type TranscribeSegmentsResult,
@@ -107,12 +118,16 @@ const chunkFilesIn = async (tempDir: string): Promise => {
/**
* The whole pipeline of one recording. Never throws: an orphan job (server
* restart) is covered by the lazy reaper, and any error marks the row `failed`
- * so the retry reuses it.
+ * so the retry reuses it. C200 adds the optional diarization pass: with a
+ * configured provider the whole extracted audio is sent once, the turns are
+ * aligned to the whisper segments and the human labels are reconciled by time
+ * overlap; a provider failure degrades to the single-block transcript.
*/
export const runRecordingJob = async (
payload: Payload,
recordingId: number,
transcribe: ChunkTranscriber = deepInfraTranscribeSegments,
+ diarize: SpeakerDiarizer | null = configuredSpeakerDiarizer(),
): Promise => {
let tempDir: string | null = null
let currentStep: RecordingStep = 'extracting'
@@ -155,6 +170,22 @@ export const runRecordingJob = async (
return
}
+ // C200: the provider accepts up to 10 h per file; a longer recording skips
+ // the step honestly (a rough upper bound is enough to refuse, and the
+ // chunk count overestimates the real duration by at most one chunk).
+ const diarizationEligible =
+ diarize !== null &&
+ chunkFiles.length * RECORDING_AUDIO_CHUNK_SECONDS <= RECORDING_DIARIZATION_MAX_SECONDS
+ const fullAudioPath = join(tempDir, 'full.mp3')
+ if (diarizationEligible) {
+ await runFfmpeg(
+ buildRecordingFullAudioFfmpegArgs({ inputPath, outputPath: fullAudioPath }),
+ RECORDING_AUDIO_CHUNK_SECONDS,
+ NO_AUDIO_MESSAGE,
+ RECORDING_FFMPEG_TIMEOUT_MS,
+ )
+ }
+
await markStep('transcribing')
const chunks: {
offsetSeconds: number
@@ -187,8 +218,63 @@ export const runRecordingJob = async (
return
}
+ // C200 — optional speaker grouping. The provider seam never fails the
+ // recording: every failure (or a missing key) keeps the plain transcript.
+ let keyedSegments: ((typeof segments)[number] & { speakerKey: string | null })[] = segments.map(
+ (segment) => ({ ...segment, speakerKey: null }),
+ )
+ if (diarizationEligible && diarize) {
+ try {
+ const result = await diarize(await diarizeInputBlob(fullAudioPath), {
+ onProgress: async () => {
+ await updateRecordingSystem(payload, recordingId, { step: 'transcribing' })
+ },
+ })
+ if (result.ok) keyedSegments = assignSpeakerKeys(segments, result.turns)
+ } catch {
+ // Defensive: the provider contract is "never throws"; if it does, the
+ // transcript still gets saved without groups.
+ }
+ }
+
await markStep('saving')
await withPayloadTransaction(payload, async ({ req }) => {
+ // Fresh reads inside the transaction: the labels a curator wrote while
+ // the transcription ran must be the ones reconciled, not the snapshot
+ // from the top of the job.
+ const freshRecording = await payload.find({
+ collection: 'recording',
+ where: { id: { equals: recordingId } },
+ depth: 0,
+ limit: 1,
+ pagination: false,
+ select: { speakerLabels: true },
+ req,
+ // Intentional admin bypass: the pipeline owns the row state.
+ overrideAccess: true,
+ })
+ const previous = await payload.find({
+ collection: 'recordingSegment',
+ where: { recording: { equals: recordingId } },
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ sort: 'order',
+ select: { speakerKey: true, startSeconds: true, endSeconds: true },
+ req,
+ // Intentional admin bypass: reconciling the transcript the job owns.
+ overrideAccess: true,
+ })
+ const { labels, dropped } = reconcileSpeakerLabels({
+ previousLabels: freshRecording.docs[0]?.speakerLabels ?? [],
+ previousSegments: previous.docs.map((segment) => ({
+ speakerKey: segment.speakerKey ?? null,
+ startSeconds: segment.startSeconds,
+ endSeconds: segment.endSeconds,
+ })),
+ nextSegments: keyedSegments,
+ })
+
await payload.delete({
collection: 'recordingSegment',
where: { recording: { equals: recordingId } },
@@ -196,7 +282,7 @@ export const runRecordingJob = async (
// Intentional admin bypass: replacing segments owned by the recording.
overrideAccess: true,
})
- for (const segment of segments) {
+ for (const segment of keyedSegments) {
await payload.create({
collection: 'recordingSegment',
// `searchText` is filled by the collection's own beforeValidate hook.
@@ -206,6 +292,7 @@ export const runRecordingJob = async (
startSeconds: segment.startSeconds,
endSeconds: segment.endSeconds,
text: segment.text,
+ speakerKey: segment.speakerKey,
}),
req,
// Intentional admin bypass: the transcript belongs to the recording row.
@@ -221,6 +308,8 @@ export const runRecordingJob = async (
error: null,
durationSeconds: Math.round(durationSeconds),
searchText: recordingSearchText(segments),
+ speakerLabels: labels,
+ speakerLabelsDropped: dropped,
},
req,
)
diff --git a/src/utilities/recordings/recordingListFilters.ts b/src/utilities/recordings/recordingListFilters.ts
index e280bbf3..2ec8bef0 100644
--- a/src/utilities/recordings/recordingListFilters.ts
+++ b/src/utilities/recordings/recordingListFilters.ts
@@ -1,16 +1,29 @@
/**
* C199 — Payload `where` of the uploaded-recordings list: one textual branch
* over the normalized `recording.searchText` (the concatenation of the
- * transcript segments). No facets: the Câmara facets do not apply to a
- * recording. A recording still processing has no `searchText` and is honestly
- * out of the search until the transcript is saved.
+ * transcript segments). C200 adds the "Pessoa" facet: any selected label that
+ * appears in the recording's derived `speakerNames`. A recording still
+ * processing has no `searchText` and is honestly out of the search until the
+ * transcript is saved.
*/
import type { Where } from 'payload'
import { normalizeForSearch } from '@/lib/speechSearch'
+import { collapseListWhereOrBranches } from '@/utilities/campaignListUrl'
import type { RecordingListState } from '@/utilities/recordings/recordingListUrl'
export const buildRecordingListWhere = (state: RecordingListState): Where => {
+ const branches: Where[] = []
+
const normalized = normalizeForSearch(state.q ?? '')
- return normalized ? { searchText: { like: normalized } } : {}
+ if (normalized) branches.push({ searchText: { like: normalized } })
+
+ const peopleBranch = collapseListWhereOrBranches(
+ (state.people ?? []).map((person) => ({ speakerNames: { contains: person } })),
+ )
+ if (peopleBranch) branches.push(peopleBranch)
+
+ if (branches.length === 0) return {}
+ if (branches.length === 1) return branches[0] ?? {}
+ return { and: branches }
}
diff --git a/src/utilities/recordings/recordingListUrl.ts b/src/utilities/recordings/recordingListUrl.ts
index 31816486..bd1cc2b7 100644
--- a/src/utilities/recordings/recordingListUrl.ts
+++ b/src/utilities/recordings/recordingListUrl.ts
@@ -6,7 +6,10 @@
*/
import { CAMPAIGN_COMMUNICATION_ACERVO } from '@/lib/campaignPaths'
import { isContactSearchQueryReady } from '@/lib/contactSearchQuery'
+import { RECORDING_SPEAKER_LABEL_MAX_LENGTH } from '@/lib/recording'
+import { foldSpeakerName } from '@/lib/recordingDiarization'
import {
+ allParamValues,
buildListHref,
firstValue,
normalizedText,
@@ -22,29 +25,52 @@ export type AcervoSource = 'camara' | 'enviadas'
const ACERVO_SOURCE_PARAM = 'source'
const ACERVO_SOURCE_ENVIADAS = 'enviadas'
+const ACERVO_PERSON_PARAM = 'person'
-const recordingListParamNames = [ACERVO_SOURCE_PARAM, 'q', 'page'] as const
+const recordingListParamNames = [ACERVO_SOURCE_PARAM, 'q', 'page', ACERVO_PERSON_PARAM] as const
const recordingListParamNameSet = new Set(recordingListParamNames)
export type RecordingListState = {
source: 'enviadas'
page: number
q?: string
+ /** Selected "Pessoa" labels; repeated `person` params, literal text. */
+ people?: string[]
}
/** `enviadas` only for the explicit value; anything else is the Câmara default. */
export const parseAcervoSource = (params: RawSearchParams): AcervoSource =>
firstValue(params[ACERVO_SOURCE_PARAM]) === ACERVO_SOURCE_ENVIADAS ? 'enviadas' : 'camara'
+/**
+ * Parses the repeated `person` filter: trims, drops empty and oversized values
+ * (a label never exceeds the field limit, so an oversized token can only be a
+ * hand-typed miss) and dedupes case-insensitively keeping the first spelling.
+ */
+export const parseRecordingPeople = (raw: string | string[] | undefined): string[] => {
+ const people: string[] = []
+ const seen = new Set()
+ for (const value of allParamValues(raw)) {
+ if (value.length > RECORDING_SPEAKER_LABEL_MAX_LENGTH) continue
+ const folded = foldSpeakerName(value)
+ if (seen.has(folded)) continue
+ seen.add(folded)
+ people.push(value)
+ }
+ return people
+}
+
export const parseRecordingListParams = (params: RawSearchParams): RecordingListState => {
const rawPage = strictDecimalInteger(firstValue(params.page))
const rawQ = normalizedText(firstValue(params.q))
const q = rawQ && isContactSearchQueryReady(rawQ) ? rawQ : undefined
+ const people = parseRecordingPeople(params[ACERVO_PERSON_PARAM])
return {
source: 'enviadas',
page: rawPage ?? 1,
...(q ? { q } : {}),
+ ...(people.length > 0 ? { people } : {}),
}
}
@@ -56,10 +82,35 @@ const recordingListSearchParams = (
// `source=enviadas` is always serialized — it is what selects the source.
params.set(ACERVO_SOURCE_PARAM, ACERVO_SOURCE_ENVIADAS)
if (state.q) params.set('q', state.q)
+ for (const person of state.people ?? []) params.append(ACERVO_PERSON_PARAM, person)
if (page > 1) params.set('page', String(page))
return params
}
+/** The URL of one "Pessoa" facet option with `person` toggled (page resets). */
+export const toggleRecordingPerson = (
+ state: RecordingListState,
+ value: string,
+): RecordingListState => {
+ const current = state.people ?? []
+ const folded = foldSpeakerName(value)
+ const selected = current.some((person) => foldSpeakerName(person) === folded)
+ const people = selected
+ ? current.filter((person) => foldSpeakerName(person) !== folded)
+ : [...current, value]
+ return { ...state, page: 1, ...(people.length > 0 ? { people } : { people: undefined }) }
+}
+
+/** The URL that removes one selected "Pessoa" (chips with an X; page resets). */
+export const removeRecordingPerson = (
+ state: RecordingListState,
+ value: string,
+): RecordingListState => {
+ const folded = foldSpeakerName(value)
+ const people = (state.people ?? []).filter((person) => foldSpeakerName(person) !== folded)
+ return { ...state, page: 1, ...(people.length > 0 ? { people } : { people: undefined }) }
+}
+
export const buildRecordingListHref = (state: RecordingListState, page: number): string =>
buildListHref(state, recordingListSearchParams, CAMPAIGN_COMMUNICATION_ACERVO, page)
diff --git a/src/utilities/recordings/recordingPageData.ts b/src/utilities/recordings/recordingPageData.ts
index d8cde2f3..e113cbfc 100644
--- a/src/utilities/recordings/recordingPageData.ts
+++ b/src/utilities/recordings/recordingPageData.ts
@@ -2,6 +2,7 @@ import 'server-only'
import type { Payload } from 'payload'
+import { foldSpeakerName } from '@/lib/recordingDiarization'
import { RECORDING_NOT_FOUND_MESSAGE } from '@/lib/schemas/recording'
import { normalizeForSearch } from '@/lib/speechSearch'
import type { CampaignUser } from '@/payload-types'
@@ -33,29 +34,76 @@ const recordingListSelect = {
step: true,
recordedAt: true,
durationSeconds: true,
+ speakerNames: true,
} as const
const recordingDetailSelect = {
...recordingListSelect,
error: true,
+ speakerLabels: true,
+ speakerLabelsDropped: true,
} as const
const segmentSelect = {
startSeconds: true,
endSeconds: true,
text: true,
+ speakerKey: true,
} as const
const toSegmentRecord = (segment: {
startSeconds: number
endSeconds: number
text: string
+ speakerKey?: string | null
}): RecordingSegmentRecord => ({
startSeconds: segment.startSeconds,
endSeconds: segment.endSeconds,
text: segment.text,
+ speakerKey: segment.speakerKey ?? null,
})
+/** C200 — the options the "Pessoa" facet offers. */
+export type RecordingFilterOptions = {
+ people: string[]
+}
+
+/**
+ * C200 — distinct labels across the recordings the actor can read: the facet
+ * only ever lists what is reachable, and the derived `speakerNames` column is
+ * the single source. Small acervo now; if the list grows past a few thousand
+ * rows this becomes a `SELECT DISTINCT` (registered as a revisit trigger in the
+ * impl plan).
+ */
+export const loadRecordingFilterOptions = async (
+ payload: Payload,
+ user: CampaignUser,
+): Promise => {
+ const result = await payload.find({
+ collection: 'recording',
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ select: { speakerNames: true },
+ user,
+ overrideAccess: false,
+ })
+
+ const people: string[] = []
+ const seen = new Set()
+ for (const recording of result.docs) {
+ for (const name of recording.speakerNames ?? []) {
+ const trimmed = name.trim()
+ const folded = foldSpeakerName(trimmed)
+ if (!trimmed || seen.has(folded)) continue
+ seen.add(folded)
+ people.push(trimmed)
+ }
+ }
+
+ return { people: people.sort((left, right) => left.localeCompare(right, 'pt-BR')) }
+}
+
/**
* The excerpt segment of each listed recording: ONE matching segment per row
* (the search runs `LIKE` on the joined `recording.searchText`, so a page of
@@ -120,6 +168,7 @@ const loadMatchedSegments = async (
export type RecordingsPageData = {
rows: RecordingListItemViewModel[]
state: RecordingListState
+ filterOptions: RecordingFilterOptions
redirectHref?: string
totalDocs: number
totalPages: number
@@ -134,17 +183,20 @@ export const loadRecordingsPageData = async (
const canonicalUrl = resolveRecordingListUrl(rawSearchParams)
const state = canonicalUrl.state
- const result = await payload.find({
- collection: 'recording',
- depth: 0,
- limit: recordingPageSize,
- page: state.page,
- sort: '-createdAt',
- where: buildRecordingListWhere(state),
- select: recordingListSelect,
- user,
- overrideAccess: false,
- })
+ const [result, filterOptions] = await Promise.all([
+ payload.find({
+ collection: 'recording',
+ depth: 0,
+ limit: recordingPageSize,
+ page: state.page,
+ sort: '-createdAt',
+ where: buildRecordingListWhere(state),
+ select: recordingListSelect,
+ user,
+ overrideAccess: false,
+ }),
+ loadRecordingFilterOptions(payload, user),
+ ])
const resolvedUrl = resolveRecordingListUrl(rawSearchParams, result.totalPages)
const recordings = result.docs
@@ -163,9 +215,11 @@ export const loadRecordingsPageData = async (
recording,
matchedSegments: segmentsByRecording.get(recording.id) ?? [],
query: state.q,
+ people: state.people,
}),
),
state: resolvedUrl.state,
+ filterOptions,
redirectHref: resolvedUrl.redirectHref ?? canonicalUrl.redirectHref,
totalDocs: result.totalDocs,
totalPages: result.totalPages,
diff --git a/src/utilities/recordings/recordingViewModels.ts b/src/utilities/recordings/recordingViewModels.ts
index 563db30c..2489c6b2 100644
--- a/src/utilities/recordings/recordingViewModels.ts
+++ b/src/utilities/recordings/recordingViewModels.ts
@@ -1,13 +1,15 @@
/**
* C199 — view models of the uploaded recordings. Pure: the loader hands raw
- * Payload rows and the view model decides labels, the matching excerpt and the
- * links the list/detail render.
+ * Payload rows and the view model decides labels, the matching excerpt, the
+ * speaker groups and the links the list/detail render. C200 adds the anonymous
+ * cluster grouping ("Falante N") and the human labels the team typed.
*/
import {
recordingFailureMessage,
toRecordingViewModel,
type RecordingViewModel,
} from '@/lib/recording'
+import { recordingSpeakerDefaultLabel, speakerNameMatches } from '@/lib/recordingDiarization'
import { formatSpeechClock } from '@/lib/speechClock'
import {
buildHighlightedExcerpt,
@@ -22,6 +24,8 @@ export type RecordingSegmentRecord = {
startSeconds: number
endSeconds: number
text: string
+ /** C200 — anonymous cluster key; null/absent on recordings without diarization. */
+ speakerKey?: string | null
}
type RecordingBaseRecord = {
@@ -33,15 +37,24 @@ type RecordingBaseRecord = {
durationSeconds?: number | null
}
-export type RecordingListRecord = RecordingBaseRecord
+export type RecordingListRecord = RecordingBaseRecord & {
+ /** C200 — derived distinct labels; drives the card chip under a "Pessoa" filter. */
+ speakerNames?: string[] | null
+}
export type RecordingDetailRecord = RecordingBaseRecord & {
/** Raw internal cause of a failure; never handed to the client as-is. */
error?: string | null
+ /** C200 — human labels by cluster key. */
+ speakerLabels?: { speakerKey: string; label: string }[] | null
+ /** C200 — true when a reprocessing could not keep some label. */
+ speakerLabelsDropped?: boolean | null
}
export type RecordingListItemViewModel = RecordingViewModel & {
excerpt: SpeechHighlightedExcerpt | null
+ /** Selected "Pessoa" labels that this recording indeed contains. */
+ matchedPersons: string[]
/**
* Detail link that seeks the player to the matching segment when the search
* found one (`?t=`), carrying the term for transcript highlighting (`?q=`).
@@ -56,10 +69,74 @@ export type RecordingDetailSegmentViewModel = {
parts: SpeechHighlightPart[]
}
+/**
+ * C200 — one anonymous speaker cluster of the detail transcript: the keyed
+ * segments in order, the numbered default label and the human label when the
+ * team identified the group.
+ */
+export type RecordingSpeakerGroupViewModel = {
+ key: string
+ index: number
+ defaultLabel: string
+ label: string | null
+ isIdentified: boolean
+ segments: RecordingDetailSegmentViewModel[]
+}
+
export type RecordingDetailViewModel = RecordingViewModel & {
/** Honest cause of a failure; null unless `status === 'failed'`. */
failureMessage: string | null
segments: RecordingDetailSegmentViewModel[]
+ /** Empty when the recording has no (or a partial) speaker grouping. */
+ speakerGroups: RecordingSpeakerGroupViewModel[]
+ /** C200 — surfaced by the imprecision banner when a label was dropped. */
+ speakerLabelsDropped: boolean
+}
+
+const toDetailSegment = (
+ segment: RecordingSegmentRecord,
+ query?: string,
+): RecordingDetailSegmentViewModel => ({
+ startSeconds: segment.startSeconds,
+ endSeconds: segment.endSeconds,
+ startLabel: formatSpeechClock(segment.startSeconds),
+ parts: splitHighlightedParts(segment.text, query ?? ''),
+})
+
+/**
+ * Groups the transcript by cluster key. A recording only renders groups when
+ * EVERY segment is keyed: a half-diarized transcript would put an invented
+ * boundary next to a real one, so the partial case falls back to the plain
+ * list. Labels are looked up by key; an unlabeled group keeps "Falante N".
+ */
+const buildSpeakerGroups = (
+ segments: readonly RecordingSegmentRecord[],
+ labelsByKey: ReadonlyMap,
+ query?: string,
+): RecordingSpeakerGroupViewModel[] => {
+ if (segments.length === 0 || segments.some((segment) => !segment.speakerKey)) return []
+
+ const groups = new Map()
+ for (const segment of segments) {
+ const key = segment.speakerKey
+ if (!key) continue
+ const group = groups.get(key)
+ const view = toDetailSegment(segment, query)
+ if (group) group.push(view)
+ else groups.set(key, [view])
+ }
+
+ return [...groups.entries()].map(([key, groupSegments], index) => {
+ const label = labelsByKey.get(key)?.trim() || null
+ return {
+ key,
+ index,
+ defaultLabel: recordingSpeakerDefaultLabel(index + 1),
+ label,
+ isIdentified: label !== null,
+ segments: groupSegments,
+ }
+ })
}
const withSeek = (href: string, segment: RecordingSegmentRecord | undefined, query?: string) => {
@@ -80,28 +157,36 @@ export const toRecordingListItemViewModel = ({
recording,
matchedSegments,
query,
+ people,
}: {
recording: RecordingListRecord
/** Segments that matched the query (or the row's first one as fallback). */
matchedSegments: readonly RecordingSegmentRecord[]
query?: string
+ /** C200 — the selected "Pessoa" labels of the list state. */
+ people?: readonly string[]
}): RecordingListItemViewModel => {
const base = toRecordingViewModel(recording)
const isReady = base.status === 'ready'
const q = query?.trim()
const segment = isReady && q ? pickMatchingSegment(matchedSegments, q) : undefined
const excerpt = segment ? buildHighlightedExcerpt(segment.text, q ?? '') : null
+ const speakerNames = recording.speakerNames ?? []
return {
...base,
excerpt,
+ matchedPersons: (people ?? []).filter((person) =>
+ speakerNames.some((name) => speakerNameMatches(name, person)),
+ ),
watchHref: withSeek(base.detailHref, segment, q),
}
}
/**
- * Detail view of one recording: the base view plus the failure message and the
- * clickable transcript segments (highlighted by the `?q=` of the search).
+ * Detail view of one recording: the base view plus the failure message, the
+ * clickable transcript segments (highlighted by the `?q=` of the search) and
+ * the speaker groups when the whole transcript is keyed.
*/
export const toRecordingDetailViewModel = ({
recording,
@@ -114,6 +199,9 @@ export const toRecordingDetailViewModel = ({
}): RecordingDetailViewModel => {
const base = toRecordingViewModel(recording)
const isFailed = base.status === 'failed'
+ const labelsByKey = new Map(
+ (recording.speakerLabels ?? []).map((entry) => [entry.speakerKey, entry.label]),
+ )
return {
...base,
@@ -123,11 +211,8 @@ export const toRecordingDetailViewModel = ({
step: base.step,
})
: null,
- segments: segments.map((segment) => ({
- startSeconds: segment.startSeconds,
- endSeconds: segment.endSeconds,
- startLabel: formatSpeechClock(segment.startSeconds),
- parts: splitHighlightedParts(segment.text, query ?? ''),
- })),
+ segments: segments.map((segment) => toDetailSegment(segment, query)),
+ speakerGroups: buildSpeakerGroups(segments, labelsByKey, query),
+ speakerLabelsDropped: recording.speakerLabelsDropped === true,
}
}
diff --git a/tests/e2e/campaignSpeechAcervo.e2e.spec.ts b/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
index 172fa7d9..5387da2c 100644
--- a/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
+++ b/tests/e2e/campaignSpeechAcervo.e2e.spec.ts
@@ -521,7 +521,18 @@ test.describe('communication vertical (C154/C162)', () => {
test.describe('uploaded recordings (C199)', () => {
const createRecording = async (
campaign: { fixtures: CampaignE2EOwnership },
- input: { marker: string; status?: 'processing' | 'ready' | 'failed'; withSegments?: boolean },
+ input: {
+ marker: string
+ status?: 'processing' | 'ready' | 'failed'
+ withSegments?: boolean
+ segments?: {
+ startSeconds: number
+ endSeconds: number
+ text: string
+ speakerKey?: string | null
+ }[]
+ speakerLabels?: { speakerKey: string; label: string }[]
+ },
) => {
const bytes = Buffer.from(`recording-${input.marker}`)
const media = await campaign.fixtures.payload.create({
@@ -541,11 +552,35 @@ test.describe('communication vertical (C154/C162)', () => {
status: input.status ?? 'ready',
recordedAt: '2026-09-01T00:00:00.000Z',
media: media.id,
+ ...(input.speakerLabels ? { speakerLabels: input.speakerLabels } : {}),
},
depth: 0,
})
- if (input.withSegments ?? true) {
+ if (input.segments) {
+ for (const [index, segment] of input.segments.entries()) {
+ await campaign.fixtures.payload.create({
+ collection: 'recordingSegment',
+ data: hookFilledCreateData<'recordingSegment'>({
+ recording: recording.id,
+ order: index + 1,
+ startSeconds: segment.startSeconds,
+ endSeconds: segment.endSeconds,
+ text: segment.text,
+ speakerKey: segment.speakerKey ?? null,
+ }),
+ depth: 0,
+ })
+ }
+ await campaign.fixtures.payload.update({
+ collection: 'recording',
+ id: recording.id,
+ data: {
+ searchText: normalizeForSearch(input.segments.map((segment) => segment.text).join(' ')),
+ },
+ depth: 0,
+ })
+ } else if (input.withSegments ?? true) {
await campaign.fixtures.payload.create({
collection: 'recordingSegment',
data: hookFilledCreateData<'recordingSegment'>({
@@ -685,5 +720,155 @@ test.describe('communication vertical (C154/C162)', () => {
const denied = await advisorRequest.post(uploadUrl)
expect(denied.status()).toBe(403)
})
+
+ test('the grouped detail renders the speaker blocks, the warning and the identify action', async ({
+ campaign,
+ campaignRequest,
+ }) => {
+ const marker = campaign.fixtures.value('gravacaofalantes')
+ const { recording } = await createRecording(campaign, {
+ marker,
+ segments: [
+ {
+ startSeconds: 12,
+ endSeconds: 20,
+ text: `A plenária discutiu a merenda ${marker}.`,
+ speakerKey: 'speaker-1',
+ },
+ {
+ startSeconds: 21,
+ endSeconds: 30,
+ text: 'O relator respondeu em seguida.',
+ speakerKey: 'speaker-2',
+ },
+ ],
+ speakerLabels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }],
+ })
+
+ const user = await campaign.fixtures.createCampaignUser('communicator')
+ const request = await campaignRequest(user, user.password)
+ const response = await request.get(
+ `/campanha/comunicacao/acervo/gravacoes/${recording.id}?t=12&q=${marker}`,
+ )
+ expect(response.status()).toBe(200)
+ const html = rendered(await response.text())
+
+ expect(html).toContain('Transcrição por falante')
+ expect(html).toContain('Dep. Jorge Solla')
+ expect(html).toContain('Identificação feita pela equipe')
+ expect(html).toContain('Falante 2')
+ expect(html).toContain('Agrupamento sem identificação')
+ expect(html).toContain('Editar identificação')
+ expect(html).toContain('Identificar falante')
+ expect(html).toContain('A separação por falante pode estar imprecisa')
+ expect(html).toContain('data-start-seconds="12"')
+ expect(html).toContain('data-start-seconds="21"')
+ expect(html).toContain(' {
+ const marker = campaign.fixtures.value('gravacaopessoa')
+ const { recording } = await createRecording(campaign, {
+ marker,
+ segments: [
+ {
+ startSeconds: 12,
+ endSeconds: 20,
+ text: `A plenária discutiu a merenda ${marker}.`,
+ speakerKey: 'speaker-1',
+ },
+ ],
+ speakerLabels: [{ speakerKey: 'speaker-1', label: `Dep. ${marker}` }],
+ })
+ const { recording: other } = await createRecording(campaign, {
+ marker: `${marker}outra`,
+ withSegments: false,
+ })
+
+ const user = await campaign.fixtures.createCampaignUser('communicator')
+ const request = await campaignRequest(user, user.password)
+ const response = await request.get(
+ `/campanha/comunicacao/acervo?source=enviadas&person=${encodeURIComponent(`dep. ${marker}`)}`,
+ )
+ expect(response.status()).toBe(200)
+ const html = rendered(await response.text())
+
+ expect(html).toContain(`Gravação ${marker}`)
+ expect(html).not.toContain(`Gravação ${marker}outra`)
+ expect(html).toContain('Pessoa:')
+ expect(html).toContain('aparece nesta gravação')
+ expect(html).toContain(`/campanha/comunicacao/acervo/gravacoes/${recording.id}`)
+ expect(html).not.toContain(`/campanha/comunicacao/acervo/gravacoes/${other.id}`)
+ })
+
+ test('labels a cluster through the JSON route and denies the non-acervo roles', async ({
+ campaign,
+ campaignRequest,
+ }) => {
+ const marker = campaign.fixtures.value('gravacaorotular')
+ const { recording } = await createRecording(campaign, {
+ marker,
+ segments: [
+ {
+ startSeconds: 12,
+ endSeconds: 20,
+ text: 'Fala única do agrupamento.',
+ speakerKey: 'speaker-1',
+ },
+ ],
+ })
+ const endpoint = `/campanha/comunicacao/acervo/gravacoes/${recording.id}/falantes`
+
+ const user = await campaign.fixtures.createCampaignUser('communicator')
+ const request = await campaignRequest(user, user.password)
+ const response = await request.post(endpoint, {
+ data: { recordingId: recording.id, speakerKey: 'speaker-1', label: `Dep. ${marker}` },
+ })
+ expect(response.status()).toBe(200)
+ expect((await response.json()).status).toBe('success')
+
+ const updated = await campaign.fixtures.payload.findByID({
+ collection: 'recording',
+ id: recording.id,
+ depth: 0,
+ })
+ expect(updated.speakerNames).toEqual([`Dep. ${marker}`])
+
+ const unknown = await request.post(endpoint, {
+ data: { recordingId: recording.id, speakerKey: 'speaker-9', label: 'Fantasma' },
+ })
+ expect(unknown.status()).toBe(400)
+
+ const advisor = await campaign.fixtures.createCampaignUser('advisor')
+ const advisorRequest = await campaignRequest(advisor, advisor.password)
+ const denied = await advisorRequest.post(endpoint, {
+ data: { recordingId: recording.id, speakerKey: 'speaker-1', label: 'Negado' },
+ })
+ // The JSON mutation wrapper maps a safe domain message to 400 (same
+ // envelope as the other `/campanha/**` mutation routes).
+ expect(denied.status()).toBe(400)
+ })
+
+ test('a recording without diarization keeps the plain transcript', async ({
+ campaign,
+ campaignRequest,
+ }) => {
+ const marker = campaign.fixtures.value('gravacaoSemFalantes')
+ const { recording } = await createRecording(campaign, { marker })
+
+ const user = await campaign.fixtures.createCampaignUser('communicator')
+ const request = await campaignRequest(user, user.password)
+ const response = await request.get(
+ `/campanha/comunicacao/acervo/gravacoes/${recording.id}?t=12&q=${marker}`,
+ )
+ const html = rendered(await response.text())
+
+ expect(html).toContain('data-start-seconds="12"')
+ expect(html).not.toContain('Transcrição por falante')
+ expect(html).not.toContain('A separação por falante pode estar imprecisa')
+ })
})
})
diff --git a/tests/int/recording.int.spec.ts b/tests/int/recording.int.spec.ts
index 6c9d3ddc..2ecf8b22 100644
--- a/tests/int/recording.int.spec.ts
+++ b/tests/int/recording.int.spec.ts
@@ -29,9 +29,14 @@ import { GET } from '@/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoe
import {
deleteRecordingForActor,
getRecordingStatusesForActor,
+ labelRecordingSpeakerForActor,
retryRecordingForActor,
} from '@/app/(campaign)/campanha/actions/recording'
import { RECORDING_MAX_BYTES } from '@/lib/recording'
+import {
+ RECORDING_FORBIDDEN_MESSAGE,
+ RECORDING_SPEAKER_UNKNOWN_MESSAGE,
+} from '@/lib/schemas/recording'
import { normalizeForSearch } from '@/lib/speechSearch'
import type { CampaignUser, RecordingMedia } from '@/payload-types'
import config from '@/payload.config'
@@ -41,7 +46,10 @@ import {
RECORDING_STALE_MS,
runRecordingJob,
} from '@/utilities/recordings/recordingJob'
-import { loadRecordingsPageData } from '@/utilities/recordings/recordingPageData'
+import {
+ loadRecordingFilterOptions,
+ loadRecordingsPageData,
+} from '@/utilities/recordings/recordingPageData'
import { receiveRecordingUpload } from '@/utilities/recordings/recordingUpload'
import { installCampaignFixtures } from '../helpers/campaignFixtures'
@@ -90,11 +98,18 @@ const createRecording = async ({
status = 'ready',
title = 'Plenária da Comissão',
segments = [],
+ speakerLabels,
withMedia = true,
}: {
status?: 'uploading' | 'processing' | 'ready' | 'failed'
title?: string
- segments?: { startSeconds: number; endSeconds: number; text: string }[]
+ segments?: {
+ startSeconds: number
+ endSeconds: number
+ text: string
+ speakerKey?: string | null
+ }[]
+ speakerLabels?: { speakerKey: string; label: string }[]
withMedia?: boolean
} = {}) => {
const media = withMedia ? await createMedia(`${title}.mp4`) : null
@@ -104,6 +119,7 @@ const createRecording = async ({
title,
status,
...(media ? { media: media.id } : {}),
+ ...(speakerLabels ? { speakerLabels } : {}),
},
overrideAccess: true,
})
@@ -118,6 +134,7 @@ const createRecording = async ({
startSeconds: segment.startSeconds,
endSeconds: segment.endSeconds,
text: segment.text,
+ speakerKey: segment.speakerKey ?? null,
}),
overrideAccess: true,
})
@@ -555,4 +572,197 @@ describe('uploaded recordings (C199)', () => {
expect(unfiltered.rows.length).toBeGreaterThanOrEqual(2)
expect(unfiltered.rows.every((row) => row.excerpt === null)).toBe(true)
})
+
+ it('filters recordings by a human label and lists the facet options', async () => {
+ const marker = `solla${Date.now().toString(36)}`
+ const { recording } = await createRecording({
+ title: `Plenária com vozes ${marker}`,
+ segments: [
+ { startSeconds: 0, endSeconds: 5, text: 'Primeira fala.', speakerKey: 'speaker-1' },
+ { startSeconds: 5, endSeconds: 10, text: 'Segunda fala.', speakerKey: 'speaker-2' },
+ ],
+ speakerLabels: [{ speakerKey: 'speaker-1', label: `Dep. ${marker}` }],
+ })
+ const { recording: other } = await createRecording({
+ title: `Sem falantes ${marker}`,
+ segments: [],
+ })
+
+ // The facet matches the same way the dialog chip does: case-insensitive
+ // containment over the derived names, against the real join table.
+ const filtered = await loadRecordingsPageData(payload, communicator, {
+ source: 'enviadas',
+ person: [`dep. ${marker}`],
+ })
+ expect(filtered.rows.map((row) => row.id)).toContain(recording.id)
+ expect(filtered.rows.map((row) => row.id)).not.toContain(other.id)
+ expect(filtered.rows.find((row) => row.id === recording.id)?.matchedPersons).toEqual([
+ `dep. ${marker}`,
+ ])
+
+ const options = await loadRecordingFilterOptions(payload, communicator)
+ expect(options.people).toContain(`Dep. ${marker}`)
+ })
+
+ it('labels one cluster through the action and rebuilds the facet names', async () => {
+ const marker = `falante${Date.now().toString(36)}`
+ const { recording } = await createRecording({
+ title: `Debate ${marker}`,
+ segments: [{ startSeconds: 0, endSeconds: 5, text: 'Fala única.', speakerKey: 'speaker-1' }],
+ })
+
+ getCampaignUserMock.mockResolvedValue(communicator)
+ await labelRecordingSpeakerForActor({
+ recordingId: recording.id,
+ speakerKey: 'speaker-1',
+ label: `Dep. ${marker}`,
+ })
+
+ const updated = await payload.findByID({
+ collection: 'recording',
+ id: recording.id,
+ depth: 0,
+ overrideAccess: true,
+ })
+ expect(updated.speakerLabels).toEqual([
+ expect.objectContaining({ speakerKey: 'speaker-1', label: `Dep. ${marker}` }),
+ ])
+ expect(updated.speakerNames).toEqual([`Dep. ${marker}`])
+
+ for (const denied of [advisor, leader]) {
+ getCampaignUserMock.mockResolvedValue(denied)
+ await expect(
+ labelRecordingSpeakerForActor({
+ recordingId: recording.id,
+ speakerKey: 'speaker-1',
+ label: 'Quem quer que seja',
+ }),
+ ).rejects.toThrow(RECORDING_FORBIDDEN_MESSAGE)
+ }
+
+ getCampaignUserMock.mockResolvedValue(communicator)
+ await expect(
+ labelRecordingSpeakerForActor({
+ recordingId: recording.id,
+ speakerKey: 'speaker-9',
+ label: 'Agrupamento fantasma',
+ }),
+ ).rejects.toThrow(RECORDING_SPEAKER_UNKNOWN_MESSAGE)
+ })
+
+ it('groups the transcript with the injected diarizer and keeps a reconcilable label', async () => {
+ const { recording } = await createRecording({
+ status: 'processing',
+ title: 'Plenária com falantes',
+ segments: [
+ { startSeconds: 1, endSeconds: 4, text: 'Fala anterior A', speakerKey: 'speaker-1' },
+ { startSeconds: 5, endSeconds: 9, text: 'Fala anterior B', speakerKey: 'speaker-2' },
+ ],
+ speakerLabels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }],
+ })
+
+ const restore = withEnv('FFMPEG_PATH', FAKE_FFMPEG)
+ try {
+ await runRecordingJob(
+ payload,
+ recording.id,
+ async () => ({
+ ok: true,
+ segments: [
+ { start: 1, end: 4, text: 'Bom dia a todas' },
+ { start: 5, end: 9, text: 'e a todos' },
+ ],
+ durationSeconds: 1_200,
+ }),
+ async (file, options) => {
+ expect(file).toBeInstanceOf(Blob)
+ await options?.onProgress?.()
+ return {
+ ok: true,
+ turns: [
+ { speaker: 'A', startSeconds: 0, endSeconds: 4.5 },
+ { speaker: 'B', startSeconds: 4.5, endSeconds: 10 },
+ ],
+ }
+ },
+ )
+ } finally {
+ restore()
+ }
+
+ const updated = await payload.findByID({
+ collection: 'recording',
+ id: recording.id,
+ depth: 0,
+ overrideAccess: true,
+ })
+ expect(updated.status).toBe('ready')
+ expect(updated.speakerLabels).toEqual([
+ expect.objectContaining({ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }),
+ ])
+ expect(updated.speakerLabelsDropped).toBe(false)
+
+ const segments = await payload.find({
+ collection: 'recordingSegment',
+ where: { recording: { equals: recording.id } },
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ sort: 'order',
+ overrideAccess: true,
+ })
+ expect(segments.docs).toHaveLength(4)
+ expect(segments.docs.map((segment) => segment.speakerKey)).toEqual([
+ 'speaker-1',
+ 'speaker-2',
+ 'speaker-2',
+ 'speaker-2',
+ ])
+ })
+
+ it('keeps the plain transcript and warns when the diarizer fails with labels present', async () => {
+ const { recording } = await createRecording({
+ status: 'processing',
+ title: 'Debate sem provedor',
+ segments: [{ startSeconds: 1, endSeconds: 4, text: 'Fala', speakerKey: 'speaker-1' }],
+ speakerLabels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }],
+ })
+
+ const restore = withEnv('FFMPEG_PATH', FAKE_FFMPEG)
+ try {
+ await runRecordingJob(
+ payload,
+ recording.id,
+ async () => ({
+ ok: true,
+ segments: [{ start: 1, end: 4, text: 'Fala' }],
+ durationSeconds: 600,
+ }),
+ async () => ({ ok: false, error: 'provider down', status: 502 }),
+ )
+ } finally {
+ restore()
+ }
+
+ const updated = await payload.findByID({
+ collection: 'recording',
+ id: recording.id,
+ depth: 0,
+ overrideAccess: true,
+ })
+ expect(updated.status).toBe('ready')
+ expect(updated.speakerLabels ?? []).toEqual([])
+ expect(updated.speakerLabelsDropped).toBe(true)
+
+ const segments = await payload.find({
+ collection: 'recordingSegment',
+ where: { recording: { equals: recording.id } },
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ sort: 'order',
+ overrideAccess: true,
+ })
+ expect(segments.docs.every((segment) => !segment.speakerKey)).toBe(true)
+ })
})
diff --git a/tests/unit/assemblyAiDiarize.unit.spec.ts b/tests/unit/assemblyAiDiarize.unit.spec.ts
new file mode 100644
index 00000000..5afbeb79
--- /dev/null
+++ b/tests/unit/assemblyAiDiarize.unit.spec.ts
@@ -0,0 +1,134 @@
+// @vitest-environment node
+
+import { afterEach, describe, expect, it, vi } from 'vitest'
+
+import {
+ ASSEMBLYAI_TRANSCRIPT_URL,
+ ASSEMBLYAI_UPLOAD_URL,
+ assemblyAiDiarize,
+} from '@/utilities/ai/assemblyAiDiarize'
+
+type FetchCall = { url: string; init: RequestInit | undefined }
+
+const jsonResponse = (body: unknown, status = 200): Response =>
+ new Response(JSON.stringify(body), { status })
+
+const audioBlob = (): Blob => new Blob([new Uint8Array([1, 2, 3])], { type: 'audio/mpeg' })
+
+const installFetch = (
+ handler: (url: string, init: RequestInit | undefined, call: number) => Response,
+): FetchCall[] => {
+ const calls: FetchCall[] = []
+ vi.stubGlobal(
+ 'fetch',
+ vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = String(input)
+ calls.push({ url, init })
+ return handler(url, init, calls.length)
+ }),
+ )
+ return calls
+}
+
+afterEach(() => {
+ vi.unstubAllGlobals()
+ vi.unstubAllEnvs()
+})
+
+describe('AssemblyAI diarization provider (C200)', () => {
+ it('uploads, submits with speaker labels and normalizes ms to seconds', async () => {
+ vi.stubEnv('ASSEMBLYAI_API_KEY', 'test-key')
+ const calls = installFetch((url, init) => {
+ if (url === ASSEMBLYAI_UPLOAD_URL)
+ return jsonResponse({ upload_url: 'https://cdn/audio.mp3' })
+ if (url === ASSEMBLYAI_TRANSCRIPT_URL && init?.method === 'POST') {
+ return jsonResponse({ id: 'tr_1', status: 'queued' })
+ }
+ return jsonResponse({
+ status: 'completed',
+ utterances: [
+ { speaker: 'A', start: 1000, end: 2500 },
+ { speaker: 'B', start: 2500, end: 4000 },
+ { speaker: 'C', start: 'x', end: 10 },
+ ],
+ })
+ })
+
+ const progress = vi.fn()
+ const result = await assemblyAiDiarize(audioBlob(), {
+ pollIntervalMs: 1,
+ onProgress: progress,
+ })
+
+ expect(result).toEqual({
+ ok: true,
+ turns: [
+ { speaker: 'A', startSeconds: 1, endSeconds: 2.5 },
+ { speaker: 'B', startSeconds: 2.5, endSeconds: 4 },
+ ],
+ })
+ const create = calls.find(
+ (call) => call.url === ASSEMBLYAI_TRANSCRIPT_URL && call.init?.method === 'POST',
+ )
+ expect(create?.init?.body).toContain('"speaker_labels":true')
+ expect(create?.init?.body).toContain('"speech_models":["universal-2"]')
+ expect(create?.init?.body).toContain('"language_code":"pt"')
+ expect(new Headers(calls[0]?.init?.headers).get('authorization')).toBe('test-key')
+ expect(calls.some((call) => call.init?.method === 'DELETE')).toBe(true)
+ expect(progress).toHaveBeenCalled()
+ })
+
+ it('answers 503 without a key and never calls the provider', async () => {
+ vi.stubEnv('ASSEMBLYAI_API_KEY', '')
+ const calls = installFetch(() => jsonResponse({}))
+
+ expect(await assemblyAiDiarize(audioBlob())).toEqual({
+ ok: false,
+ error: 'Separação de falantes está indisponível no momento.',
+ status: 503,
+ })
+ expect(calls).toHaveLength(0)
+ })
+
+ it('degrades to an error when the upload fails', async () => {
+ vi.stubEnv('ASSEMBLYAI_API_KEY', 'test-key')
+ const calls = installFetch(() => jsonResponse({}, 500))
+
+ const result = await assemblyAiDiarize(audioBlob())
+ expect(result.ok).toBe(false)
+ expect(calls).toHaveLength(1)
+ })
+
+ it('degrades to an error and deletes the remote transcript on provider error', async () => {
+ vi.stubEnv('ASSEMBLYAI_API_KEY', 'test-key')
+ const calls = installFetch((url, init) => {
+ if (url === ASSEMBLYAI_UPLOAD_URL)
+ return jsonResponse({ upload_url: 'https://cdn/audio.mp3' })
+ if (url === ASSEMBLYAI_TRANSCRIPT_URL && init?.method === 'POST') {
+ return jsonResponse({ id: 'tr_9', status: 'queued' })
+ }
+ if (init?.method === 'DELETE') return jsonResponse({}, 200)
+ return jsonResponse({ status: 'error', error: 'provider exploded' })
+ })
+
+ expect(await assemblyAiDiarize(audioBlob(), { pollIntervalMs: 1 })).toMatchObject({ ok: false })
+ expect(calls.some((call) => call.init?.method === 'DELETE')).toBe(true)
+ })
+
+ it('accepts a completed transcript without utterances (honest empty turns)', async () => {
+ vi.stubEnv('ASSEMBLYAI_API_KEY', 'test-key')
+ installFetch((url, init) => {
+ if (url === ASSEMBLYAI_UPLOAD_URL)
+ return jsonResponse({ upload_url: 'https://cdn/audio.mp3' })
+ if (url === ASSEMBLYAI_TRANSCRIPT_URL && init?.method === 'POST') {
+ return jsonResponse({ id: 'tr_2', status: 'queued' })
+ }
+ return jsonResponse({ status: 'completed', utterances: [] })
+ })
+
+ expect(await assemblyAiDiarize(audioBlob(), { pollIntervalMs: 1 })).toEqual({
+ ok: true,
+ turns: [],
+ })
+ })
+})
diff --git a/tests/unit/recording.unit.spec.ts b/tests/unit/recording.unit.spec.ts
index 23388a57..ad1085a2 100644
--- a/tests/unit/recording.unit.spec.ts
+++ b/tests/unit/recording.unit.spec.ts
@@ -21,6 +21,7 @@ import {
} from '@/lib/recording'
import {
buildRecordingAudioFfmpegArgs,
+ buildRecordingFullAudioFfmpegArgs,
mergeChunkTranscriptions,
RECORDING_AUDIO_CHUNK_SECONDS,
recordingSearchText,
@@ -180,4 +181,17 @@ describe('recording transcription (C199)', () => {
expect(mergeChunkTranscriptions([])).toEqual([])
expect(recordingSearchText([])).toBe('')
})
+
+ it('builds the single-file extraction for diarization (C200)', () => {
+ const args = buildRecordingFullAudioFfmpegArgs({
+ inputPath: '/tmp/in.mkv',
+ outputPath: '/tmp/full.mp3',
+ })
+ expect(args).toContain('-vn')
+ expect(args).toContain('libmp3lame')
+ // One output, no segmentation: speaker numbering needs the whole audio.
+ expect(args).not.toContain('-f')
+ expect(args).not.toContain('-segment_time')
+ expect(args.at(-1)).toBe('/tmp/full.mp3')
+ })
})
diff --git a/tests/unit/recordingDiarization.unit.spec.ts b/tests/unit/recordingDiarization.unit.spec.ts
new file mode 100644
index 00000000..f342158d
--- /dev/null
+++ b/tests/unit/recordingDiarization.unit.spec.ts
@@ -0,0 +1,177 @@
+// @vitest-environment node
+
+import { describe, expect, it } from 'vitest'
+
+import {
+ assignSpeakerKeys,
+ reconcileSpeakerLabels,
+ RECORDING_DIARIZATION_MAX_SECONDS,
+ recordingSpeakerDefaultLabel,
+ speakerKeysInOrder,
+ speakerNameMatches,
+ speakerNamesFromLabels,
+ type DiarizedTurn,
+} from '@/lib/recordingDiarization'
+
+const segment = (startSeconds: number, endSeconds: number) => ({
+ startSeconds,
+ endSeconds,
+ text: `trecho ${startSeconds}`,
+})
+
+const turn = (speaker: string, startSeconds: number, endSeconds: number): DiarizedTurn => ({
+ speaker,
+ startSeconds,
+ endSeconds,
+})
+
+describe('recording speaker grouping (C200)', () => {
+ it('assigns each segment to the turn with the largest overlap', () => {
+ const segments = [segment(0, 10), segment(10, 20), segment(20, 30)]
+ const turns = [turn('A', 0, 11), turn('B', 12, 30)]
+
+ const keyed = assignSpeakerKeys(segments, turns)
+ expect(keyed.map((entry) => entry.speakerKey)).toEqual(['speaker-1', 'speaker-2', 'speaker-2'])
+ // The original fields survive the assignment.
+ expect(keyed[1]).toMatchObject({ startSeconds: 10, endSeconds: 20, text: 'trecho 10' })
+ })
+
+ it('breaks an overlap tie by the closest midpoint', () => {
+ const segments = [segment(0, 10)]
+ // Both turns overlap the same 5 s; B's midpoint is closer to the segment's.
+ const turns = [turn('B', 5, 20), turn('A', 0, 5)]
+
+ expect(assignSpeakerKeys(segments, turns)[0]?.speakerKey).toBe('speaker-1')
+ expect(
+ assignSpeakerKeys([segment(0, 10)], [turn('A', 0, 5), turn('B', 5, 20)])[0],
+ ).toMatchObject({
+ speakerKey: 'speaker-1',
+ })
+ })
+
+ it('falls back to the nearest turn when there is no overlap at all', () => {
+ const segments = [segment(0, 10), segment(30, 40)]
+ const turns = [turn('A', 12, 20), turn('B', 45, 60)]
+
+ expect(assignSpeakerKeys(segments, turns).map((entry) => entry.speakerKey)).toEqual([
+ 'speaker-1',
+ 'speaker-2',
+ ])
+ })
+
+ it('numbers clusters globally by first appearance, not by provider name', () => {
+ const segments = [segment(0, 5), segment(5, 10), segment(10, 15)]
+ const turns = [turn('spk_9', 10, 15), turn('spk_2', 0, 5), turn('spk_9', 0, 10)]
+
+ expect(assignSpeakerKeys(segments, turns).map((entry) => entry.speakerKey)).toEqual([
+ 'speaker-1',
+ 'speaker-2',
+ 'speaker-2',
+ ])
+ })
+
+ it('keeps every key null without a usable turn and ignores invalid turns', () => {
+ expect(assignSpeakerKeys([segment(0, 5)], []).map((entry) => entry.speakerKey)).toEqual([null])
+ expect(
+ assignSpeakerKeys([segment(0, 5)], [turn('', 0, 5), turn('A', Number.NaN, 5)]).map(
+ (entry) => entry.speakerKey,
+ ),
+ ).toEqual([null])
+ })
+
+ it('lists the cluster keys in first-appearance order', () => {
+ expect(
+ speakerKeysInOrder([
+ { speakerKey: 'speaker-2' },
+ { speakerKey: null },
+ { speakerKey: 'speaker-1' },
+ { speakerKey: 'speaker-2' },
+ ]),
+ ).toEqual(['speaker-2', 'speaker-1'])
+ })
+
+ it('reconciles labels 1:1 by time overlap and reports what was dropped', () => {
+ const previousSegments = [{ speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 10 }]
+ const nextSegments = [{ speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 10 }]
+
+ expect(
+ reconcileSpeakerLabels({
+ previousLabels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }],
+ previousSegments,
+ nextSegments,
+ }),
+ ).toEqual({ labels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }], dropped: false })
+
+ expect(
+ reconcileSpeakerLabels({
+ previousLabels: [{ speakerKey: 'speaker-1', label: 'Dep. Jorge Solla' }],
+ previousSegments,
+ nextSegments: [{ speakerKey: 'speaker-1', startSeconds: 40, endSeconds: 50 }],
+ }),
+ ).toEqual({ labels: [], dropped: true })
+
+ expect(
+ reconcileSpeakerLabels({
+ previousLabels: [],
+ previousSegments,
+ nextSegments,
+ }),
+ ).toEqual({ labels: [], dropped: false })
+ })
+
+ it('never shares one new cluster between two old labels (merge)', () => {
+ // Two old clusters merged into one new cluster: keeping either label would
+ // name the other person's speech, so both are dropped.
+ const result = reconcileSpeakerLabels({
+ previousLabels: [
+ { speakerKey: 'speaker-1', label: 'Solla' },
+ { speakerKey: 'speaker-2', label: 'Outra pessoa' },
+ ],
+ previousSegments: [
+ { speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 5 },
+ { speakerKey: 'speaker-2', startSeconds: 5, endSeconds: 10 },
+ ],
+ nextSegments: [{ speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 10 }],
+ })
+
+ expect(result).toEqual({ labels: [], dropped: true })
+ })
+
+ it('never duplicates one label across two new clusters (split)', () => {
+ // One old cluster split into two new ones: the label cannot name both.
+ const result = reconcileSpeakerLabels({
+ previousLabels: [{ speakerKey: 'speaker-1', label: 'Solla' }],
+ previousSegments: [{ speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 10 }],
+ nextSegments: [
+ { speakerKey: 'speaker-1', startSeconds: 0, endSeconds: 5 },
+ { speakerKey: 'speaker-2', startSeconds: 5, endSeconds: 10 },
+ ],
+ })
+
+ expect(result).toEqual({ labels: [], dropped: true })
+ })
+
+ it('derives distinct speaker names case-insensitively, in order', () => {
+ expect(
+ speakerNamesFromLabels([
+ { speakerKey: 'speaker-1', label: ' Dep. Jorge Solla ' },
+ { speakerKey: 'speaker-2', label: 'dep. jorge solla' },
+ { speakerKey: 'speaker-3', label: 'Presidente' },
+ { speakerKey: 'speaker-4', label: ' ' },
+ ]),
+ ).toEqual(['Dep. Jorge Solla', 'Presidente'])
+ expect(speakerNamesFromLabels(null)).toEqual([])
+ })
+
+ it('matches a facet term the same way the ILIKE query does', () => {
+ expect(speakerNameMatches('Dep. Jorge Solla', 'solla')).toBe(true)
+ expect(speakerNameMatches('Dep. Jorge Solla', 'Dep. Jorge')).toBe(true)
+ expect(speakerNameMatches('Presidente', 'solla')).toBe(false)
+ })
+
+ it('names the default label with a number and pins the provider ceiling', () => {
+ expect(recordingSpeakerDefaultLabel(1)).toBe('Falante 1')
+ expect(recordingSpeakerDefaultLabel(12)).toBe('Falante 12')
+ expect(RECORDING_DIARIZATION_MAX_SECONDS).toBe(10 * 60 * 60)
+ })
+})
diff --git a/tests/unit/recordingListFilters.unit.spec.ts b/tests/unit/recordingListFilters.unit.spec.ts
new file mode 100644
index 00000000..7dd2d923
--- /dev/null
+++ b/tests/unit/recordingListFilters.unit.spec.ts
@@ -0,0 +1,44 @@
+// @vitest-environment node
+
+import { describe, expect, it } from 'vitest'
+
+import { buildRecordingListWhere } from '@/utilities/recordings/recordingListFilters'
+
+describe('recording list filters (C199/C200)', () => {
+ it('returns an empty where without search or facet', () => {
+ expect(buildRecordingListWhere({ source: 'enviadas', page: 1 })).toEqual({})
+ })
+
+ it('searches the normalized transcript text', () => {
+ expect(buildRecordingListWhere({ source: 'enviadas', page: 1, q: 'Comissão' })).toEqual({
+ searchText: { like: 'comissao' },
+ })
+ })
+
+ it('filters one person through the derived names', () => {
+ expect(
+ buildRecordingListWhere({ source: 'enviadas', page: 1, people: ['Dep. Jorge Solla'] }),
+ ).toEqual({ speakerNames: { contains: 'Dep. Jorge Solla' } })
+ })
+
+ it('ORs the selected people and ANDs them with the search', () => {
+ expect(
+ buildRecordingListWhere({
+ source: 'enviadas',
+ page: 1,
+ q: 'merenda',
+ people: ['Solla', 'Presidente'],
+ }),
+ ).toEqual({
+ and: [
+ { searchText: { like: 'merenda' } },
+ {
+ or: [
+ { speakerNames: { contains: 'Solla' } },
+ { speakerNames: { contains: 'Presidente' } },
+ ],
+ },
+ ],
+ })
+ })
+})
diff --git a/tests/unit/recordingListUrl.unit.spec.ts b/tests/unit/recordingListUrl.unit.spec.ts
index bee668c6..353136bd 100644
--- a/tests/unit/recordingListUrl.unit.spec.ts
+++ b/tests/unit/recordingListUrl.unit.spec.ts
@@ -7,7 +7,10 @@ import {
buildRecordingListHref,
parseAcervoSource,
parseRecordingListParams,
+ parseRecordingPeople,
+ removeRecordingPerson,
resolveRecordingListUrl,
+ toggleRecordingPerson,
} from '@/utilities/recordings/recordingListUrl'
describe('recording list URL (C199)', () => {
@@ -68,4 +71,63 @@ describe('recording list URL (C199)', () => {
expect(buildAcervoSourceHref('enviadas')).toBe('/campanha/comunicacao/acervo?source=enviadas')
expect(buildAcervoSourceHref('camara')).toBe('/campanha/comunicacao/acervo')
})
+
+ it('parses repeated `person` values: trim, oversized drop, case-insensitive dedupe', () => {
+ expect(parseRecordingPeople([' Solla ', 'solla', 'Presidente'])).toEqual([
+ 'Solla',
+ 'Presidente',
+ ])
+ expect(parseRecordingPeople(undefined)).toEqual([])
+ expect(parseRecordingPeople('')).toEqual([])
+ expect(parseRecordingPeople('x'.repeat(121))).toEqual([])
+ })
+
+ it('canonicalizes and serializes the `person` facet with the query', () => {
+ const parsed = parseRecordingListParams({
+ source: 'enviadas',
+ q: 'merenda',
+ person: ['Solla', 'Presidente'],
+ })
+ expect(parsed.people).toEqual(['Solla', 'Presidente'])
+
+ const canonical = resolveRecordingListUrl({
+ source: 'enviadas',
+ q: 'merenda',
+ person: ['Solla', 'Presidente'],
+ })
+ expect(canonical.href).toBe(
+ '/campanha/comunicacao/acervo?source=enviadas&q=merenda&person=Solla&person=Presidente',
+ )
+ expect(canonical.redirectHref).toBeUndefined()
+ })
+
+ it('drops a duplicated facet value on canonicalization', () => {
+ const canonical = resolveRecordingListUrl({
+ source: 'enviadas',
+ person: ['Solla', 'solla', 'x'.repeat(200)],
+ })
+ expect(canonical.href).toBe('/campanha/comunicacao/acervo?source=enviadas&person=Solla')
+ expect(canonical.redirectHref).toBe('/campanha/comunicacao/acervo?source=enviadas&person=Solla')
+ })
+
+ it('toggles and removes one person, resetting to the first page', () => {
+ const base = { source: 'enviadas' as const, page: 3, q: 'merenda' }
+
+ const added = toggleRecordingPerson(base, 'Solla')
+ expect(added).toEqual({ ...base, page: 1, people: ['Solla'] })
+
+ const removed = toggleRecordingPerson({ ...base, people: ['Solla'] }, 'solla')
+ expect(removed).toEqual({ ...base, page: 1, people: undefined })
+
+ expect(removeRecordingPerson({ ...base, people: ['Solla', 'Presidente'] }, 'Solla')).toEqual({
+ ...base,
+ page: 1,
+ people: ['Presidente'],
+ })
+ expect(removeRecordingPerson({ ...base, people: ['Solla'] }, 'Solla')).toEqual({
+ ...base,
+ page: 1,
+ people: undefined,
+ })
+ })
})