diff --git a/.gitignore b/.gitignore
index df0d8dfd..940332d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,9 @@ next-env.d.ts
/media
+# TSE open-data zip cache (pnpm db:seed:tse)
+/data/tse/
+
# Playwright
node_modules/
/test-results/
diff --git a/docs/plans/baseline-eleitoral-tse.md b/docs/plans/baseline-eleitoral-tse.md
index 9c2a757f..68f47d80 100644
--- a/docs/plans/baseline-eleitoral-tse.md
+++ b/docs/plans/baseline-eleitoral-tse.md
@@ -1,8 +1,8 @@
# Baseline eleitoral TSE 2022
-Status: rascunho
-Atualizado em: 2026-07-17
-Item do roadmap: [docs/roadmap.md](../roadmap.md) (seção "Campanha → Próximos ciclos")
+Status: Fase 1 (A3) implementada — modelo + import
+Atualizado em: 2026-07-18
+Item do roadmap: [docs/roadmap.md](../roadmap.md) (A3/A4 Baseline TSE 2022)
Responsável: —
## Referência visual (UX Pilot)
@@ -43,13 +43,16 @@ Candidatos de interesse (destaques na UI): **Solla = dep. federal BA, PT, nº 13
- **Access control:** dado público TSE. `read` para autenticado em `campaignUser` ou `users` (não aberto ao público). `create/update/delete` admin only. Sem `Consent`, sem transação em runtime. Import via CLI com `overrideAccess: true` fora do runtime deployado.
- **Sem PII.** Não persistir CPF nem título eleitoral. A identidade cross-ano usa `identityKey = sha256(normalize(urnaName) + birthCity + birthState + party)` — só dados públicos da consulta de candidaturas.
- **Migration** `add_election_results` (push:false). `pnpm build` aplica em prod no deploy. Não esbarra no bloqueador LGPD (dado público, sem liderança real).
-- **Import idempotente** pela chave única (upsert), com proveniência (URL, data, SHA-256) no cabeçalho do script. Sem step de revalidate (páginas de campanha são dinâmicas com auth, sem ISR/tag).
+- **Import idempotente por escopo** `(year, office, turn)`: delete + bulk insert via `payload.db.drizzle` (não upsert row a row). Proveniência (URL, SHA-256) no cabeçalho de `scripts/seed-tse-results.mjs`. Sem step de revalidate (páginas de campanha são dinâmicas com auth, sem ISR/tag).
+- **Sem votos de legenda na v1.** Campo `voteType` permanece no schema; só rows `nominal` são importadas. O agregado `votosLegenda` do tally cobre o que os insights precisam. Dataset `votacao_partido_munzona` fica fora do escopo A3.
+- **Votos anulados sub judice (2022):** `electionCandidateVote.votes` ← `QT_VOTOS_NOMINAIS` (apurados); validade fica no tally (`votosNominaisValidos`, `votosAnulados`).
+- **Mapeamento de município:** `NM_MUNICIPIO` (TSE) → nome canônico de `bahiaTerritories.ts` via normalização case/acento/apóstrofo; falha alta em município não mapeado; persiste `cityCode` (TSE) + `cityName` canônico. Teste cobre os 417 municípios.
- **Agregação núcleo→baseline é autossuficiente** (revisão 2026-07-17): `citiesForTerritory` **já existe** em `src/lib/bahiaTerritories.ts` (não vem do plano `zonas-por-municipio.md`), e a correspondência cidade↔zona pode ser resolvida pelas próprias rows de `electionTally` (cada par cidade×zona importado é uma row — `where cityName = C` retorna todas as zonas da cidade). `tseZonesForCity` (plano `zonas-por-municipio.md`) é **opcional**: quando existir, vira a fonte preferida por ser validada por fixture. Sem `tseZones` → todas as zonas da(s) cidade(s); só `region` → todas as cidades do território; sem geografia → estado "sem baseline TSE".
- **i18n e naming** seguem o AGENTS.md: identificadores em inglês, strings visíveis em pt-BR.
## Questões em aberto
-- Mapeamento `CD_MUNICIPIO` (TSE) → nome canônico em `CitiesByState.BA`: usar `NM_MUNICIPIO` do CSV + validação cruzada.
+- ~~Mapeamento `CD_MUNICIPIO` (TSE) → nome canônico~~ — resolvido na Fase 1 via `canonicalizeMunicipalityName` + `cityCode` persistido.
- Limiares de classificação territorial (defesa/ataque/indecisa/perdida) — definir com produto.
- Mostrar 1º e 2º turno para presidente/governador, ou só o decisivo? Recomendação: ambos, com destaque ao decisivo.
- `lideranca` vê ranking/competidor? Recomendação: sim, é dado público e útil ao engajamento.
@@ -96,7 +99,7 @@ flowchart LR
### Fases
-1. **Modelo + import** — collections, migration, `scripts/seed-tse-results.mjs` + `pnpm db:seed:tse`, helpers `src/lib/electionResults.ts` e `src/lib/electionCandidateIdentity.ts`, teste int (totais conhecidos, `elected=true` para Solla/Lula/Jerônimo, idempotência).
+1. **Modelo + import** — **feito (A3, 2026-07-18).** Collections `electionTally` / `electionCandidateVote` / `electionCandidate`, migration `20260718_195854_add_election_results`, `pnpm db:seed:tse`, helpers em `src/lib/electionResults*.ts` + `electionCandidateIdentity.ts`, import drizzle em `src/utilities/electionResultsImport.ts`, testes unit/int com fixtures em `tests/fixtures/tse/`. Seed local BA 2022: **211.014** votos nominais, **2.700** apurações, **1.654** candidatos (6 escopos office×turn).
2. **Detalhe do núcleo** — bloco "Baseline eleitoral 2022" na aba overview; view model `NucleusElectoralBaselineViewModel` em `src/utilities/nucleusViewModels.ts`; componente `src/components/campaign/NucleusElectoralBaseline.tsx`.
3. **Overview da lista** — estende `overview-lista-nucleos.md`: somar baselines sobre o conjunto filtrado (`buildNucleusListWhere`); novo bloco no `NucleusListOverview`.
4. **Insights automáticos (Gap vs 2022)** — implementa somente o insight "Gap vs 2022" (ver seção "Insights automáticos" abaixo). `src/components/campaign/NucleusInsights.tsx` + `src/lib/electionInsights.ts`. Os demais insights viram itens separados no roadmap com planos próprios.
@@ -167,8 +170,12 @@ Cada insight é uma **derivação de leitura** (sem escrita, sem `Consent`, sem
## Arquivos a criar/alterar
-- Criar: `src/collections/ElectionTally.ts`, `src/collections/ElectionCandidateVote.ts`, `src/collections/ElectionCandidate.ts`, `src/lib/electionResults.ts`, `src/lib/electionInsights.ts`, `src/lib/electionCandidateIdentity.ts`, `src/utilities/nucleusElectoralBaseline.ts`, `src/components/campaign/NucleusElectoralBaseline.tsx`, `src/components/campaign/NucleusInsights.tsx`, `scripts/seed-tse-results.mjs`, `scripts/reconcile-running-again.mjs`, migration `add_election_results`, testes int. (Os demais componentes de insight — `electionAlliances.ts`, `DobradinhaOpportunities.tsx` etc. — ficam nos planos separados dos respectivos insights.)
-- Alterar: `src/payload.config.ts`, `src/app/(campaign)/campanha/(app)/nucleos/[slug]/page.tsx` + `src/utilities/nucleusDetailPageData.ts`, `src/utilities/nucleusPageData.ts`/`nucleusViewModels.ts`, overview da lista, `package.json` (`db:seed:tse`).
+**Fase 1 (A3) — feitos:**
+- Criar: `src/collections/ElectionTally.ts`, `ElectionCandidateVote.ts`, `ElectionCandidate.ts`; `src/lib/electionResults.ts`, `electionResultsParse.ts`, `electionResultsBuild.ts`, `electionResultsCsv.ts`, `electionResultsZip.ts`, `electionCandidateIdentity.ts`; `src/utilities/electionResultsImport.ts`; `scripts/seed-tse-results.mjs`; migration `20260718_195854_add_election_results`; fixtures `tests/fixtures/tse/*`; testes unit/int.
+- Alterar: `src/payload.config.ts`, `src/utilities/campaignAccess.ts` (`canReadElectionData` / `canMutateElectionData`), `package.json` (`db:seed:tse`), `scripts/seed-loader.mjs` (stub `server-only`), `.gitignore` (`/data/tse/`).
+
+**Fases 2–5 (A4+) — ainda a criar:**
+- `src/lib/electionInsights.ts`, `src/utilities/nucleusElectoralBaseline.ts`, `src/components/campaign/NucleusElectoralBaseline.tsx`, `NucleusInsights.tsx`, `scripts/reconcile-running-again.mjs`; alterar páginas do núcleo / overview.
## Dependências
diff --git a/docs/roadmap.md b/docs/roadmap.md
index 94db1bbe..3b154805 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -82,7 +82,7 @@ flowchart TD
subgraph TrilhaA["Trilha A — dados eleitorais e território"]
A1["A1 Território multi-município/bairro
(migration)"]
A2["A2 Zonas TSE por município
(auto-preenchimento)"]
- A3["A3 Baseline TSE 2022 — Fase 1
(import, collections)"]
+ A3["A3 Baseline TSE 2022 — Fase 1
(import, collections) ✓"]
A4["A4 Baseline no produto + Gap vs 2022
(detalhe, overview)"]
A5["A5 Insights: conversão, classificação,
alavancagem, mobilização, competitiva"]
A6["A6 Dobradinha 2026"]
@@ -130,7 +130,7 @@ flowchart TD
JUR -.chave de push.-> D2
```
-Itens sem seta de entrada (**paralelizáveis a qualquer momento**): A1, A3, B1, B2, C1, C2 (engenharia — produção espera o jurídico), D1, além dos fill-ins (visitados recentemente, listas globais, reset de senha, higiene PascalCase).
+Itens sem seta de entrada (**paralelizáveis a qualquer momento**): A1, B1, B2, C1, C2 (engenharia — produção espera o jurídico), D1, além dos fill-ins (visitados recentemente, listas globais, reset de senha, higiene PascalCase). **A3 entregue** (2026-07-18) — collections + `pnpm db:seed:tse`.
### Sequência de execução por janela do calendário
@@ -143,7 +143,7 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): A1, A3, B1,
| 3 | A2 Zonas TSE por município (auto-preenchimento) | [detalhes](plans/zonas-por-municipio.md) | A1 (nasce contra `cities[]`) | B1, C1, A3 |
| 4 | B1 Overview da lista de núcleos | [detalhes](plans/overview-lista-nucleos.md) | — | A1, A2, C1 |
| 5 | C1 Compartilhar página (quick win de campo) | [detalhes](plans/compartilhar-pagina.md) | — | tudo |
-| 6 | A3 Baseline TSE 2022 — Fase 1 (import) | [detalhes](plans/baseline-eleitoral-tse.md) | — (dado público; sem bloqueador LGPD) | tudo |
+| 6 | A3 Baseline TSE 2022 — Fase 1 (import) **feito** | [detalhes](plans/baseline-eleitoral-tse.md) | — (dado público; sem bloqueador LGPD) | tudo |
**Janela 2 — 05/08 → 16/08 (pré-propaganda): base nominal + inteligência + agenda prontas para o arranque.**
diff --git a/package.json b/package.json
index be94196e..c4b73605 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"db:stop": "docker compose down",
"db:pull": "node scripts/db-pull.mjs",
"db:seed:posts": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-posts.mjs",
+ "db:seed:tse": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-tse-results.mjs",
"dev": "node scripts/guard-dev-db.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev",
"devsafe": "rm -rf .next && node scripts/guard-dev-db.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev",
"generate:importmap": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload generate:importmap",
@@ -89,8 +90,10 @@
"@types/node": "22.19.9",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
+ "@types/yauzl": "^3.4.0",
"@vercel/blob": "2.3.1",
"@vitejs/plugin-react": "4.5.2",
+ "csv-parse": "^7.0.1",
"eslint": "^9.39.2",
"eslint-config-next": "15.4.11",
"eslint-config-prettier": "^10.1.8",
@@ -104,7 +107,8 @@
"typescript": "5.7.3",
"typescript-eslint": "^8.55.0",
"vite-tsconfig-paths": "6.0.5",
- "vitest": "4.0.18"
+ "vitest": "4.0.18",
+ "yauzl": "^3.4.0"
},
"engines": {
"node": "^18.20.2 || >=20.9.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 68f21d7d..719916d2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -138,12 +138,18 @@ importers:
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.14)
+ '@types/yauzl':
+ specifier: ^3.4.0
+ version: 3.4.0
'@vercel/blob':
specifier: 2.3.1
version: 2.3.1
'@vitejs/plugin-react':
specifier: 4.5.2
version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2))
+ csv-parse:
+ specifier: ^7.0.1
+ version: 7.0.1
eslint:
specifier: ^9.39.2
version: 9.39.2(jiti@2.6.1)
@@ -186,6 +192,9 @@ importers:
vitest:
specifier: 4.0.18
version: 4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(typescript@5.7.3))(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2)
+ yauzl:
+ specifier: ^3.4.0
+ version: 3.4.0
packages:
@@ -2828,6 +2837,9 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+ '@types/yauzl@3.4.0':
+ resolution: {integrity: sha512-NRPn5w6h8dhcnmx3YIRQcqMywY/+nND/uOkJessedcrowO3C0AssHp3tMJpxKAwOhFOo0OV1y9VtsC5hbKKBAw==}
+
'@typescript-eslint/eslint-plugin@8.55.0':
resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3498,6 +3510,9 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ csv-parse@7.0.1:
+ resolution: {integrity: sha512-+2z7Ar0APQ7Uu6fX4cn+pitRmxjZ1WPBcGmZFKmA74FCyi7Et/XZx8cjNQ5CjbZ4HCOxXCOpRBYvYH08Qa003A==}
+
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -5240,6 +5255,9 @@ packages:
peerDependencies:
graphql: ^16.8.1
+ pend@1.2.0:
+ resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
+
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
@@ -6438,6 +6456,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
+ yauzl@3.4.0:
+ resolution: {integrity: sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==}
+ engines: {node: '>=12'}
+
yjs@13.6.29:
resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
@@ -9098,6 +9120,10 @@ snapshots:
dependencies:
'@types/node': 22.19.9
+ '@types/yauzl@3.4.0':
+ dependencies:
+ '@types/node': 22.19.9
+
'@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -9750,6 +9776,8 @@ snapshots:
csstype@3.2.3: {}
+ csv-parse@7.0.1: {}
+
damerau-levenshtein@1.0.8: {}
data-uri-to-buffer@4.0.1: {}
@@ -11755,6 +11783,8 @@ snapshots:
- typescript
- utf-8-validate
+ pend@1.2.0: {}
+
pg-cloudflare@1.3.0:
optional: true
@@ -13155,6 +13185,10 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
+ yauzl@3.4.0:
+ dependencies:
+ pend: 1.2.0
+
yjs@13.6.29:
dependencies:
lib0: 0.2.117
diff --git a/scripts/assert-local-database.mjs b/scripts/assert-local-database.mjs
new file mode 100644
index 00000000..2503af49
--- /dev/null
+++ b/scripts/assert-local-database.mjs
@@ -0,0 +1,45 @@
+/**
+ * Shared local-DB guard for CLI scripts (seeds, pnpm dev guard, etc.).
+ * Host allowlist + ALLOW_REMOTE_DB escape hatch.
+ */
+
+const OVERRIDE_FLAG = 'ALLOW_REMOTE_DB'
+const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', 'postgres'])
+
+/**
+ * @param {string} label Log/error prefix, e.g. "seed:tse"
+ * @param {string} [usageHint] Extra lines shown when refusing a remote host
+ */
+export const assertLocalDatabase = (label, usageHint = '') => {
+ const die = (message) => {
+ console.error(`\n[${label}] ${message}\n`)
+ process.exit(1)
+ }
+
+ const databaseUrl = process.env.DATABASE_URL
+ if (!databaseUrl) die('DATABASE_URL is not set. Refusing to continue.')
+
+ if (process.env[OVERRIDE_FLAG] === 'true' || process.env[OVERRIDE_FLAG] === '1') {
+ console.warn(
+ `\n[${label}] ${OVERRIDE_FLAG} is set — connecting to a remote database on purpose. Be careful.\n`,
+ )
+ return
+ }
+
+ let host
+ try {
+ host = new URL(databaseUrl).hostname
+ } catch {
+ die('DATABASE_URL is not a valid connection string.')
+ }
+
+ if (!LOCAL_HOSTS.has(host)) {
+ die(
+ `DATABASE_URL points at a non-local host ("${host}").\n` +
+ (usageHint ? `${usageHint}\n\n` : '') +
+ `If you REALLY mean to use a remote database, re-run with:\n ${OVERRIDE_FLAG}=true …`,
+ )
+ }
+
+ console.log(`[${label}] OK — using local database host "${host}".`)
+}
diff --git a/scripts/guard-dev-db.mjs b/scripts/guard-dev-db.mjs
index 11decf64..b2d2c7e4 100644
--- a/scripts/guard-dev-db.mjs
+++ b/scripts/guard-dev-db.mjs
@@ -9,8 +9,7 @@
*/
import { config as loadEnv } from 'dotenv'
-const OVERRIDE_FLAG = 'ALLOW_REMOTE_DB'
-const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', 'postgres'])
+import { assertLocalDatabase } from './assert-local-database.mjs'
// Mirror Next.js precedence (.env.local wins over .env) WITHOUT overriding a
// DATABASE_URL that is already set in the real environment (e.g. injected by
@@ -18,40 +17,10 @@ const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', 'postgr
loadEnv({ path: '.env.local' })
loadEnv({ path: '.env' })
-const fail = (message) => {
- console.error(`\n[guard-dev-db] ${message}\n`)
- process.exit(1)
-}
-
-const databaseUrl = process.env.DATABASE_URL
-
-if (!databaseUrl) {
- fail('DATABASE_URL is not set. Refusing to start the dev server.')
-}
-
-if (process.env[OVERRIDE_FLAG] === 'true' || process.env[OVERRIDE_FLAG] === '1') {
- console.warn(
- `\n[guard-dev-db] ${OVERRIDE_FLAG} is set — connecting to a remote database on purpose. Be careful.\n`,
- )
- process.exit(0)
-}
-
-let host
-try {
- host = new URL(databaseUrl).hostname
-} catch {
- fail('DATABASE_URL is not a valid connection string.')
-}
-
-if (!LOCAL_HOSTS.has(host)) {
- fail(
- `DATABASE_URL points at a non-local host ("${host}").\n` +
- 'This is almost certainly the production or a shared database.\n\n' +
- 'Local development should use the local Postgres from docker-compose:\n' +
- ' 1. pnpm db:start\n' +
- ' 2. set DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo in .env.local\n\n' +
- `If you REALLY mean to connect to a remote database, re-run with:\n ${OVERRIDE_FLAG}=true pnpm dev`,
- )
-}
-
-console.log(`[guard-dev-db] OK — using local database host "${host}".`)
+assertLocalDatabase(
+ 'guard-dev-db',
+ 'This is almost certainly the production or a shared database.\n\n' +
+ 'Local development should use the local Postgres from docker-compose:\n' +
+ ' 1. pnpm db:start\n' +
+ ' 2. set DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo in .env.local',
+)
diff --git a/scripts/seed-loader.mjs b/scripts/seed-loader.mjs
index 1a375f51..15d21646 100644
--- a/scripts/seed-loader.mjs
+++ b/scripts/seed-loader.mjs
@@ -1,5 +1,5 @@
/**
- * Seed-only Node module loader: stubs `next/cache` with no-ops.
+ * Seed-only Node module loader: stubs `next/cache` and `server-only`.
*
* The `post` and `tag` collections call `revalidateTag()` (via
* `src/utilities/documents.ts`) inside their `afterChange` hooks. `revalidateTag`
@@ -8,12 +8,16 @@
* every `payload.create`. Rather than editing the collections (owned elsewhere),
* we replace `next/cache` with harmless no-ops for the duration of the seed.
*
- * Registered via `--import=./scripts/seed-loader.mjs` in the `db:seed:posts`
- * npm script; it affects only that process.
+ * Campaign collections also pull in utilities marked `import 'server-only'`; that
+ * package throws outside the Next.js server runtime, so we stub it as an empty
+ * module for CLI seeds (`db:seed:posts`, `db:seed:tse`).
+ *
+ * Registered via `--import=./scripts/seed-loader.mjs` in the seed npm scripts;
+ * it affects only that process.
*/
import { register } from 'node:module'
-const shim =
+const nextCacheShim =
'data:text/javascript,' +
encodeURIComponent(
[
@@ -24,12 +28,17 @@ const shim =
].join('\n'),
)
+const serverOnlyShim = 'data:text/javascript,' + encodeURIComponent('export {};')
+
const hooks =
'data:text/javascript,' +
encodeURIComponent(
`export function resolve(specifier, context, nextResolve) {
if (specifier === 'next/cache') {
- return { url: ${JSON.stringify(shim)}, shortCircuit: true }
+ return { url: ${JSON.stringify(nextCacheShim)}, shortCircuit: true }
+ }
+ if (specifier === 'server-only') {
+ return { url: ${JSON.stringify(serverOnlyShim)}, shortCircuit: true }
}
return nextResolve(specifier, context)
}`,
diff --git a/scripts/seed-posts.mjs b/scripts/seed-posts.mjs
index 8803cb82..79aff666 100644
--- a/scripts/seed-posts.mjs
+++ b/scripts/seed-posts.mjs
@@ -29,6 +29,8 @@ import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext-
import { del as blobDel } from '@vercel/blob'
import { getPayload } from 'payload'
+import { assertLocalDatabase } from './assert-local-database.mjs'
+
// Mirror Next.js precedence (.env.local wins over .env) without clobbering a
// DATABASE_URL already set in the real environment.
loadEnv({ path: '.env.local' })
@@ -36,49 +38,11 @@ loadEnv({ path: '.env' })
const config = (await import('../src/payload.config.ts')).default
-// ---------------------------------------------------------------------------
-// DB-safety guard (same shape as scripts/guard-dev-db.mjs)
-// ---------------------------------------------------------------------------
-
-const OVERRIDE_FLAG = 'ALLOW_REMOTE_DB'
-const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', 'postgres'])
-
const die = (message) => {
console.error(`\n[seed:posts] ${message}\n`)
process.exit(1)
}
-const assertLocalDatabase = () => {
- const databaseUrl = process.env.DATABASE_URL
- if (!databaseUrl) die('DATABASE_URL is not set. Refusing to seed.')
-
- if (process.env[OVERRIDE_FLAG] === 'true' || process.env[OVERRIDE_FLAG] === '1') {
- console.warn(
- `\n[seed:posts] ${OVERRIDE_FLAG} is set — seeding a remote database on purpose. Be careful.\n`,
- )
- return
- }
-
- let host
- try {
- host = new URL(databaseUrl).hostname
- } catch {
- die('DATABASE_URL is not a valid connection string.')
- }
-
- if (!LOCAL_HOSTS.has(host)) {
- die(
- `DATABASE_URL points at a non-local host ("${host}").\n` +
- 'This seed WRITES data (posts/tags/media) and is meant for the local Postgres.\n\n' +
- ' 1. pnpm db:start\n' +
- ' 2. set DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo in .env.local\n\n' +
- `If you REALLY mean to seed a remote database, re-run with:\n ${OVERRIDE_FLAG}=true pnpm db:seed:posts`,
- )
- }
-
- console.log(`[seed:posts] OK — using local database host "${host}".`)
-}
-
// ---------------------------------------------------------------------------
// Taxonomy (Section 7): the categories + control tag, and the per-article map.
// Keys are the WordPress slugs of the live articles. Every post gets exactly
@@ -450,7 +414,12 @@ async function ensureCoverMedia(payload, { slug, coverUrl, alt }) {
// ---------------------------------------------------------------------------
async function main() {
- assertLocalDatabase()
+ assertLocalDatabase(
+ 'seed:posts',
+ 'This seed WRITES data (posts/tags/media) and is meant for the local Postgres.\n' +
+ ' 1. pnpm db:start\n' +
+ ' 2. set DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo in .env.local',
+ )
const payload = await getPayload({ config })
const editorConfig = await editorConfigFactory.default({ config: payload.config })
diff --git a/scripts/seed-tse-results.mjs b/scripts/seed-tse-results.mjs
new file mode 100644
index 00000000..c00b959d
--- /dev/null
+++ b/scripts/seed-tse-results.mjs
@@ -0,0 +1,173 @@
+/**
+ * Seeds electionTally / electionCandidateVote / electionCandidate from TSE
+ * open data for the 2022 general election (Bahia scope).
+ *
+ * Provenance (do not strip):
+ * - Portal: https://dadosabertos.tse.jus.br/dataset/resultados-2022
+ * - Portal: https://dadosabertos.tse.jus.br/dataset/candidatos-2022
+ * - votacao_candidato_munzona_2022.zip
+ * https://cdn.tse.jus.br/estatistica/sead/odsele/votacao_candidato_munzona/votacao_candidato_munzona_2022.zip
+ * - detalhe_votacao_munzona_2022.zip
+ * https://cdn.tse.jus.br/estatistica/sead/odsele/detalhe_votacao_munzona/detalhe_votacao_munzona_2022.zip
+ * - consulta_cand_2022.zip
+ * https://cdn.tse.jus.br/estatistica/sead/odsele/consulta_cand/consulta_cand_2022.zip
+ * - License: Creative Commons Atribuição (dados abertos TSE)
+ * - electionCandidateVote.votes ← QT_VOTOS_NOMINAIS (votos apurados; validade no tally)
+ * - v1: only voteType=nominal (no votacao_partido_munzona / legenda rows)
+ *
+ * Safety: refuses non-local DATABASE_URL unless ALLOW_REMOTE_DB=true.
+ * Idempotent: replace-by-scope (year, office, turn) via drizzle transaction.
+ *
+ * Usage:
+ * pnpm db:seed:tse
+ * ALLOW_REMOTE_DB=true pnpm db:seed:tse
+ * TSE_CACHE_DIR=./data/tse pnpm db:seed:tse # reuse downloaded zips
+ */
+
+import { createHash } from 'node:crypto'
+import { mkdir, readFile, writeFile, access } from 'node:fs/promises'
+import { join } from 'node:path'
+import { config as loadEnv } from 'dotenv'
+import { getPayload } from 'payload'
+
+import { assertLocalDatabase } from './assert-local-database.mjs'
+
+loadEnv({ path: '.env.local' })
+loadEnv({ path: '.env' })
+
+const config = (await import('../src/payload.config.ts')).default
+const { parseTseCsvBuffer } = await import('../src/lib/electionResultsCsv.ts')
+const { buildElectionResultsFromCsvRows } = await import('../src/lib/electionResultsBuild.ts')
+const { downloadToBuffer, readZipEntry } = await import('../src/lib/electionResultsZip.ts')
+const { buildImportBundles, importElectionBundles } = await import(
+ '../src/utilities/electionResultsImport.ts'
+)
+
+const SOURCES = [
+ {
+ key: 'votacao_candidato_munzona_2022',
+ url: 'https://cdn.tse.jus.br/estatistica/sead/odsele/votacao_candidato_munzona/votacao_candidato_munzona_2022.zip',
+ },
+ {
+ key: 'detalhe_votacao_munzona_2022',
+ url: 'https://cdn.tse.jus.br/estatistica/sead/odsele/detalhe_votacao_munzona/detalhe_votacao_munzona_2022.zip',
+ },
+ {
+ key: 'consulta_cand_2022',
+ url: 'https://cdn.tse.jus.br/estatistica/sead/odsele/consulta_cand/consulta_cand_2022.zip',
+ },
+]
+
+const die = (message) => {
+ console.error(`\n[seed:tse] ${message}\n`)
+ process.exit(1)
+}
+
+const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex')
+
+const cacheDir = () => process.env.TSE_CACHE_DIR || join(process.cwd(), 'data', 'tse')
+
+const ensureCachedZip = async ({ key, url }) => {
+ const dir = cacheDir()
+ await mkdir(dir, { recursive: true })
+ const path = join(dir, `${key}.zip`)
+ try {
+ await access(path)
+ console.log(`[seed:tse] cache hit ${path}`)
+ const buffer = await readFile(path)
+ return { key, url, buffer, hash: sha256(buffer) }
+ } catch (error) {
+ if (error?.code !== 'ENOENT') throw error
+ }
+ console.log(`[seed:tse] downloading ${url}`)
+ const buffer = await downloadToBuffer(url)
+ const hash = sha256(buffer)
+ await writeFile(path, buffer)
+ console.log(`[seed:tse] saved ${path} (${buffer.length} bytes, sha256=${hash})`)
+ return { key, url, buffer, hash }
+}
+
+const loadCsvFromZip = async (zipBuffer, fileName) => {
+ const entry = await readZipEntry(zipBuffer, (name) => name.endsWith(fileName))
+ if (!entry) die(`ZIP entry not found: ${fileName}`)
+ console.log(`[seed:tse] parsing ${entry.fileName} (${entry.buffer.length} bytes)`)
+ return parseTseCsvBuffer(entry.buffer)
+}
+
+const main = async () => {
+ assertLocalDatabase(
+ 'seed:tse',
+ 'This seed WRITES election reference data and is meant for the local Postgres.\n' +
+ ' 1. pnpm db:start\n' +
+ ' 2. set DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo in .env.local',
+ )
+
+ const [votacaoZip, detalheZip, candZip] = await Promise.all(SOURCES.map(ensureCachedZip))
+
+ console.log('[seed:tse] provenance:')
+ for (const source of [votacaoZip, detalheZip, candZip]) {
+ console.log(` - ${source.url}`)
+ console.log(` sha256=${source.hash}`)
+ }
+
+ // Sequential: BA vote CSV alone is hundreds of MB; parallel parse spikes peak RAM.
+ const voteBa = await loadCsvFromZip(votacaoZip.buffer, 'votacao_candidato_munzona_2022_BA.csv')
+ const voteBr = await loadCsvFromZip(votacaoZip.buffer, 'votacao_candidato_munzona_2022_BR.csv')
+ const detalheBa = await loadCsvFromZip(
+ detalheZip.buffer,
+ 'detalhe_votacao_munzona_2022_BA.csv',
+ )
+ const detalheBr = await loadCsvFromZip(
+ detalheZip.buffer,
+ 'detalhe_votacao_munzona_2022_BR.csv',
+ )
+ const candBa = await loadCsvFromZip(candZip.buffer, 'consulta_cand_2022_BA.csv')
+ const candBr = await loadCsvFromZip(candZip.buffer, 'consulta_cand_2022_BR.csv')
+
+ const built = buildElectionResultsFromCsvRows({
+ voteRows: [...voteBa, ...voteBr],
+ detalheRows: [...detalheBa, ...detalheBr],
+ candBaRows: candBa,
+ candBrRows: candBr,
+ })
+
+ if (built.unknownMunicipalities.length > 0) {
+ die(
+ `Municípios TSE sem mapeamento canônico (${built.unknownMunicipalities.length}):\n - ` +
+ built.unknownMunicipalities.slice(0, 20).join('\n - ') +
+ (built.unknownMunicipalities.length > 20 ? '\n - …' : ''),
+ )
+ }
+
+ console.log(
+ `[seed:tse] parsed votes=${built.votes.length} tallies=${built.tallies.length} candidates=${built.candidates.length}`,
+ )
+
+ const bundles = buildImportBundles(built)
+ console.log(`[seed:tse] importing ${bundles.length} scopes…`)
+
+ const payload = await getPayload({ config })
+ const counts = await importElectionBundles(payload, bundles)
+
+ let votes = 0
+ let tallies = 0
+ let candidates = 0
+ for (const count of counts) {
+ votes += count.votesInserted
+ tallies += count.talliesInserted
+ candidates += count.candidatesInserted
+ console.log(
+ ` ${count.scope.office} turn=${count.scope.turn}: votes=${count.votesInserted} tallies=${count.talliesInserted} candidates=${count.candidatesInserted}`,
+ )
+ }
+
+ console.log(
+ `\n[seed:tse] done. inserted votes=${votes} tallies=${tallies} candidates=${candidates}`,
+ )
+ process.exit(0)
+}
+
+main().catch((error) => {
+ console.error('[seed:tse] fatal:', error)
+ process.exit(1)
+})
diff --git a/src/collections/ElectionCandidate.ts b/src/collections/ElectionCandidate.ts
new file mode 100644
index 00000000..bf09832f
--- /dev/null
+++ b/src/collections/ElectionCandidate.ts
@@ -0,0 +1,159 @@
+import type { CollectionConfig } from 'payload'
+
+import { canMutateElectionData, canReadElectionData } from '@/utilities/campaignAccess'
+
+import {
+ ELECTION_ELECTED_BY_OPTIONS,
+ ELECTION_OFFICE_OPTIONS,
+ ELECTION_RUNNING_AGAIN_OPTIONS,
+ ELECTION_TURN_OPTIONS,
+} from '@/lib/electionResults'
+
+const slug = 'electionCandidate'
+
+export const ElectionCandidate: CollectionConfig = {
+ slug,
+ labels: {
+ singular: 'Candidato eleitoral',
+ plural: 'Candidatos eleitorais',
+ },
+ admin: {
+ group: 'Dados Eleitorais',
+ useAsTitle: 'urnaName',
+ defaultColumns: [
+ 'year',
+ 'office',
+ 'candidateNumber',
+ 'urnaName',
+ 'party',
+ 'elected',
+ 'updatedAt',
+ ],
+ description: 'Registro de candidatura TSE (dado público).',
+ },
+ access: {
+ create: canMutateElectionData,
+ read: canReadElectionData,
+ update: canMutateElectionData,
+ delete: canMutateElectionData,
+ },
+ indexes: [
+ {
+ fields: ['year', 'office', 'turn', 'state', 'candidateNumber'],
+ unique: true,
+ },
+ ],
+ fields: [
+ {
+ name: 'year',
+ type: 'number',
+ label: 'Ano',
+ required: true,
+ index: true,
+ min: 2000,
+ max: 2100,
+ },
+ {
+ name: 'office',
+ type: 'select',
+ label: 'Cargo',
+ required: true,
+ index: true,
+ options: [...ELECTION_OFFICE_OPTIONS],
+ },
+ {
+ name: 'turn',
+ type: 'select',
+ label: 'Turno',
+ required: true,
+ index: true,
+ options: [...ELECTION_TURN_OPTIONS],
+ },
+ {
+ name: 'state',
+ type: 'text',
+ label: 'UF',
+ required: true,
+ defaultValue: 'BA',
+ maxLength: 2,
+ index: true,
+ },
+ {
+ name: 'candidateNumber',
+ type: 'number',
+ label: 'Número do candidato',
+ required: true,
+ index: true,
+ min: 0,
+ },
+ {
+ name: 'urnaName',
+ type: 'text',
+ label: 'Nome na urna',
+ required: true,
+ maxLength: 160,
+ },
+ {
+ name: 'completeName',
+ type: 'text',
+ label: 'Nome completo',
+ maxLength: 200,
+ },
+ {
+ name: 'party',
+ type: 'text',
+ label: 'Partido',
+ maxLength: 40,
+ },
+ {
+ name: 'coalition',
+ type: 'text',
+ label: 'Coligação',
+ maxLength: 240,
+ },
+ {
+ name: 'candidateStatus',
+ type: 'text',
+ label: 'Situação da candidatura',
+ maxLength: 80,
+ },
+ {
+ name: 'elected',
+ type: 'checkbox',
+ label: 'Eleito',
+ defaultValue: false,
+ index: true,
+ },
+ {
+ name: 'electedBy',
+ type: 'select',
+ label: 'Forma de eleição',
+ options: [...ELECTION_ELECTED_BY_OPTIONS],
+ },
+ {
+ name: 'totalVotesState',
+ type: 'number',
+ label: 'Total de votos no estado',
+ min: 0,
+ },
+ {
+ name: 'identityKey',
+ type: 'text',
+ label: 'Chave de identidade cross-ano',
+ index: true,
+ maxLength: 64,
+ admin: {
+ description: 'sha256(nome urna + naturalidade + partido); só dados públicos.',
+ readOnly: true,
+ },
+ },
+ {
+ name: 'runningAgain2026',
+ type: 'select',
+ label: 'Concorre de novo em 2026',
+ required: true,
+ defaultValue: 'desconhecido',
+ options: [...ELECTION_RUNNING_AGAIN_OPTIONS],
+ },
+ ],
+}
diff --git a/src/collections/ElectionCandidateVote.ts b/src/collections/ElectionCandidateVote.ts
new file mode 100644
index 00000000..786eea81
--- /dev/null
+++ b/src/collections/ElectionCandidateVote.ts
@@ -0,0 +1,158 @@
+import type { CollectionConfig } from 'payload'
+
+import { canMutateElectionData, canReadElectionData } from '@/utilities/campaignAccess'
+
+import {
+ ELECTION_OFFICE_OPTIONS,
+ ELECTION_TURN_OPTIONS,
+ ELECTION_VOTE_TYPE_OPTIONS,
+} from '@/lib/electionResults'
+
+const slug = 'electionCandidateVote'
+
+export const ElectionCandidateVote: CollectionConfig = {
+ slug,
+ labels: {
+ singular: 'Voto por candidato',
+ plural: 'Votos por candidato',
+ },
+ admin: {
+ group: 'Dados Eleitorais',
+ useAsTitle: 'candidateName',
+ defaultColumns: [
+ 'year',
+ 'office',
+ 'cityName',
+ 'zoneNumber',
+ 'candidateNumber',
+ 'votes',
+ 'updatedAt',
+ ],
+ description: 'Votos nominais oficiais TSE por município e zona (dado público).',
+ },
+ access: {
+ create: canMutateElectionData,
+ read: canReadElectionData,
+ update: canMutateElectionData,
+ delete: canMutateElectionData,
+ },
+ indexes: [
+ {
+ fields: [
+ 'year',
+ 'office',
+ 'turn',
+ 'state',
+ 'cityCode',
+ 'zoneNumber',
+ 'candidateNumber',
+ 'voteType',
+ ],
+ unique: true,
+ },
+ ],
+ fields: [
+ {
+ name: 'year',
+ type: 'number',
+ label: 'Ano',
+ required: true,
+ index: true,
+ min: 2000,
+ max: 2100,
+ },
+ {
+ name: 'office',
+ type: 'select',
+ label: 'Cargo',
+ required: true,
+ index: true,
+ options: [...ELECTION_OFFICE_OPTIONS],
+ },
+ {
+ name: 'turn',
+ type: 'select',
+ label: 'Turno',
+ required: true,
+ index: true,
+ options: [...ELECTION_TURN_OPTIONS],
+ },
+ {
+ name: 'state',
+ type: 'text',
+ label: 'UF',
+ required: true,
+ defaultValue: 'BA',
+ maxLength: 2,
+ index: true,
+ },
+ {
+ name: 'cityCode',
+ type: 'text',
+ label: 'Código do município (TSE)',
+ required: true,
+ index: true,
+ maxLength: 16,
+ },
+ {
+ name: 'cityName',
+ type: 'text',
+ label: 'Município',
+ required: true,
+ index: true,
+ maxLength: 120,
+ },
+ {
+ name: 'zoneNumber',
+ type: 'number',
+ label: 'Zona TSE',
+ required: true,
+ index: true,
+ min: 1,
+ max: 999,
+ },
+ {
+ name: 'candidateNumber',
+ type: 'number',
+ label: 'Número do candidato',
+ required: true,
+ index: true,
+ min: 0,
+ },
+ {
+ name: 'candidateName',
+ type: 'text',
+ label: 'Nome na urna',
+ required: true,
+ maxLength: 160,
+ },
+ {
+ name: 'coalition',
+ type: 'text',
+ label: 'Coligação',
+ maxLength: 240,
+ },
+ {
+ name: 'party',
+ type: 'text',
+ label: 'Partido',
+ maxLength: 40,
+ },
+ {
+ name: 'voteType',
+ type: 'select',
+ label: 'Tipo de voto',
+ required: true,
+ index: true,
+ defaultValue: 'nominal',
+ options: [...ELECTION_VOTE_TYPE_OPTIONS],
+ },
+ {
+ name: 'votes',
+ type: 'number',
+ label: 'Votos',
+ required: true,
+ min: 1,
+ },
+ ],
+}
diff --git a/src/collections/ElectionTally.ts b/src/collections/ElectionTally.ts
new file mode 100644
index 00000000..8bbc9f28
--- /dev/null
+++ b/src/collections/ElectionTally.ts
@@ -0,0 +1,188 @@
+import type { CollectionConfig } from 'payload'
+
+import { canMutateElectionData, canReadElectionData } from '@/utilities/campaignAccess'
+
+import { ELECTION_OFFICE_OPTIONS, ELECTION_TURN_OPTIONS } from '@/lib/electionResults'
+
+const slug = 'electionTally'
+
+export const ElectionTally: CollectionConfig = {
+ slug,
+ labels: {
+ singular: 'Apuração eleitoral',
+ plural: 'Apurações eleitorais',
+ },
+ admin: {
+ group: 'Dados Eleitorais',
+ useAsTitle: 'cityName',
+ defaultColumns: ['year', 'office', 'turn', 'cityName', 'zoneNumber', 'updatedAt'],
+ description: 'Totais oficiais TSE por município e zona (dado público).',
+ },
+ access: {
+ create: canMutateElectionData,
+ read: canReadElectionData,
+ update: canMutateElectionData,
+ delete: canMutateElectionData,
+ },
+ indexes: [
+ {
+ fields: ['year', 'office', 'turn', 'state', 'cityCode', 'zoneNumber'],
+ unique: true,
+ },
+ ],
+ fields: [
+ {
+ name: 'year',
+ type: 'number',
+ label: 'Ano',
+ required: true,
+ index: true,
+ min: 2000,
+ max: 2100,
+ },
+ {
+ name: 'office',
+ type: 'select',
+ label: 'Cargo',
+ required: true,
+ index: true,
+ options: [...ELECTION_OFFICE_OPTIONS],
+ },
+ {
+ name: 'turn',
+ type: 'select',
+ label: 'Turno',
+ required: true,
+ index: true,
+ options: [...ELECTION_TURN_OPTIONS],
+ },
+ {
+ name: 'state',
+ type: 'text',
+ label: 'UF',
+ required: true,
+ defaultValue: 'BA',
+ maxLength: 2,
+ index: true,
+ },
+ {
+ name: 'cityCode',
+ type: 'text',
+ label: 'Código do município (TSE)',
+ required: true,
+ index: true,
+ maxLength: 16,
+ },
+ {
+ name: 'cityName',
+ type: 'text',
+ label: 'Município',
+ required: true,
+ index: true,
+ maxLength: 120,
+ },
+ {
+ name: 'zoneNumber',
+ type: 'number',
+ label: 'Zona TSE',
+ required: true,
+ index: true,
+ min: 1,
+ max: 999,
+ },
+ {
+ name: 'aptos',
+ type: 'number',
+ label: 'Eleitores aptos',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'comparecimento',
+ type: 'number',
+ label: 'Comparecimento',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'abstencoes',
+ type: 'number',
+ label: 'Abstenções',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosValidos',
+ type: 'number',
+ label: 'Votos válidos',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosNominaisValidos',
+ type: 'number',
+ label: 'Votos nominais válidos',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosLegenda',
+ type: 'number',
+ label: 'Votos de legenda',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosBranco',
+ type: 'number',
+ label: 'Votos em branco',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosNulo',
+ type: 'number',
+ label: 'Votos nulos',
+ required: true,
+ min: 0,
+ },
+ {
+ name: 'votosAnulados',
+ type: 'number',
+ label: 'Votos anulados',
+ required: true,
+ min: 0,
+ defaultValue: 0,
+ },
+ {
+ name: 'winnerCandidateNumber',
+ type: 'number',
+ label: 'Número do mais votado',
+ min: 0,
+ },
+ {
+ name: 'winnerCandidateName',
+ type: 'text',
+ label: 'Nome do mais votado',
+ maxLength: 160,
+ },
+ {
+ name: 'winnerVotes',
+ type: 'number',
+ label: 'Votos do mais votado',
+ min: 0,
+ },
+ {
+ name: 'winnerCoalition',
+ type: 'text',
+ label: 'Coligação do mais votado',
+ maxLength: 240,
+ },
+ {
+ name: 'winnerParty',
+ type: 'text',
+ label: 'Partido do mais votado',
+ maxLength: 40,
+ },
+ ],
+}
diff --git a/src/lib/electionCandidateIdentity.ts b/src/lib/electionCandidateIdentity.ts
new file mode 100644
index 00000000..a845f5ab
--- /dev/null
+++ b/src/lib/electionCandidateIdentity.ts
@@ -0,0 +1,30 @@
+import { createHash } from 'node:crypto'
+
+import { normalizeMunicipalityKey } from '@/lib/electionResults'
+
+/** Same NFD/case fold as municipality keys; empty string for missing parts. */
+export const normalizeIdentityPart = (value: string | null | undefined): string =>
+ normalizeMunicipalityKey(value ?? '')
+
+export type IdentityKeyInput = {
+ urnaName: string | null | undefined
+ birthCity: string | null | undefined
+ birthState: string | null | undefined
+ party: string | null | undefined
+}
+
+/**
+ * identityKey = sha256(normalize(urnaName) + birthCity + birthState + party)
+ * Collisions (homonyms) and false negatives (party change) are expected;
+ * admin review sets runningAgain2026 before relying on the match.
+ */
+export const computeIdentityKey = (input: IdentityKeyInput): string => {
+ const material = [
+ normalizeIdentityPart(input.urnaName),
+ normalizeIdentityPart(input.birthCity),
+ normalizeIdentityPart(input.birthState),
+ normalizeIdentityPart(input.party),
+ ].join('|')
+
+ return createHash('sha256').update(material, 'utf8').digest('hex')
+}
diff --git a/src/lib/electionResults.ts b/src/lib/electionResults.ts
new file mode 100644
index 00000000..a6e9b87e
--- /dev/null
+++ b/src/lib/electionResults.ts
@@ -0,0 +1,317 @@
+import { bahiaMunicipalities } from '@/lib/bahiaTerritories'
+
+export const ELECTION_YEAR_2022 = 2022 as const
+
+export const ELECTION_OFFICE_OPTIONS = [
+ { label: 'Presidente', value: 'presidente' },
+ { label: 'Governador', value: 'governador' },
+ { label: 'Deputado federal', value: 'deputado_federal' },
+ { label: 'Deputado estadual', value: 'deputado_estadual' },
+] as const
+
+export const ELECTION_OFFICES = ELECTION_OFFICE_OPTIONS.map((o) => o.value)
+export type ElectionOffice = (typeof ELECTION_OFFICES)[number]
+
+export const ELECTION_TURN_OPTIONS = [
+ { label: '1º turno', value: '1' },
+ { label: '2º turno', value: '2' },
+] as const
+
+export const ELECTION_TURNS = ELECTION_TURN_OPTIONS.map((o) => o.value)
+export type ElectionTurn = (typeof ELECTION_TURNS)[number]
+
+export const ELECTION_VOTE_TYPE_OPTIONS = [
+ { label: 'Nominal', value: 'nominal' },
+ { label: 'Legenda', value: 'legenda' },
+] as const
+
+export const ELECTION_VOTE_TYPES = ELECTION_VOTE_TYPE_OPTIONS.map((o) => o.value)
+export type ElectionVoteType = (typeof ELECTION_VOTE_TYPES)[number]
+
+export const ELECTION_ELECTED_BY_OPTIONS = [
+ { label: 'Quociente partidário', value: 'QP' },
+ { label: 'Média', value: 'média' },
+ { label: '2º turno', value: '2º turno' },
+] as const
+
+export const ELECTION_ELECTED_BY = ELECTION_ELECTED_BY_OPTIONS.map((o) => o.value)
+export type ElectionElectedBy = (typeof ELECTION_ELECTED_BY)[number]
+
+export const ELECTION_RUNNING_AGAIN_OPTIONS = [
+ { label: 'Sim', value: 'sim' },
+ { label: 'Não', value: 'nao' },
+ { label: 'Desconhecido', value: 'desconhecido' },
+] as const
+
+export const ELECTION_RUNNING_AGAIN = ELECTION_RUNNING_AGAIN_OPTIONS.map((o) => o.value)
+export type ElectionRunningAgain = (typeof ELECTION_RUNNING_AGAIN)[number]
+
+/** Solla — dep. federal BA, PT, nº 1313 (eleito em 2022). */
+export const SOLLA_CANDIDATE_NUMBER = 1313
+export const LULA_CANDIDATE_NUMBER = 13
+export const JERONIMO_CANDIDATE_NUMBER = 13
+
+/** TSE CD_CARGO → office enum used in collections. */
+export const TSE_CARGO_TO_OFFICE: Readonly> = {
+ '1': 'presidente',
+ '3': 'governador',
+ '6': 'deputado_federal',
+ '7': 'deputado_estadual',
+}
+
+export type ParsedElectedStatus = {
+ elected: boolean
+ electedBy: ElectionElectedBy | null
+}
+
+/**
+ * Map TSE DS_SIT_TOT_TURNO to elected flag + electedBy.
+ * Values observed in 2022 consulta_cand / votacao datasets.
+ * When `turn` is `2` and the candidate is elected, electedBy defaults to `2º turno`
+ * (TSE often writes plain "ELEITO" on the second-turn file).
+ */
+export const parseElectedStatus = (
+ dsSitTotTurno: string | null | undefined,
+ turn?: ElectionTurn | null,
+): ParsedElectedStatus => {
+ const value = (dsSitTotTurno ?? '').trim().toUpperCase()
+ if (!value || value === '#NULO#' || value === 'NÃO ELEITO' || value === 'NAO ELEITO') {
+ return { elected: false, electedBy: null }
+ }
+ if (value.includes('2º TURNO') || value.includes('2O TURNO') || value.includes('2° TURNO')) {
+ // "ELEITO … 2º TURNO" counts as elected; bare "2º TURNO" means advanced only.
+ if (value.startsWith('ELEITO')) {
+ return { elected: true, electedBy: '2º turno' }
+ }
+ return { elected: false, electedBy: null }
+ }
+ if (value.includes('MÉDIA') || value.includes('MEDIA')) {
+ return { elected: true, electedBy: 'média' }
+ }
+ if (value.startsWith('ELEITO')) {
+ return { elected: true, electedBy: turn === '2' ? '2º turno' : 'QP' }
+ }
+ return { elected: false, electedBy: null }
+}
+
+/** Fold accents and case for municipality matching. */
+export const normalizeMunicipalityKey = (value: string): string =>
+ value
+ .normalize('NFD')
+ .replace(/\p{M}/gu, '')
+ .replace(/['’`]/g, '')
+ .replace(/\s+/g, ' ')
+ .trim()
+ .toUpperCase()
+
+const bahiaMunicipalityByKey = new Map(
+ bahiaMunicipalities.map((name) => [normalizeMunicipalityKey(name), name]),
+)
+
+/**
+ * Known TSE NM_MUNICIPIO spellings that diverge from the SEPLAN/SECULT canonical
+ * names in bahiaTerritories (2022 open-data files). Keys are normalizeMunicipalityKey outputs.
+ */
+const TSE_MUNICIPALITY_ALIASES: Readonly> = {
+ // TSE: CAMACÃ → canonical Camacan
+ CAMACA: 'Camacan',
+ // TSE: "DIAS D ÁVILA" (space, no apostrophe) → Dias d'Ávila
+ 'DIAS D AVILA': "Dias d'Ávila",
+ // TSE: MUQUÉM DO SÃO FRANCISCO → Muquém de São Francisco
+ 'MUQUEM DO SAO FRANCISCO': 'Muquém de São Francisco',
+ // TSE: SANTA TEREZINHA → Santa Teresinha
+ 'SANTA TEREZINHA': 'Santa Teresinha',
+}
+
+for (const [aliasKey, canonical] of Object.entries(TSE_MUNICIPALITY_ALIASES)) {
+ if (!bahiaMunicipalityByKey.has(normalizeMunicipalityKey(canonical))) {
+ throw new Error(`TSE alias points to unknown canonical municipality: ${canonical}`)
+ }
+ bahiaMunicipalityByKey.set(aliasKey, canonical)
+}
+
+export class UnknownMunicipalityError extends Error {
+ constructor(public readonly tseName: string) {
+ super(`Município TSE sem mapeamento canônico: "${tseName}"`)
+ this.name = 'UnknownMunicipalityError'
+ }
+}
+
+/** Cache raw TSE NM_MUNICIPIO → canonical (millions of repeated lookups on seed). */
+const municipalityResolveCache = new Map()
+
+/**
+ * Map TSE NM_MUNICIPIO (usually ALL CAPS) to the canonical Bahia municipality name
+ * used by bahiaTerritories / electoralNucleus.city. Fails closed on unknown names.
+ */
+export const canonicalizeMunicipalityName = (tseMunicipalityName: string): string => {
+ const cached = municipalityResolveCache.get(tseMunicipalityName)
+ if (cached) return cached
+
+ const key = normalizeMunicipalityKey(tseMunicipalityName)
+ const canonical = bahiaMunicipalityByKey.get(key)
+ if (!canonical) {
+ throw new UnknownMunicipalityError(tseMunicipalityName)
+ }
+ municipalityResolveCache.set(tseMunicipalityName, canonical)
+ return canonical
+}
+
+export const isElectionTurn = (value: string): value is ElectionTurn =>
+ (ELECTION_TURNS as readonly string[]).includes(value)
+
+export type CandidateVoteRow = {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+ state: string
+ cityCode: string
+ cityName: string
+ zoneNumber: number
+ candidateNumber: number
+ candidateName: string
+ coalition: string | null
+ party: string | null
+ voteType: ElectionVoteType
+ votes: number
+}
+
+/** Winner fields as stored on electionTally (null when no nominal votes in that cell). */
+export type TallyWinner = {
+ winnerCandidateNumber: number | null
+ winnerCandidateName: string | null
+ winnerVotes: number | null
+ winnerCoalition: string | null
+ winnerParty: string | null
+}
+
+type ResolvedWinner = {
+ winnerCandidateNumber: number
+ winnerCandidateName: string
+ winnerVotes: number
+ winnerCoalition: string | null
+ winnerParty: string | null
+}
+
+const EMPTY_WINNER: TallyWinner = {
+ winnerCandidateNumber: null,
+ winnerCandidateName: null,
+ winnerVotes: null,
+ winnerCoalition: null,
+ winnerParty: null,
+}
+
+export const winnerKey = (cityCode: string, zoneNumber: number, office: ElectionOffice, turn: ElectionTurn) =>
+ `${cityCode}|${zoneNumber}|${office}|${turn}`
+
+/**
+ * Compute the local winner (highest nominal votes) for each city+zone+office+turn.
+ * Ties: first seen candidate with the max vote count wins (stable for import order).
+ */
+export const computeWinnersByScope = (
+ votes: readonly CandidateVoteRow[],
+): Map => {
+ const winners = new Map()
+
+ for (const row of votes) {
+ if (row.voteType !== 'nominal' || row.votes <= 0) continue
+ const key = winnerKey(row.cityCode, row.zoneNumber, row.office, row.turn)
+ const current = winners.get(key)
+ if (!current || current.winnerVotes < row.votes) {
+ winners.set(key, {
+ winnerCandidateNumber: row.candidateNumber,
+ winnerCandidateName: row.candidateName,
+ winnerVotes: row.votes,
+ winnerCoalition: row.coalition,
+ winnerParty: row.party,
+ })
+ }
+ }
+
+ return winners
+}
+
+export type TseDetalheApuracaoRow = {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+ state: string
+ cityCode: string
+ cityName: string
+ zoneNumber: number
+ aptos: number
+ comparecimento: number
+ abstencoes: number
+ votosValidos: number
+ votosNominaisValidos: number
+ votosLegenda: number
+ votosBranco: number
+ votosNulo: number
+ votosAnulados: number
+}
+
+export type ElectionTallyRow = TseDetalheApuracaoRow & TallyWinner
+
+export type ElectionCandidateRow = {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+ state: string
+ candidateNumber: number
+ urnaName: string
+ completeName: string | null
+ party: string | null
+ coalition: string | null
+ candidateStatus: string | null
+ elected: boolean
+ electedBy: ElectionElectedBy | null
+ totalVotesState: number
+ identityKey: string
+ runningAgain2026: ElectionRunningAgain
+}
+
+export const mergeTallyWithWinners = (
+ tallies: readonly TseDetalheApuracaoRow[],
+ winners: ReadonlyMap,
+): ElectionTallyRow[] =>
+ tallies.map((tally) => {
+ const winner =
+ winners.get(winnerKey(tally.cityCode, tally.zoneNumber, tally.office, tally.turn)) ??
+ EMPTY_WINNER
+ return { ...tally, ...winner }
+ })
+
+/** Parse a TSE integer cell that may be "#NULO#" / empty. */
+export const parseTseInt = (value: string | undefined | null, fallback = 0): number => {
+ if (value == null) return fallback
+ const trimmed = value.trim()
+ if (!trimmed || trimmed === '#NULO#' || trimmed === '#NE#') return fallback
+ const n = Number(trimmed)
+ return Number.isFinite(n) ? n : fallback
+}
+
+export const parseTseOptionalText = (value: string | undefined | null): string | null => {
+ if (value == null) return null
+ const trimmed = value.trim()
+ if (!trimmed || trimmed === '#NULO#' || trimmed === '#NE#') return null
+ return trimmed
+}
+
+export const officeFromTseCargo = (cdCargo: string): ElectionOffice | null => {
+ const office = TSE_CARGO_TO_OFFICE[cdCargo.trim()]
+ return office ?? null
+}
+
+export const turnFromTse = (nrTurno: string): ElectionTurn | null => {
+ const turn = nrTurno.trim()
+ return isElectionTurn(turn) ? turn : null
+}
+
+export const assertAllCanonicalMunicipalitiesResolvable = (): void => {
+ for (const name of bahiaMunicipalities) {
+ const resolved = canonicalizeMunicipalityName(name)
+ if (resolved !== name) {
+ throw new Error(`canonicalizeMunicipalityName drifted for "${name}" → "${resolved}"`)
+ }
+ }
+}
diff --git a/src/lib/electionResultsBuild.ts b/src/lib/electionResultsBuild.ts
new file mode 100644
index 00000000..5d24c6a4
--- /dev/null
+++ b/src/lib/electionResultsBuild.ts
@@ -0,0 +1,98 @@
+import type {
+ CandidateVoteRow,
+ ElectionCandidateRow,
+ ElectionOffice,
+ TseDetalheApuracaoRow,
+} from '@/lib/electionResults'
+import { ELECTION_OFFICES, ELECTION_YEAR_2022, UnknownMunicipalityError } from '@/lib/electionResults'
+import {
+ applyStateVoteTotals,
+ dedupeCandidates,
+ parseCandidateVoteRow,
+ parseConsultaCandRow,
+ parseDetalheApuracaoRow,
+ type TseCsvRow,
+} from '@/lib/electionResultsParse'
+
+const ALL_OFFICES = new Set(ELECTION_OFFICES)
+const STATE_OFFICES = new Set([
+ 'governador',
+ 'deputado_federal',
+ 'deputado_estadual',
+])
+const PRESIDENT_OFFICE = new Set(['presidente'])
+
+export type BuiltElectionResults = {
+ votes: CandidateVoteRow[]
+ tallies: TseDetalheApuracaoRow[]
+ candidates: ElectionCandidateRow[]
+ unknownMunicipalities: string[]
+}
+
+const collectParsed = (
+ rows: readonly TseCsvRow[],
+ year: number,
+ unknownMunicipalities: Set,
+ parseRow: (row: TseCsvRow) => T | null,
+): T[] => {
+ const out: T[] = []
+ for (const row of rows) {
+ try {
+ const parsed = parseRow(row)
+ if (parsed && parsed.year === year) out.push(parsed)
+ } catch (error) {
+ if (error instanceof UnknownMunicipalityError) {
+ unknownMunicipalities.add(error.tseName)
+ continue
+ }
+ throw error
+ }
+ }
+ return out
+}
+
+/**
+ * Build typed election rows from already-parsed TSE CSV tables.
+ * Safe for fixtures and for the full seed (no I/O).
+ */
+export const buildElectionResultsFromCsvRows = (args: {
+ voteRows: readonly TseCsvRow[]
+ detalheRows: readonly TseCsvRow[]
+ candBaRows: readonly TseCsvRow[]
+ candBrRows: readonly TseCsvRow[]
+ year?: number
+}): BuiltElectionResults => {
+ const year = args.year ?? ELECTION_YEAR_2022
+ const unknownMunicipalities = new Set()
+
+ const votes = collectParsed(args.voteRows, year, unknownMunicipalities, (row) =>
+ parseCandidateVoteRow(row, { stateFilter: 'BA', offices: ALL_OFFICES }),
+ )
+ const tallies = collectParsed(args.detalheRows, year, unknownMunicipalities, (row) =>
+ parseDetalheApuracaoRow(row, { stateFilter: 'BA', offices: ALL_OFFICES }),
+ )
+
+ const candidatesRaw: ElectionCandidateRow[] = []
+ for (const row of args.candBaRows) {
+ const parsed = parseConsultaCandRow(row, {
+ stateFilter: 'BA',
+ offices: STATE_OFFICES,
+ })
+ if (parsed && parsed.year === year) candidatesRaw.push(parsed)
+ }
+ for (const row of args.candBrRows) {
+ const parsed = parseConsultaCandRow(row, {
+ stateFilter: 'BR',
+ offices: PRESIDENT_OFFICE,
+ forceState: 'BA',
+ })
+ if (parsed && parsed.year === year) candidatesRaw.push(parsed)
+ }
+
+ return {
+ votes,
+ tallies,
+ candidates: applyStateVoteTotals(dedupeCandidates(candidatesRaw), votes),
+ unknownMunicipalities: [...unknownMunicipalities].sort(),
+ }
+}
diff --git a/src/lib/electionResultsCsv.ts b/src/lib/electionResultsCsv.ts
new file mode 100644
index 00000000..75362b70
--- /dev/null
+++ b/src/lib/electionResultsCsv.ts
@@ -0,0 +1,22 @@
+import { parse } from 'csv-parse/sync'
+
+import type { TseCsvRow } from '@/lib/electionResultsParse'
+
+/**
+ * Parse a TSE open-data CSV buffer (Latin-1 / ISO-8859-1, `;` separator, quoted fields).
+ */
+export const parseTseCsvBuffer = (buffer: Buffer): TseCsvRow[] => {
+ const text = buffer.toString('latin1')
+ return parse(text, {
+ columns: true,
+ delimiter: ';',
+ relax_quotes: true,
+ relax_column_count: true,
+ skip_empty_lines: true,
+ trim: true,
+ bom: true,
+ }) as TseCsvRow[]
+}
+
+export const parseTseCsvString = (text: string): TseCsvRow[] =>
+ parseTseCsvBuffer(Buffer.from(text, 'utf8'))
diff --git a/src/lib/electionResultsParse.ts b/src/lib/electionResultsParse.ts
new file mode 100644
index 00000000..ce171761
--- /dev/null
+++ b/src/lib/electionResultsParse.ts
@@ -0,0 +1,220 @@
+/** Pure parsers for TSE open-data CSVs (Latin-1, `;`-separated). */
+
+import {
+ canonicalizeMunicipalityName,
+ officeFromTseCargo,
+ parseElectedStatus,
+ parseTseInt,
+ parseTseOptionalText,
+ turnFromTse,
+ type CandidateVoteRow,
+ type ElectionCandidateRow,
+ type ElectionOffice,
+ type ElectionTurn,
+ type TseDetalheApuracaoRow,
+} from '@/lib/electionResults'
+import { computeIdentityKey } from '@/lib/electionCandidateIdentity'
+
+export type TseCsvRow = Record
+
+const cell = (row: TseCsvRow, key: string): string => row[key] ?? ''
+
+type ScopeParseOptions = {
+ /** Uppercase UF (e.g. BA). Compared as-is against SG_UF — normalize at the call site. */
+ stateFilter?: string
+ offices?: ReadonlySet
+}
+
+const matchScope = (
+ row: TseCsvRow,
+ options: ScopeParseOptions,
+): { state: string; office: ElectionOffice; turn: ElectionTurn } | null => {
+ const state = cell(row, 'SG_UF').trim().toUpperCase()
+ if (options.stateFilter && state !== options.stateFilter) return null
+
+ const office = officeFromTseCargo(cell(row, 'CD_CARGO'))
+ if (!office) return null
+ if (options.offices && !options.offices.has(office)) return null
+
+ const turn = turnFromTse(cell(row, 'NR_TURNO'))
+ if (!turn) return null
+
+ return { state, office, turn }
+}
+
+export type ParseVoteOptions = ScopeParseOptions & {
+ /** Skip zero-vote rows (default true — collection requires votes >= 1). */
+ skipZeroVotes?: boolean
+}
+
+/**
+ * Parse a votacao_candidato_munzona row into a CandidateVoteRow.
+ * Returns null when the row is out of scope (wrong UF/cargo) or has zero votes.
+ */
+export const parseCandidateVoteRow = (
+ row: TseCsvRow,
+ options: ParseVoteOptions = {},
+): CandidateVoteRow | null => {
+ const scope = matchScope(row, options)
+ if (!scope) return null
+
+ const votes = parseTseInt(cell(row, 'QT_VOTOS_NOMINAIS'))
+ if ((options.skipZeroVotes ?? true) && votes <= 0) return null
+
+ return {
+ year: parseTseInt(cell(row, 'ANO_ELEICAO')),
+ office: scope.office,
+ turn: scope.turn,
+ state: scope.state,
+ cityCode: cell(row, 'CD_MUNICIPIO').trim(),
+ cityName: canonicalizeMunicipalityName(cell(row, 'NM_MUNICIPIO')),
+ zoneNumber: parseTseInt(cell(row, 'NR_ZONA')),
+ candidateNumber: parseTseInt(cell(row, 'NR_CANDIDATO')),
+ candidateName: parseTseOptionalText(cell(row, 'NM_URNA_CANDIDATO')) ?? '',
+ coalition: parseTseOptionalText(cell(row, 'NM_COLIGACAO')),
+ party: parseTseOptionalText(cell(row, 'SG_PARTIDO')),
+ voteType: 'nominal',
+ votes,
+ }
+}
+
+export type ParseDetalheOptions = ScopeParseOptions
+
+/**
+ * Parse detalhe_votacao_munzona into a tally row (without winner — filled later).
+ */
+export const parseDetalheApuracaoRow = (
+ row: TseCsvRow,
+ options: ParseDetalheOptions = {},
+): TseDetalheApuracaoRow | null => {
+ const scope = matchScope(row, options)
+ if (!scope) return null
+
+ // Prefer QT_VOTOS_VALIDOS when present; else nominais_validos + legenda.
+ const votosNominaisValidos = parseTseInt(
+ cell(row, 'QT_VOTOS_NOMINAIS_VALIDOS') || cell(row, 'QT_VOTOS_NOMINAIS'),
+ )
+ const votosLegenda = parseTseInt(cell(row, 'QT_VOTOS_LEGENDA'))
+ const votosValidos =
+ parseTseInt(cell(row, 'QT_VOTOS_VALIDOS')) || votosNominaisValidos + votosLegenda
+
+ return {
+ year: parseTseInt(cell(row, 'ANO_ELEICAO')),
+ office: scope.office,
+ turn: scope.turn,
+ state: scope.state,
+ cityCode: cell(row, 'CD_MUNICIPIO').trim(),
+ cityName: canonicalizeMunicipalityName(cell(row, 'NM_MUNICIPIO')),
+ zoneNumber: parseTseInt(cell(row, 'NR_ZONA')),
+ aptos: parseTseInt(cell(row, 'QT_APTOS')),
+ comparecimento: parseTseInt(cell(row, 'QT_COMPARECIMENTO')),
+ abstencoes: parseTseInt(cell(row, 'QT_ABSTENCOES')),
+ votosValidos,
+ votosNominaisValidos,
+ votosLegenda,
+ votosBranco: parseTseInt(cell(row, 'QT_VOTOS_BRANCOS')),
+ votosNulo: parseTseInt(cell(row, 'QT_VOTOS_NULOS')),
+ votosAnulados: parseTseInt(
+ cell(row, 'QT_VOTOS_ANULADOS') || cell(row, 'QT_VOTOS_ANULADOS_APU_SEP'),
+ ),
+ }
+}
+
+export type ParseCandidateOptions = ScopeParseOptions & {
+ /** Override state stored (presidente candidacies are national; store BA for our model). */
+ forceState?: string
+}
+
+/**
+ * Parse consulta_cand into ElectionCandidateRow.
+ * President candidates live in the BR file (SG_UF=BR); we store state=BA for the BA campaign model.
+ */
+export const parseConsultaCandRow = (
+ row: TseCsvRow,
+ options: ParseCandidateOptions = {},
+): ElectionCandidateRow | null => {
+ const scope = matchScope(row, options)
+ if (!scope) return null
+
+ const urnaName = parseTseOptionalText(cell(row, 'NM_URNA_CANDIDATO')) ?? ''
+ const party = parseTseOptionalText(cell(row, 'SG_PARTIDO'))
+ const birthCity = parseTseOptionalText(cell(row, 'NM_MUNICIPIO_NASCIMENTO'))
+ const birthState = parseTseOptionalText(cell(row, 'SG_UF_NASCIMENTO'))
+ const { elected, electedBy } = parseElectedStatus(cell(row, 'DS_SIT_TOT_TURNO'), scope.turn)
+
+ return {
+ year: parseTseInt(cell(row, 'ANO_ELEICAO')),
+ office: scope.office,
+ turn: scope.turn,
+ state: (options.forceState ?? scope.state).toUpperCase(),
+ candidateNumber: parseTseInt(cell(row, 'NR_CANDIDATO')),
+ urnaName,
+ completeName: parseTseOptionalText(cell(row, 'NM_CANDIDATO')),
+ party,
+ coalition: parseTseOptionalText(cell(row, 'NM_COLIGACAO')),
+ candidateStatus: parseTseOptionalText(cell(row, 'DS_SITUACAO_CANDIDATURA')),
+ elected,
+ electedBy,
+ totalVotesState: 0,
+ identityKey: computeIdentityKey({ urnaName, birthCity, birthState, party }),
+ runningAgain2026: 'desconhecido',
+ }
+}
+
+export type ScopeKey = {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+}
+
+export const scopeKeyString = (scope: ScopeKey): string =>
+ `${scope.year}|${scope.office}|${scope.turn}`
+
+const candidateAggregateKey = (row: {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+ state: string
+ candidateNumber: number
+}): string =>
+ `${row.year}|${row.office}|${row.turn}|${row.state}|${row.candidateNumber}`
+
+/** Deduplicate candidate rows by unique key, preferring elected=true when colliding. */
+export const dedupeCandidates = (rows: readonly ElectionCandidateRow[]): ElectionCandidateRow[] => {
+ const map = new Map()
+ for (const row of rows) {
+ const key = candidateAggregateKey(row)
+ const existing = map.get(key)
+ if (!existing || (!existing.elected && row.elected)) {
+ map.set(key, row)
+ }
+ }
+ return [...map.values()]
+}
+
+/** Aggregate nominal votes per candidate across all zones → totalVotesState. */
+export const applyStateVoteTotals = (
+ candidates: readonly ElectionCandidateRow[],
+ votes: readonly CandidateVoteRow[],
+): ElectionCandidateRow[] => {
+ const totals = new Map()
+ for (const vote of votes) {
+ const key = candidateAggregateKey(vote)
+ totals.set(key, (totals.get(key) ?? 0) + vote.votes)
+ }
+ return candidates.map((candidate) => ({
+ ...candidate,
+ totalVotesState: totals.get(candidateAggregateKey(candidate)) ?? 0,
+ }))
+}
+
+export const groupByScope = (rows: readonly T[]): Map => {
+ const map = new Map()
+ for (const row of rows) {
+ const key = scopeKeyString(row)
+ const list = map.get(key)
+ if (list) list.push(row)
+ else map.set(key, [row])
+ }
+ return map
+}
diff --git a/src/lib/electionResultsZip.ts b/src/lib/electionResultsZip.ts
new file mode 100644
index 00000000..5f0c9f12
--- /dev/null
+++ b/src/lib/electionResultsZip.ts
@@ -0,0 +1,69 @@
+import { fromBuffer as yauzlFromBuffer } from 'yauzl'
+
+/**
+ * Read a single entry from a ZIP buffer by exact filename or suffix match.
+ * Returns null when no matching entry exists.
+ */
+export const readZipEntry = async (
+ zipBuffer: Buffer,
+ matcher: string | ((fileName: string) => boolean),
+): Promise<{ fileName: string; buffer: Buffer } | null> => {
+ const match =
+ typeof matcher === 'string'
+ ? (name: string) => name === matcher || name.endsWith(`/${matcher}`) || name.endsWith(matcher)
+ : matcher
+
+ return new Promise((resolve, reject) => {
+ yauzlFromBuffer(zipBuffer, { lazyEntries: true }, (err, zipfile) => {
+ if (err || !zipfile) {
+ reject(err ?? new Error('Falha ao abrir ZIP'))
+ return
+ }
+
+ let settled = false
+ const finish = (value: { fileName: string; buffer: Buffer } | null) => {
+ if (settled) return
+ settled = true
+ try {
+ zipfile.close()
+ } catch {
+ // ignore close errors after resolve
+ }
+ resolve(value)
+ }
+
+ zipfile.readEntry()
+ zipfile.on('entry', (entry) => {
+ if (settled) return
+ if (/\/$/.test(entry.fileName) || !match(entry.fileName)) {
+ zipfile.readEntry()
+ return
+ }
+ zipfile.openReadStream(entry, (streamErr, readStream) => {
+ if (streamErr || !readStream) {
+ reject(streamErr ?? new Error(`Falha ao ler ${entry.fileName}`))
+ return
+ }
+ const chunks: Buffer[] = []
+ readStream.on('data', (chunk: Buffer) => chunks.push(chunk))
+ readStream.on('error', reject)
+ readStream.on('end', () => {
+ finish({ fileName: entry.fileName, buffer: Buffer.concat(chunks) })
+ })
+ })
+ })
+ zipfile.on('end', () => {
+ finish(null)
+ })
+ zipfile.on('error', reject)
+ })
+ })
+}
+
+export const downloadToBuffer = async (url: string): Promise => {
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error(`Download falhou (${response.status}): ${url}`)
+ }
+ return Buffer.from(await response.arrayBuffer())
+}
diff --git a/src/migrations/20260718_195854_add_election_results.json b/src/migrations/20260718_195854_add_election_results.json
new file mode 100644
index 00000000..6147a1aa
--- /dev/null
+++ b/src/migrations/20260718_195854_add_election_results.json
@@ -0,0 +1,7709 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "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
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "enum_campaign_user_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'lideranca'"
+ },
+ "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_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": {},
+ "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.electoral_nucleus_tse_zones": {
+ "name": "electoral_nucleus_tse_zones",
+ "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
+ },
+ "zone_number": {
+ "name": "zone_number",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "electoral_nucleus_tse_zones_order_idx": {
+ "name": "electoral_nucleus_tse_zones_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_tse_zones_parent_id_idx": {
+ "name": "electoral_nucleus_tse_zones_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_tse_zones_parent_id_fk": {
+ "name": "electoral_nucleus_tse_zones_parent_id_fk",
+ "tableFrom": "electoral_nucleus_tse_zones",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.electoral_nucleus_voter_profiles": {
+ "name": "electoral_nucleus_voter_profiles",
+ "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
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "age_range": {
+ "name": "age_range",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "income_band": {
+ "name": "income_band",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "occupation": {
+ "name": "occupation",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "local_traits": {
+ "name": "local_traits",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "electoral_nucleus_voter_profiles_order_idx": {
+ "name": "electoral_nucleus_voter_profiles_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_voter_profiles_parent_id_idx": {
+ "name": "electoral_nucleus_voter_profiles_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_voter_profiles_parent_id_fk": {
+ "name": "electoral_nucleus_voter_profiles_parent_id_fk",
+ "tableFrom": "electoral_nucleus_voter_profiles",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.electoral_nucleus_strengths": {
+ "name": "electoral_nucleus_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": {
+ "electoral_nucleus_strengths_order_idx": {
+ "name": "electoral_nucleus_strengths_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_strengths_parent_id_idx": {
+ "name": "electoral_nucleus_strengths_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_strengths_parent_id_fk": {
+ "name": "electoral_nucleus_strengths_parent_id_fk",
+ "tableFrom": "electoral_nucleus_strengths",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.electoral_nucleus_risks": {
+ "name": "electoral_nucleus_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": {
+ "electoral_nucleus_risks_order_idx": {
+ "name": "electoral_nucleus_risks_order_idx",
+ "columns": [
+ {
+ "expression": "_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_risks_parent_id_idx": {
+ "name": "electoral_nucleus_risks_parent_id_idx",
+ "columns": [
+ {
+ "expression": "_parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_risks_parent_id_fk": {
+ "name": "electoral_nucleus_risks_parent_id_fk",
+ "tableFrom": "electoral_nucleus_risks",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "_parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.electoral_nucleus": {
+ "name": "electoral_nucleus",
+ "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
+ },
+ "status": {
+ "name": "status",
+ "type": "enum_electoral_nucleus_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ativo'"
+ },
+ "region": {
+ "name": "region",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "city": {
+ "name": "city",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "neighborhood": {
+ "name": "neighborhood",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "locality": {
+ "name": "locality",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "territory_notes": {
+ "name": "territory_notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_kind": {
+ "name": "organization_kind",
+ "type": "enum_electoral_nucleus_organization_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'territorial'"
+ },
+ "organization_label": {
+ "name": "organization_label",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sector_kind": {
+ "name": "sector_kind",
+ "type": "enum_electoral_nucleus_sector_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "primary_contact_id": {
+ "name": "primary_contact_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_vote_estimate": {
+ "name": "confirmed_vote_estimate",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_vote_estimate_at": {
+ "name": "confirmed_vote_estimate_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_vote_estimate_by_id": {
+ "name": "confirmed_vote_estimate_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation_note": {
+ "name": "confirmation_note",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_vote_estimate": {
+ "name": "proposed_vote_estimate",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_vote_estimate_at": {
+ "name": "proposed_vote_estimate_at",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_vote_estimate_by_id": {
+ "name": "proposed_vote_estimate_by_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_vote_estimate_version": {
+ "name": "proposed_vote_estimate_version",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ticket_alliance_partner_name": {
+ "name": "ticket_alliance_partner_name",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ticket_alliance_office": {
+ "name": "ticket_alliance_office",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ticket_alliance_is_campaign_partner": {
+ "name": "ticket_alliance_is_campaign_partner",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ticket_alliance_notes": {
+ "name": "ticket_alliance_notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_update_at": {
+ "name": "last_update_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": {
+ "electoral_nucleus_name_idx": {
+ "name": "electoral_nucleus_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_slug_idx": {
+ "name": "electoral_nucleus_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_status_idx": {
+ "name": "electoral_nucleus_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_region_idx": {
+ "name": "electoral_nucleus_region_idx",
+ "columns": [
+ {
+ "expression": "region",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_city_idx": {
+ "name": "electoral_nucleus_city_idx",
+ "columns": [
+ {
+ "expression": "city",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_organization_kind_idx": {
+ "name": "electoral_nucleus_organization_kind_idx",
+ "columns": [
+ {
+ "expression": "organization_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_sector_kind_idx": {
+ "name": "electoral_nucleus_sector_kind_idx",
+ "columns": [
+ {
+ "expression": "sector_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_primary_contact_idx": {
+ "name": "electoral_nucleus_primary_contact_idx",
+ "columns": [
+ {
+ "expression": "primary_contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_confirmed_vote_estimate_idx": {
+ "name": "electoral_nucleus_confirmed_vote_estimate_idx",
+ "columns": [
+ {
+ "expression": "confirmed_vote_estimate",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_confirmed_vote_estimate_by_idx": {
+ "name": "electoral_nucleus_confirmed_vote_estimate_by_idx",
+ "columns": [
+ {
+ "expression": "confirmed_vote_estimate_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_proposed_vote_estimate_idx": {
+ "name": "electoral_nucleus_proposed_vote_estimate_idx",
+ "columns": [
+ {
+ "expression": "proposed_vote_estimate",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_proposed_vote_estimate_by_idx": {
+ "name": "electoral_nucleus_proposed_vote_estimate_by_idx",
+ "columns": [
+ {
+ "expression": "proposed_vote_estimate_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_proposed_vote_estimate_version_idx": {
+ "name": "electoral_nucleus_proposed_vote_estimate_version_idx",
+ "columns": [
+ {
+ "expression": "proposed_vote_estimate_version",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_last_update_at_idx": {
+ "name": "electoral_nucleus_last_update_at_idx",
+ "columns": [
+ {
+ "expression": "last_update_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_created_by_idx": {
+ "name": "electoral_nucleus_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_updated_at_idx": {
+ "name": "electoral_nucleus_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_created_at_idx": {
+ "name": "electoral_nucleus_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_primary_contact_id_contact_id_fk": {
+ "name": "electoral_nucleus_primary_contact_id_contact_id_fk",
+ "tableFrom": "electoral_nucleus",
+ "tableTo": "contact",
+ "columnsFrom": [
+ "primary_contact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "electoral_nucleus_confirmed_vote_estimate_by_id_campaign_user_id_fk": {
+ "name": "electoral_nucleus_confirmed_vote_estimate_by_id_campaign_user_id_fk",
+ "tableFrom": "electoral_nucleus",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "confirmed_vote_estimate_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "electoral_nucleus_proposed_vote_estimate_by_id_campaign_user_id_fk": {
+ "name": "electoral_nucleus_proposed_vote_estimate_by_id_campaign_user_id_fk",
+ "tableFrom": "electoral_nucleus",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "proposed_vote_estimate_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "electoral_nucleus_created_by_id_campaign_user_id_fk": {
+ "name": "electoral_nucleus_created_by_id_campaign_user_id_fk",
+ "tableFrom": "electoral_nucleus",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.electoral_nucleus_rels": {
+ "name": "electoral_nucleus_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": {
+ "electoral_nucleus_rels_order_idx": {
+ "name": "electoral_nucleus_rels_order_idx",
+ "columns": [
+ {
+ "expression": "order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_rels_parent_idx": {
+ "name": "electoral_nucleus_rels_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_rels_path_idx": {
+ "name": "electoral_nucleus_rels_path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "electoral_nucleus_rels_campaign_user_id_idx": {
+ "name": "electoral_nucleus_rels_campaign_user_id_idx",
+ "columns": [
+ {
+ "expression": "campaign_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "electoral_nucleus_rels_parent_fk": {
+ "name": "electoral_nucleus_rels_parent_fk",
+ "tableFrom": "electoral_nucleus_rels",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "electoral_nucleus_rels_campaign_user_fk": {
+ "name": "electoral_nucleus_rels_campaign_user_fk",
+ "tableFrom": "electoral_nucleus_rels",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "campaign_user_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
+ },
+ "nucleus_id": {
+ "name": "nucleus_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sector": {
+ "name": "sector",
+ "type": "enum_leadership_sector",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sector_notes": {
+ "name": "sector_notes",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "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
+ },
+ "consent_note": {
+ "name": "consent_note",
+ "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": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_nucleus_idx": {
+ "name": "leadership_nucleus_idx",
+ "columns": [
+ {
+ "expression": "nucleus_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leadership_sector_idx": {
+ "name": "leadership_sector_idx",
+ "columns": [
+ {
+ "expression": "sector",
+ "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": {}
+ },
+ "contact_nucleus_idx": {
+ "name": "contact_nucleus_idx",
+ "columns": [
+ {
+ "expression": "contact_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "nucleus_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "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_nucleus_id_electoral_nucleus_id_fk": {
+ "name": "leadership_nucleus_id_electoral_nucleus_id_fk",
+ "tableFrom": "leadership",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "nucleus_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.nucleus_update": {
+ "name": "nucleus_update",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "nucleus_id": {
+ "name": "nucleus_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "enum_nucleus_update_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'semanal'"
+ },
+ "worked": {
+ "name": "worked",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failed": {
+ "name": "failed",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "needs": {
+ "name": "needs",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_volunteers": {
+ "name": "active_volunteers",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "new_supports": {
+ "name": "new_supports",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "body": {
+ "name": "body",
+ "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": {
+ "nucleus_update_nucleus_idx": {
+ "name": "nucleus_update_nucleus_idx",
+ "columns": [
+ {
+ "expression": "nucleus_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "nucleus_update_author_idx": {
+ "name": "nucleus_update_author_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "nucleus_update_kind_idx": {
+ "name": "nucleus_update_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "nucleus_update_updated_at_idx": {
+ "name": "nucleus_update_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "nucleus_update_created_at_idx": {
+ "name": "nucleus_update_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "nucleus_update_nucleus_id_electoral_nucleus_id_fk": {
+ "name": "nucleus_update_nucleus_id_electoral_nucleus_id_fk",
+ "tableFrom": "nucleus_update",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "nucleus_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "nucleus_update_author_id_campaign_user_id_fk": {
+ "name": "nucleus_update_author_id_campaign_user_id_fk",
+ "tableFrom": "nucleus_update",
+ "tableTo": "campaign_user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "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
+ },
+ "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_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": {
+ "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
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": true
+ },
+ "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_phone_idx": {
+ "name": "contact_phone_idx",
+ "columns": [
+ {
+ "expression": "phone",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "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
+ },
+ "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.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_invite_id": {
+ "name": "campaign_invite_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "electoral_nucleus_id": {
+ "name": "electoral_nucleus_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "leadership_id": {
+ "name": "leadership_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nucleus_update_id": {
+ "name": "nucleus_update_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_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_electoral_nucleus_id_idx": {
+ "name": "payload_locked_documents_rels_electoral_nucleus_id_idx",
+ "columns": [
+ {
+ "expression": "electoral_nucleus_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_nucleus_update_id_idx": {
+ "name": "payload_locked_documents_rels_nucleus_update_id_idx",
+ "columns": [
+ {
+ "expression": "nucleus_update_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_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_electoral_nucleus_fk": {
+ "name": "payload_locked_documents_rels_electoral_nucleus_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "electoral_nucleus",
+ "columnsFrom": [
+ "electoral_nucleus_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_nucleus_update_fk": {
+ "name": "payload_locked_documents_rels_nucleus_update_fk",
+ "tableFrom": "payload_locked_documents_rels",
+ "tableTo": "nucleus_update",
+ "columnsFrom": [
+ "nucleus_update_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
+ },
+ "_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__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
+ }
+ },
+ "enums": {
+ "public.enum_campaign_user_role": {
+ "name": "enum_campaign_user_role",
+ "schema": "public",
+ "values": [
+ "geral",
+ "coordenador",
+ "lideranca"
+ ]
+ },
+ "public.enum_campaign_invite_kind": {
+ "name": "enum_campaign_invite_kind",
+ "schema": "public",
+ "values": [
+ "login",
+ "autopreenchimento"
+ ]
+ },
+ "public.enum_electoral_nucleus_status": {
+ "name": "enum_electoral_nucleus_status",
+ "schema": "public",
+ "values": [
+ "ativo",
+ "arquivado"
+ ]
+ },
+ "public.enum_electoral_nucleus_organization_kind": {
+ "name": "enum_electoral_nucleus_organization_kind",
+ "schema": "public",
+ "values": [
+ "territorial",
+ "associacao",
+ "sindicato",
+ "religioso",
+ "movimento",
+ "categoria_profissional",
+ "outro"
+ ]
+ },
+ "public.enum_electoral_nucleus_sector_kind": {
+ "name": "enum_electoral_nucleus_sector_kind",
+ "schema": "public",
+ "values": [
+ "rural",
+ "religioso",
+ "sindical",
+ "empresarial",
+ "juventude",
+ "saude",
+ "educacao",
+ "cultura",
+ "outro"
+ ]
+ },
+ "public.enum_leadership_sector": {
+ "name": "enum_leadership_sector",
+ "schema": "public",
+ "values": [
+ "religioso",
+ "sindical",
+ "comunitario",
+ "rural",
+ "empresarial",
+ "juventude",
+ "saude",
+ "educacao",
+ "cultura",
+ "outro"
+ ]
+ },
+ "public.enum_leadership_support_status": {
+ "name": "enum_leadership_support_status",
+ "schema": "public",
+ "values": [
+ "engajado",
+ "a_abordar",
+ "em_disputa",
+ "negativo"
+ ]
+ },
+ "public.enum_nucleus_update_kind": {
+ "name": "enum_nucleus_update_kind",
+ "schema": "public",
+ "values": [
+ "semanal",
+ "urgente",
+ "nota"
+ ]
+ },
+ "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_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_payload_jobs_log_task_slug": {
+ "name": "enum_payload_jobs_log_task_slug",
+ "schema": "public",
+ "values": [
+ "inline",
+ "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",
+ "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"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "id": "ee7e91dd-2a73-40a0-b7a9-b58a3a92b897",
+ "prevId": "00000000-0000-0000-0000-000000000000"
+}
\ No newline at end of file
diff --git a/src/migrations/20260718_195854_add_election_results.ts b/src/migrations/20260718_195854_add_election_results.ts
new file mode 100644
index 00000000..24ae7d5b
--- /dev/null
+++ b/src/migrations/20260718_195854_add_election_results.ts
@@ -0,0 +1,153 @@
+import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
+
+export async function up({ db, payload, req }: MigrateUpArgs): Promise {
+ await db.execute(sql`
+ CREATE TYPE "public"."enum_election_tally_office" AS ENUM('presidente', 'governador', 'deputado_federal', 'deputado_estadual');
+ CREATE TYPE "public"."enum_election_tally_turn" AS ENUM('1', '2');
+ CREATE TYPE "public"."enum_election_candidate_vote_office" AS ENUM('presidente', 'governador', 'deputado_federal', 'deputado_estadual');
+ CREATE TYPE "public"."enum_election_candidate_vote_turn" AS ENUM('1', '2');
+ CREATE TYPE "public"."enum_election_candidate_vote_vote_type" AS ENUM('nominal', 'legenda');
+ CREATE TYPE "public"."enum_election_candidate_office" AS ENUM('presidente', 'governador', 'deputado_federal', 'deputado_estadual');
+ CREATE TYPE "public"."enum_election_candidate_turn" AS ENUM('1', '2');
+ CREATE TYPE "public"."enum_election_candidate_elected_by" AS ENUM('QP', 'média', '2º turno');
+ CREATE TYPE "public"."enum_election_candidate_running_again2026" AS ENUM('sim', 'nao', 'desconhecido');
+ CREATE TABLE "election_tally" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "year" numeric NOT NULL,
+ "office" "enum_election_tally_office" NOT NULL,
+ "turn" "enum_election_tally_turn" NOT NULL,
+ "state" varchar DEFAULT 'BA' NOT NULL,
+ "city_code" varchar NOT NULL,
+ "city_name" varchar NOT NULL,
+ "zone_number" numeric NOT NULL,
+ "aptos" numeric NOT NULL,
+ "comparecimento" numeric NOT NULL,
+ "abstencoes" numeric NOT NULL,
+ "votos_validos" numeric NOT NULL,
+ "votos_nominais_validos" numeric NOT NULL,
+ "votos_legenda" numeric NOT NULL,
+ "votos_branco" numeric NOT NULL,
+ "votos_nulo" numeric NOT NULL,
+ "votos_anulados" numeric DEFAULT 0 NOT NULL,
+ "winner_candidate_number" numeric,
+ "winner_candidate_name" varchar,
+ "winner_votes" numeric,
+ "winner_coalition" varchar,
+ "winner_party" varchar,
+ "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
+ "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
+ );
+
+ CREATE TABLE "election_candidate_vote" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "year" numeric NOT NULL,
+ "office" "enum_election_candidate_vote_office" NOT NULL,
+ "turn" "enum_election_candidate_vote_turn" NOT NULL,
+ "state" varchar DEFAULT 'BA' NOT NULL,
+ "city_code" varchar NOT NULL,
+ "city_name" varchar NOT NULL,
+ "zone_number" numeric NOT NULL,
+ "candidate_number" numeric NOT NULL,
+ "candidate_name" varchar NOT NULL,
+ "coalition" varchar,
+ "party" varchar,
+ "vote_type" "enum_election_candidate_vote_vote_type" DEFAULT 'nominal' NOT NULL,
+ "votes" numeric NOT NULL,
+ "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
+ "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
+ );
+
+ CREATE TABLE "election_candidate" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "year" numeric NOT NULL,
+ "office" "enum_election_candidate_office" NOT NULL,
+ "turn" "enum_election_candidate_turn" NOT NULL,
+ "state" varchar DEFAULT 'BA' NOT NULL,
+ "candidate_number" numeric NOT NULL,
+ "urna_name" varchar NOT NULL,
+ "complete_name" varchar,
+ "party" varchar,
+ "coalition" varchar,
+ "candidate_status" varchar,
+ "elected" boolean DEFAULT false,
+ "elected_by" "enum_election_candidate_elected_by",
+ "total_votes_state" numeric,
+ "identity_key" varchar,
+ "running_again2026" "enum_election_candidate_running_again2026" DEFAULT 'desconhecido' NOT NULL,
+ "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
+ "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
+ );
+
+ ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "election_tally_id" integer;
+ ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "election_candidate_vote_id" integer;
+ ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "election_candidate_id" integer;
+ CREATE INDEX "election_tally_year_idx" ON "election_tally" USING btree ("year");
+ CREATE INDEX "election_tally_office_idx" ON "election_tally" USING btree ("office");
+ CREATE INDEX "election_tally_turn_idx" ON "election_tally" USING btree ("turn");
+ CREATE INDEX "election_tally_state_idx" ON "election_tally" USING btree ("state");
+ CREATE INDEX "election_tally_city_code_idx" ON "election_tally" USING btree ("city_code");
+ CREATE INDEX "election_tally_city_name_idx" ON "election_tally" USING btree ("city_name");
+ CREATE INDEX "election_tally_zone_number_idx" ON "election_tally" USING btree ("zone_number");
+ CREATE INDEX "election_tally_updated_at_idx" ON "election_tally" USING btree ("updated_at");
+ CREATE INDEX "election_tally_created_at_idx" ON "election_tally" USING btree ("created_at");
+ CREATE UNIQUE INDEX "year_office_turn_state_cityCode_zoneNumber_idx" ON "election_tally" USING btree ("year","office","turn","state","city_code","zone_number");
+ CREATE INDEX "election_candidate_vote_year_idx" ON "election_candidate_vote" USING btree ("year");
+ CREATE INDEX "election_candidate_vote_office_idx" ON "election_candidate_vote" USING btree ("office");
+ CREATE INDEX "election_candidate_vote_turn_idx" ON "election_candidate_vote" USING btree ("turn");
+ CREATE INDEX "election_candidate_vote_state_idx" ON "election_candidate_vote" USING btree ("state");
+ CREATE INDEX "election_candidate_vote_city_code_idx" ON "election_candidate_vote" USING btree ("city_code");
+ CREATE INDEX "election_candidate_vote_city_name_idx" ON "election_candidate_vote" USING btree ("city_name");
+ CREATE INDEX "election_candidate_vote_zone_number_idx" ON "election_candidate_vote" USING btree ("zone_number");
+ CREATE INDEX "election_candidate_vote_candidate_number_idx" ON "election_candidate_vote" USING btree ("candidate_number");
+ CREATE INDEX "election_candidate_vote_vote_type_idx" ON "election_candidate_vote" USING btree ("vote_type");
+ CREATE INDEX "election_candidate_vote_updated_at_idx" ON "election_candidate_vote" USING btree ("updated_at");
+ CREATE INDEX "election_candidate_vote_created_at_idx" ON "election_candidate_vote" USING btree ("created_at");
+ CREATE UNIQUE INDEX "compound_index_idx" ON "election_candidate_vote" USING btree ("year","office","turn","state","city_code","zone_number","candidate_number","vote_type");
+ CREATE INDEX "election_candidate_year_idx" ON "election_candidate" USING btree ("year");
+ CREATE INDEX "election_candidate_office_idx" ON "election_candidate" USING btree ("office");
+ CREATE INDEX "election_candidate_turn_idx" ON "election_candidate" USING btree ("turn");
+ CREATE INDEX "election_candidate_state_idx" ON "election_candidate" USING btree ("state");
+ CREATE INDEX "election_candidate_candidate_number_idx" ON "election_candidate" USING btree ("candidate_number");
+ CREATE INDEX "election_candidate_elected_idx" ON "election_candidate" USING btree ("elected");
+ CREATE INDEX "election_candidate_identity_key_idx" ON "election_candidate" USING btree ("identity_key");
+ CREATE INDEX "election_candidate_updated_at_idx" ON "election_candidate" USING btree ("updated_at");
+ CREATE INDEX "election_candidate_created_at_idx" ON "election_candidate" USING btree ("created_at");
+ CREATE UNIQUE INDEX "year_office_turn_state_candidateNumber_idx" ON "election_candidate" USING btree ("year","office","turn","state","candidate_number");
+ ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_election_tally_fk" FOREIGN KEY ("election_tally_id") REFERENCES "public"."election_tally"("id") ON DELETE cascade ON UPDATE no action;
+ ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_election_candidate_vote_fk" FOREIGN KEY ("election_candidate_vote_id") REFERENCES "public"."election_candidate_vote"("id") ON DELETE cascade ON UPDATE no action;
+ ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_election_candidate_fk" FOREIGN KEY ("election_candidate_id") REFERENCES "public"."election_candidate"("id") ON DELETE cascade ON UPDATE no action;
+ CREATE INDEX "payload_locked_documents_rels_election_tally_id_idx" ON "payload_locked_documents_rels" USING btree ("election_tally_id");
+ CREATE INDEX "payload_locked_documents_rels_election_candidate_vote_id_idx" ON "payload_locked_documents_rels" USING btree ("election_candidate_vote_id");
+ CREATE INDEX "payload_locked_documents_rels_election_candidate_id_idx" ON "payload_locked_documents_rels" USING btree ("election_candidate_id");`)
+}
+
+export async function down({ db, payload, req }: MigrateDownArgs): Promise {
+ await db.execute(sql`
+ ALTER TABLE "election_tally" DISABLE ROW LEVEL SECURITY;
+ ALTER TABLE "election_candidate_vote" DISABLE ROW LEVEL SECURITY;
+ ALTER TABLE "election_candidate" DISABLE ROW LEVEL SECURITY;
+ DROP TABLE "election_tally" CASCADE;
+ DROP TABLE "election_candidate_vote" CASCADE;
+ DROP TABLE "election_candidate" CASCADE;
+ ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_election_tally_fk";
+
+ ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_election_candidate_vote_fk";
+
+ ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_election_candidate_fk";
+
+ DROP INDEX "payload_locked_documents_rels_election_tally_id_idx";
+ DROP INDEX "payload_locked_documents_rels_election_candidate_vote_id_idx";
+ DROP INDEX "payload_locked_documents_rels_election_candidate_id_idx";
+ ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "election_tally_id";
+ ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "election_candidate_vote_id";
+ ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "election_candidate_id";
+ DROP TYPE "public"."enum_election_tally_office";
+ DROP TYPE "public"."enum_election_tally_turn";
+ DROP TYPE "public"."enum_election_candidate_vote_office";
+ DROP TYPE "public"."enum_election_candidate_vote_turn";
+ DROP TYPE "public"."enum_election_candidate_vote_vote_type";
+ DROP TYPE "public"."enum_election_candidate_office";
+ DROP TYPE "public"."enum_election_candidate_turn";
+ DROP TYPE "public"."enum_election_candidate_elected_by";
+ DROP TYPE "public"."enum_election_candidate_running_again2026";`)
+}
diff --git a/src/migrations/index.ts b/src/migrations/index.ts
index 228fd65d..71d903d8 100644
--- a/src/migrations/index.ts
+++ b/src/migrations/index.ts
@@ -4,6 +4,7 @@ import * as migration_20260715_181058_add_post_and_tag from './20260715_181058_a
import * as migration_20260715_215834_rename_tag_visible_to_hidden from './20260715_215834_rename_tag_visible_to_hidden';
import * as migration_20260716_010420_add_campaign_user from './20260716_010420_add_campaign_user';
import * as migration_20260718_010733_consolidate_campaign_schema from './20260718_010733_consolidate_campaign_schema';
+import * as migration_20260718_195854_add_election_results from './20260718_195854_add_election_results';
export const migrations = [
{
@@ -36,4 +37,10 @@ export const migrations = [
down: migration_20260718_010733_consolidate_campaign_schema.down,
name: '20260718_010733_consolidate_campaign_schema',
},
-];
+ {
+ up: migration_20260718_195854_add_election_results.up,
+ down: migration_20260718_195854_add_election_results.down,
+ name: '20260718_195854_add_election_results',
+ },
+]
+
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 1ff2c59d..fdf8f0ea 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -74,6 +74,9 @@ export interface Config {
electoralNucleus: ElectoralNucleus;
leadership: Leadership;
nucleusUpdate: NucleusUpdate;
+ electionTally: ElectionTally;
+ electionCandidateVote: ElectionCandidateVote;
+ electionCandidate: ElectionCandidate;
media: Media;
petition: Petition;
contact: Contact;
@@ -96,6 +99,9 @@ export interface Config {
electoralNucleus: ElectoralNucleusSelect | ElectoralNucleusSelect;
leadership: LeadershipSelect | LeadershipSelect;
nucleusUpdate: NucleusUpdateSelect | NucleusUpdateSelect;
+ electionTally: ElectionTallySelect | ElectionTallySelect;
+ electionCandidateVote: ElectionCandidateVoteSelect | ElectionCandidateVoteSelect;
+ electionCandidate: ElectionCandidateSelect | ElectionCandidateSelect;
media: MediaSelect | MediaSelect;
petition: PetitionSelect | PetitionSelect;
contact: ContactSelect | ContactSelect;
@@ -461,6 +467,91 @@ export interface NucleusUpdate {
updatedAt: string;
createdAt: string;
}
+/**
+ * Totais oficiais TSE por município e zona (dado público).
+ *
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionTally".
+ */
+export interface ElectionTally {
+ id: number;
+ year: number;
+ office: 'presidente' | 'governador' | 'deputado_federal' | 'deputado_estadual';
+ turn: '1' | '2';
+ state: string;
+ cityCode: string;
+ cityName: string;
+ zoneNumber: number;
+ aptos: number;
+ comparecimento: number;
+ abstencoes: number;
+ votosValidos: number;
+ votosNominaisValidos: number;
+ votosLegenda: number;
+ votosBranco: number;
+ votosNulo: number;
+ votosAnulados: number;
+ winnerCandidateNumber?: number | null;
+ winnerCandidateName?: string | null;
+ winnerVotes?: number | null;
+ winnerCoalition?: string | null;
+ winnerParty?: string | null;
+ updatedAt: string;
+ createdAt: string;
+}
+/**
+ * Votos nominais oficiais TSE por município e zona (dado público).
+ *
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionCandidateVote".
+ */
+export interface ElectionCandidateVote {
+ id: number;
+ year: number;
+ office: 'presidente' | 'governador' | 'deputado_federal' | 'deputado_estadual';
+ turn: '1' | '2';
+ state: string;
+ cityCode: string;
+ cityName: string;
+ zoneNumber: number;
+ candidateNumber: number;
+ candidateName: string;
+ coalition?: string | null;
+ party?: string | null;
+ voteType: 'nominal' | 'legenda';
+ votes: number;
+ updatedAt: string;
+ createdAt: string;
+}
+/**
+ * Registro de candidatura TSE (dado público).
+ *
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionCandidate".
+ */
+export interface ElectionCandidate {
+ id: number;
+ year: number;
+ office: 'presidente' | 'governador' | 'deputado_federal' | 'deputado_estadual';
+ turn: '1' | '2';
+ state: string;
+ candidateNumber: number;
+ urnaName: string;
+ completeName?: string | null;
+ party?: string | null;
+ coalition?: string | null;
+ candidateStatus?: string | null;
+ elected?: boolean | null;
+ electedBy?: ('QP' | 'média' | '2º turno') | null;
+ totalVotesState?: number | null;
+ /**
+ * sha256(nome urna + naturalidade + partido); só dados públicos.
+ */
+ identityKey?: string | null;
+ runningAgain2026: 'sim' | 'nao' | 'desconhecido';
+ updatedAt: string;
+ createdAt: string;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media".
@@ -735,6 +826,18 @@ export interface PayloadLockedDocument {
relationTo: 'nucleusUpdate';
value: number | NucleusUpdate;
} | null)
+ | ({
+ relationTo: 'electionTally';
+ value: number | ElectionTally;
+ } | null)
+ | ({
+ relationTo: 'electionCandidateVote';
+ value: number | ElectionCandidateVote;
+ } | null)
+ | ({
+ relationTo: 'electionCandidate';
+ value: number | ElectionCandidate;
+ } | null)
| ({
relationTo: 'media';
value: number | Media;
@@ -988,6 +1091,79 @@ export interface NucleusUpdateSelect {
updatedAt?: T;
createdAt?: T;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionTally_select".
+ */
+export interface ElectionTallySelect {
+ year?: T;
+ office?: T;
+ turn?: T;
+ state?: T;
+ cityCode?: T;
+ cityName?: T;
+ zoneNumber?: T;
+ aptos?: T;
+ comparecimento?: T;
+ abstencoes?: T;
+ votosValidos?: T;
+ votosNominaisValidos?: T;
+ votosLegenda?: T;
+ votosBranco?: T;
+ votosNulo?: T;
+ votosAnulados?: T;
+ winnerCandidateNumber?: T;
+ winnerCandidateName?: T;
+ winnerVotes?: T;
+ winnerCoalition?: T;
+ winnerParty?: T;
+ updatedAt?: T;
+ createdAt?: T;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionCandidateVote_select".
+ */
+export interface ElectionCandidateVoteSelect {
+ year?: T;
+ office?: T;
+ turn?: T;
+ state?: T;
+ cityCode?: T;
+ cityName?: T;
+ zoneNumber?: T;
+ candidateNumber?: T;
+ candidateName?: T;
+ coalition?: T;
+ party?: T;
+ voteType?: T;
+ votes?: T;
+ updatedAt?: T;
+ createdAt?: T;
+}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "electionCandidate_select".
+ */
+export interface ElectionCandidateSelect {
+ year?: T;
+ office?: T;
+ turn?: T;
+ state?: T;
+ candidateNumber?: T;
+ urnaName?: T;
+ completeName?: T;
+ party?: T;
+ coalition?: T;
+ candidateStatus?: T;
+ elected?: T;
+ electedBy?: T;
+ totalVotesState?: T;
+ identityKey?: T;
+ runningAgain2026?: T;
+ updatedAt?: T;
+ createdAt?: T;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media_select".
diff --git a/src/payload.config.ts b/src/payload.config.ts
index d7b7cd97..8130ee23 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -11,6 +11,9 @@ import { CampaignInvite } from './collections/CampaignInvite'
import { CampaignUser } from './collections/CampaignUser'
import { Consent } from './collections/Consent'
import { Contact } from './collections/Contact'
+import { ElectionCandidate } from './collections/ElectionCandidate'
+import { ElectionCandidateVote } from './collections/ElectionCandidateVote'
+import { ElectionTally } from './collections/ElectionTally'
import { ElectoralNucleus } from './collections/ElectoralNucleus'
import { Leadership } from './collections/Leadership'
import { Media } from './collections/Media'
@@ -46,6 +49,9 @@ export default buildConfig({
ElectoralNucleus,
Leadership,
NucleusUpdate,
+ ElectionTally,
+ ElectionCandidateVote,
+ ElectionCandidate,
Media,
Petition,
Contact,
diff --git a/src/utilities/campaignAccess.ts b/src/utilities/campaignAccess.ts
index 884b623f..6686c492 100644
--- a/src/utilities/campaignAccess.ts
+++ b/src/utilities/campaignAccess.ts
@@ -296,6 +296,13 @@ export const canManageNucleusLifecycle: FieldAccess = async ({ req }) => {
export const canSetDerivedNucleusField: FieldAccess = ({ req }) => isPayloadAdmin(req.user)
+/** Public TSE election data: any authenticated campaign or admin user may read. */
+export const canReadElectionData: Access = ({ req }) =>
+ isPayloadAdmin(req.user) || isCampaignUser(req.user)
+
+/** Election reference data is mutated only by Payload admins (or CLI with overrideAccess). */
+export const canMutateElectionData: Access = ({ req }) => isPayloadAdmin(req.user)
+
type DynamicFind = (args: {
collection: string
depth: number
diff --git a/src/utilities/electionResultsImport.ts b/src/utilities/electionResultsImport.ts
new file mode 100644
index 00000000..32ea34e6
--- /dev/null
+++ b/src/utilities/electionResultsImport.ts
@@ -0,0 +1,206 @@
+import { and, eq } from '@payloadcms/db-postgres/drizzle'
+import type { Payload } from 'payload'
+
+import type {
+ CandidateVoteRow,
+ ElectionCandidateRow,
+ ElectionOffice,
+ ElectionTallyRow,
+ ElectionTurn,
+ TseDetalheApuracaoRow,
+} from '@/lib/electionResults'
+import { computeWinnersByScope, mergeTallyWithWinners } from '@/lib/electionResults'
+import { groupByScope } from '@/lib/electionResultsParse'
+
+const VOTE_TABLE = 'election_candidate_vote'
+const TALLY_TABLE = 'election_tally'
+const CANDIDATE_TABLE = 'election_candidate'
+
+/** ~15 columns × 500 ≈ 7500 bind params — well under PG 65535. */
+const INSERT_CHUNK_SIZE = 500
+
+export type ElectionImportScope = {
+ year: number
+ office: ElectionOffice
+ turn: ElectionTurn
+}
+
+export type ElectionImportBundle = {
+ scope: ElectionImportScope
+ votes: CandidateVoteRow[]
+ tallies: TseDetalheApuracaoRow[]
+ candidates: ElectionCandidateRow[]
+}
+
+export type ElectionImportCounts = {
+ scope: ElectionImportScope
+ votesDeleted: number
+ votesInserted: number
+ talliesDeleted: number
+ talliesInserted: number
+ candidatesDeleted: number
+ candidatesInserted: number
+}
+
+type DrizzleDeleteResult = { rowCount?: number | null }
+
+type DrizzleTx = {
+ delete: (table: unknown) => {
+ where: (condition: unknown) => Promise
+ }
+ insert: (table: unknown) => {
+ values: (rows: Record[]) => Promise
+ }
+}
+
+type PayloadDb = {
+ drizzle: {
+ transaction: (fn: (tx: DrizzleTx) => Promise) => Promise
+ }
+ tables: Record>
+}
+
+const chunk = (rows: T[], size: number): T[][] => {
+ const out: T[][] = []
+ for (let i = 0; i < rows.length; i += size) out.push(rows.slice(i, i + size))
+ return out
+}
+
+const getDb = (payload: Payload): PayloadDb => payload.db as unknown as PayloadDb
+
+const requireTable = (db: PayloadDb, name: string) => {
+ const table = db.tables[name]
+ if (!table) throw new Error(`Tabela drizzle ausente: ${name}. Rode as migrations.`)
+ return table
+}
+
+const scopeWhere = (table: Record, scope: ElectionImportScope) =>
+ and(
+ eq(table.year as never, scope.year),
+ eq(table.office as never, scope.office),
+ eq(table.turn as never, scope.turn),
+ )
+
+const deleteScope = async (
+ tx: DrizzleTx,
+ table: Record,
+ scope: ElectionImportScope,
+): Promise => {
+ // Avoid DELETE … RETURNING id (materializes every deleted row in memory on re-seed).
+ const result = await tx.delete(table).where(scopeWhere(table, scope))
+ return result.rowCount ?? 0
+}
+
+const nowIso = () => new Date().toISOString()
+
+const withTimestamps = >(row: T, now: string) => ({
+ ...row,
+ createdAt: now,
+ updatedAt: now,
+})
+
+/**
+ * Replace all election data for a single (year, office, turn) scope in one transaction:
+ * delete existing rows, then bulk-insert the provided bundle.
+ */
+export const importElectionScope = async (
+ payload: Payload,
+ bundle: ElectionImportBundle,
+): Promise => {
+ const dbAdapter = getDb(payload)
+ const voteTable = requireTable(dbAdapter, VOTE_TABLE)
+ const tallyTable = requireTable(dbAdapter, TALLY_TABLE)
+ const candidateTable = requireTable(dbAdapter, CANDIDATE_TABLE)
+
+ const winners = computeWinnersByScope(bundle.votes)
+ const talliesWithWinners = mergeTallyWithWinners(bundle.tallies, winners)
+ const now = nowIso()
+
+ return dbAdapter.drizzle.transaction(async (tx) => {
+ const votesDeleted = await deleteScope(tx, voteTable, bundle.scope)
+ const talliesDeleted = await deleteScope(tx, tallyTable, bundle.scope)
+ const candidatesDeleted = await deleteScope(tx, candidateTable, bundle.scope)
+
+ for (const batch of chunk(bundle.votes, INSERT_CHUNK_SIZE)) {
+ if (batch.length > 0) {
+ await tx.insert(voteTable).values(batch.map((row) => withTimestamps(row, now)))
+ }
+ }
+
+ for (const batch of chunk(talliesWithWinners, INSERT_CHUNK_SIZE)) {
+ if (batch.length > 0) {
+ await tx.insert(tallyTable).values(
+ batch.map((row: ElectionTallyRow) => withTimestamps(row, now)),
+ )
+ }
+ }
+
+ for (const batch of chunk(bundle.candidates, INSERT_CHUNK_SIZE)) {
+ if (batch.length > 0) {
+ await tx.insert(candidateTable).values(batch.map((row) => withTimestamps(row, now)))
+ }
+ }
+
+ return {
+ scope: bundle.scope,
+ votesDeleted,
+ votesInserted: bundle.votes.length,
+ talliesDeleted,
+ talliesInserted: talliesWithWinners.length,
+ candidatesDeleted,
+ candidatesInserted: bundle.candidates.length,
+ }
+ })
+}
+
+/** Import many scopes sequentially (each scope is its own transaction). */
+export const importElectionBundles = async (
+ payload: Payload,
+ bundles: readonly ElectionImportBundle[],
+): Promise => {
+ const results: ElectionImportCounts[] = []
+ for (const bundle of bundles) {
+ results.push(await importElectionScope(payload, bundle))
+ }
+ return results
+}
+
+/**
+ * Build import bundles from flat parsed arrays, grouping by (year, office, turn).
+ * Candidates without matching vote/tally scopes are still imported under their own scope.
+ */
+export const buildImportBundles = (args: {
+ votes: readonly CandidateVoteRow[]
+ tallies: readonly TseDetalheApuracaoRow[]
+ candidates: readonly ElectionCandidateRow[]
+}): ElectionImportBundle[] => {
+ const votesByScope = groupByScope(args.votes)
+ const talliesByScope = groupByScope(args.tallies)
+ const candidatesByScope = groupByScope(args.candidates)
+
+ const keys = new Set([
+ ...votesByScope.keys(),
+ ...talliesByScope.keys(),
+ ...candidatesByScope.keys(),
+ ])
+
+ return [...keys].map((key) => {
+ const sample =
+ votesByScope.get(key)?.[0] ??
+ talliesByScope.get(key)?.[0] ??
+ candidatesByScope.get(key)?.[0]
+ if (!sample) {
+ throw new Error(`Empty election import scope key: ${key}`)
+ }
+ return {
+ scope: {
+ year: sample.year,
+ office: sample.office,
+ turn: sample.turn,
+ },
+ votes: votesByScope.get(key) ?? [],
+ tallies: talliesByScope.get(key) ?? [],
+ candidates: candidatesByScope.get(key) ?? [],
+ }
+ })
+}
diff --git a/tests/fixtures/tse/consulta_cand_ba_fixture.csv b/tests/fixtures/tse/consulta_cand_ba_fixture.csv
new file mode 100644
index 00000000..6305eb28
--- /dev/null
+++ b/tests/fixtures/tse/consulta_cand_ba_fixture.csv
@@ -0,0 +1,8 @@
+ANO_ELEICAO;NR_TURNO;SG_UF;CD_CARGO;NR_CANDIDATO;NM_URNA_CANDIDATO;NM_CANDIDATO;SG_PARTIDO;NM_COLIGACAO;DS_SITUACAO_CANDIDATURA;DS_SIT_TOT_TURNO;NM_MUNICIPIO_NASCIMENTO;SG_UF_NASCIMENTO
+2022;1;BA;6;1313;JORGE SOLLA;JORGE JOSE SANTOS PEREIRA SOLLA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;ELEITO;SALVADOR;BA
+2022;1;BA;6;2222;RIVAL FEDERAL;RIVAL DA SILVA;PL;PARTIDO LIBERAL;DEFERIDO;#NULO#;FEIRA DE SANTANA;BA
+2022;1;BA;7;13131;ESTADUAL PT;CANDIDATO ESTADUAL;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;#NULO#;SALVADOR;BA
+2022;1;BA;3;13;JERÔNIMO;JERONIMO RODRIGUES SOUZA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;2º TURNO;ACAJUTIBA;BA
+2022;2;BA;3;13;JERÔNIMO;JERONIMO RODRIGUES SOUZA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;ELEITO;ACAJUTIBA;BA
+2022;1;BA;3;45;RIVAL GOV;RIVAL GOVERNADOR;PSDB;COLIGAÇÃO OPOSIÇÃO;DEFERIDO;2º TURNO;SALVADOR;BA
+2022;2;BA;3;45;RIVAL GOV;RIVAL GOVERNADOR;PSDB;COLIGAÇÃO OPOSIÇÃO;DEFERIDO;NÃO ELEITO;SALVADOR;BA
diff --git a/tests/fixtures/tse/consulta_cand_br_fixture.csv b/tests/fixtures/tse/consulta_cand_br_fixture.csv
new file mode 100644
index 00000000..3f29efc1
--- /dev/null
+++ b/tests/fixtures/tse/consulta_cand_br_fixture.csv
@@ -0,0 +1,5 @@
+ANO_ELEICAO;NR_TURNO;SG_UF;CD_CARGO;NR_CANDIDATO;NM_URNA_CANDIDATO;NM_CANDIDATO;SG_PARTIDO;NM_COLIGACAO;DS_SITUACAO_CANDIDATURA;DS_SIT_TOT_TURNO;NM_MUNICIPIO_NASCIMENTO;SG_UF_NASCIMENTO
+2022;1;BR;1;13;LULA;LUIZ INACIO LULA DA SILVA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;2º TURNO;GARANHUNS;PE
+2022;2;BR;1;13;LULA;LUIZ INACIO LULA DA SILVA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;DEFERIDO;ELEITO;GARANHUNS;PE
+2022;1;BR;1;22;BOLSONARO;JAIR MESSIAS BOLSONARO;PL;PARTIDO LIBERAL;DEFERIDO;2º TURNO;CAMPINAS;SP
+2022;2;BR;1;22;BOLSONARO;JAIR MESSIAS BOLSONARO;PL;PARTIDO LIBERAL;DEFERIDO;NÃO ELEITO;CAMPINAS;SP
diff --git a/tests/fixtures/tse/detalhe_votacao_munzona_fixture.csv b/tests/fixtures/tse/detalhe_votacao_munzona_fixture.csv
new file mode 100644
index 00000000..55ac6e70
--- /dev/null
+++ b/tests/fixtures/tse/detalhe_votacao_munzona_fixture.csv
@@ -0,0 +1,9 @@
+ANO_ELEICAO;NR_TURNO;SG_UF;CD_MUNICIPIO;NM_MUNICIPIO;NR_ZONA;CD_CARGO;QT_APTOS;QT_COMPARECIMENTO;QT_ABSTENCOES;QT_VOTOS_VALIDOS;QT_VOTOS_NOMINAIS_VALIDOS;QT_VOTOS_LEGENDA;QT_VOTOS_BRANCOS;QT_VOTOS_NULOS;QT_VOTOS_ANULADOS
+2022;1;BA;38490;SALVADOR;1;6;10000;8000;2000;7500;7300;200;300;200;0
+2022;1;BA;38490;SALVADOR;2;6;9000;7200;1800;6800;6600;200;250;150;0
+2022;1;BA;35150;FEIRA DE SANTANA;10;6;5000;4000;1000;3800;3700;100;120;80;0
+2022;1;BA;38490;SALVADOR;1;7;10000;8000;2000;7400;7200;200;300;300;0
+2022;1;BA;38490;SALVADOR;1;3;10000;8000;2000;7600;7400;200;200;200;0
+2022;2;BA;38490;SALVADOR;1;3;10000;8200;1800;7800;7600;200;200;200;0
+2022;1;BA;38490;SALVADOR;1;1;10000;8000;2000;7700;7500;200;150;150;0
+2022;2;BA;38490;SALVADOR;1;1;10000;8300;1700;7900;7700;200;200;200;0
diff --git a/tests/fixtures/tse/votacao_candidato_munzona_fixture.csv b/tests/fixtures/tse/votacao_candidato_munzona_fixture.csv
new file mode 100644
index 00000000..efb58929
--- /dev/null
+++ b/tests/fixtures/tse/votacao_candidato_munzona_fixture.csv
@@ -0,0 +1,17 @@
+ANO_ELEICAO;NR_TURNO;SG_UF;CD_MUNICIPIO;NM_MUNICIPIO;NR_ZONA;CD_CARGO;NR_CANDIDATO;NM_URNA_CANDIDATO;SG_PARTIDO;NM_COLIGACAO;QT_VOTOS_NOMINAIS;DS_SIT_TOT_TURNO
+2022;1;BA;38490;SALVADOR;1;6;1313;JORGE SOLLA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;1200;ELEITO
+2022;1;BA;38490;SALVADOR;1;6;2222;RIVAL FEDERAL;PL;PARTIDO LIBERAL;800;#NULO#
+2022;1;BA;38490;SALVADOR;2;6;1313;JORGE SOLLA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;900;ELEITO
+2022;1;BA;38490;SALVADOR;2;6;2222;RIVAL FEDERAL;PL;PARTIDO LIBERAL;1100;#NULO#
+2022;1;BA;35150;FEIRA DE SANTANA;10;6;1313;JORGE SOLLA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;500;ELEITO
+2022;1;BA;35150;FEIRA DE SANTANA;10;6;2222;RIVAL FEDERAL;PL;PARTIDO LIBERAL;300;#NULO#
+2022;1;BA;38490;SALVADOR;1;7;13131;ESTADUAL PT;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;400;#NULO#
+2022;1;BA;38490;SALVADOR;1;3;13;JERÔNIMO;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;5000;2º TURNO
+2022;1;BA;38490;SALVADOR;1;3;45;RIVAL GOV;PSDB;COLIGAÇÃO OPOSIÇÃO;4200;2º TURNO
+2022;2;BA;38490;SALVADOR;1;3;13;JERÔNIMO;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;6000;ELEITO
+2022;2;BA;38490;SALVADOR;1;3;45;RIVAL GOV;PSDB;COLIGAÇÃO OPOSIÇÃO;4500;NÃO ELEITO
+2022;1;BA;38490;SALVADOR;1;1;13;LULA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;7000;2º TURNO
+2022;1;BA;38490;SALVADOR;1;1;22;BOLSONARO;PL;PARTIDO LIBERAL;3000;2º TURNO
+2022;2;BA;38490;SALVADOR;1;1;13;LULA;PT;FEDERAÇÃO BRASIL DA ESPERANÇA - FE BRASIL;8000;ELEITO
+2022;2;BA;38490;SALVADOR;1;1;22;BOLSONARO;PL;PARTIDO LIBERAL;3500;NÃO ELEITO
+2022;1;BA;38490;SALVADOR;1;6;9999;ZERO VOTOS;PSD;#NULO#;0;#NULO#
diff --git a/tests/helpers/tseFixtures.ts b/tests/helpers/tseFixtures.ts
new file mode 100644
index 00000000..1613aa37
--- /dev/null
+++ b/tests/helpers/tseFixtures.ts
@@ -0,0 +1,35 @@
+import { readFileSync } from 'node:fs'
+import { join } from 'node:path'
+
+import { parseTseCsvString } from '@/lib/electionResultsCsv'
+import { buildElectionResultsFromCsvRows } from '@/lib/electionResultsBuild'
+import type { BuiltElectionResults } from '@/lib/electionResultsBuild'
+
+const fixtureDir = join(process.cwd(), 'tests/fixtures/tse')
+
+const readFixture = (name: string): string => readFileSync(join(fixtureDir, name), 'utf8')
+
+export const loadTseFixtureResults = (): BuiltElectionResults => {
+ const voteRows = parseTseCsvString(readFixture('votacao_candidato_munzona_fixture.csv'))
+ const detalheRows = parseTseCsvString(readFixture('detalhe_votacao_munzona_fixture.csv'))
+ const candBaRows = parseTseCsvString(readFixture('consulta_cand_ba_fixture.csv'))
+ const candBrRows = parseTseCsvString(readFixture('consulta_cand_br_fixture.csv'))
+
+ return buildElectionResultsFromCsvRows({
+ voteRows,
+ detalheRows,
+ candBaRows,
+ candBrRows,
+ })
+}
+
+/** Known totals from the fixture (after skipping the zero-vote row). */
+export const TSE_FIXTURE_EXPECTED = {
+ sollaVotesTotal: 1200 + 900 + 500,
+ lulaTurn2SalvadorZ1: 8000,
+ jeronimoTurn2SalvadorZ1: 6000,
+ federalWinnerSalvadorZ2: { candidateNumber: 2222, votes: 1100 },
+ federalWinnerSalvadorZ1: { candidateNumber: 1313, votes: 1200 },
+ voteRowCount: 15, // 16 CSV rows minus the zero-vote row
+ tallyRowCount: 8,
+} as const
diff --git a/tests/int/electionResultsImport.int.spec.ts b/tests/int/electionResultsImport.int.spec.ts
new file mode 100644
index 00000000..f42d19f0
--- /dev/null
+++ b/tests/int/electionResultsImport.int.spec.ts
@@ -0,0 +1,186 @@
+// @vitest-environment node
+
+import { afterAll, beforeAll, describe, expect, it } from 'vitest'
+import { getPayload, type Payload } from 'payload'
+
+import config from '@/payload.config'
+import { SOLLA_CANDIDATE_NUMBER } from '@/lib/electionResults'
+import {
+ buildImportBundles,
+ importElectionBundles,
+} from '@/utilities/electionResultsImport'
+import { loadTseFixtureResults, TSE_FIXTURE_EXPECTED } from '../helpers/tseFixtures'
+
+let payload: Payload
+
+const deleteAllElectionData = async () => {
+ for (const collection of [
+ 'electionCandidateVote',
+ 'electionTally',
+ 'electionCandidate',
+ ] as const) {
+ const existing = await payload.find({
+ collection,
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ overrideAccess: true,
+ })
+ for (const doc of existing.docs) {
+ await payload.delete({
+ collection,
+ id: doc.id,
+ overrideAccess: true,
+ })
+ }
+ }
+}
+
+describe('election results import', () => {
+ beforeAll(async () => {
+ payload = await getPayload({ config: await config })
+ await deleteAllElectionData()
+ })
+
+ afterAll(async () => {
+ await deleteAllElectionData()
+ })
+
+ it('imports fixture totals, winners, elected flags, and is idempotent', async () => {
+ const built = loadTseFixtureResults()
+ const bundles = buildImportBundles(built)
+
+ const first = await importElectionBundles(payload, bundles)
+ const second = await importElectionBundles(payload, bundles)
+
+ const firstVotes = first.reduce((sum, c) => sum + c.votesInserted, 0)
+ const secondVotes = second.reduce((sum, c) => sum + c.votesInserted, 0)
+ expect(firstVotes).toBe(TSE_FIXTURE_EXPECTED.voteRowCount)
+ expect(secondVotes).toBe(firstVotes)
+ expect(second.reduce((sum, c) => sum + c.votesDeleted, 0)).toBe(firstVotes)
+
+ const votes = await payload.find({
+ collection: 'electionCandidateVote',
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ overrideAccess: true,
+ })
+ expect(votes.totalDocs).toBe(TSE_FIXTURE_EXPECTED.voteRowCount)
+
+ const sollaVotes = votes.docs
+ .filter(
+ (doc) =>
+ doc.office === 'deputado_federal' &&
+ Number(doc.candidateNumber) === SOLLA_CANDIDATE_NUMBER,
+ )
+ .reduce((sum, doc) => sum + Number(doc.votes), 0)
+ expect(sollaVotes).toBe(TSE_FIXTURE_EXPECTED.sollaVotesTotal)
+
+ const tallies = await payload.find({
+ collection: 'electionTally',
+ where: {
+ and: [
+ { office: { equals: 'deputado_federal' } },
+ { cityCode: { equals: '38490' } },
+ { zoneNumber: { equals: 1 } },
+ { turn: { equals: '1' } },
+ ],
+ },
+ depth: 0,
+ limit: 1,
+ overrideAccess: true,
+ })
+ expect(tallies.docs[0]?.winnerCandidateNumber).toBe(
+ TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ1.candidateNumber,
+ )
+ expect(Number(tallies.docs[0]?.winnerVotes)).toBe(
+ TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ1.votes,
+ )
+
+ const candidates = await payload.find({
+ collection: 'electionCandidate',
+ depth: 0,
+ limit: 0,
+ pagination: false,
+ overrideAccess: true,
+ })
+ const solla = candidates.docs.find(
+ (doc) => doc.office === 'deputado_federal' && Number(doc.candidateNumber) === 1313,
+ )
+ const lula = candidates.docs.find(
+ (doc) =>
+ doc.office === 'presidente' &&
+ Number(doc.candidateNumber) === 13 &&
+ doc.turn === '2',
+ )
+ const jeronimo = candidates.docs.find(
+ (doc) =>
+ doc.office === 'governador' &&
+ Number(doc.candidateNumber) === 13 &&
+ doc.turn === '2',
+ )
+ expect(solla?.elected).toBe(true)
+ expect(lula?.elected).toBe(true)
+ expect(jeronimo?.elected).toBe(true)
+ expect(lula?.identityKey).toBeTruthy()
+ })
+
+ it('allows campaignUser read and denies campaignUser mutation', async () => {
+ // Avoid campaignFixtures here: teqo_test may already include territory migrations
+ // from other worktrees that diverge from this branch's electoralNucleus schema.
+ const campaignUser = await payload.create({
+ collection: 'campaignUser',
+ data: {
+ name: 'Election Access Tester',
+ role: 'geral',
+ email: `election-access-${Date.now()}@example.com`,
+ password: 'Password123!',
+ },
+ overrideAccess: true,
+ })
+
+ try {
+ const readable = await payload.find({
+ collection: 'electionTally',
+ depth: 0,
+ limit: 1,
+ user: campaignUser,
+ overrideAccess: false,
+ })
+ expect(readable.totalDocs).toBeGreaterThan(0)
+
+ await expect(
+ payload.create({
+ collection: 'electionTally',
+ data: {
+ year: 2022,
+ office: 'deputado_federal',
+ turn: '1',
+ state: 'BA',
+ cityCode: '99999',
+ cityName: 'Salvador',
+ zoneNumber: 99,
+ aptos: 1,
+ comparecimento: 1,
+ abstencoes: 0,
+ votosValidos: 1,
+ votosNominaisValidos: 1,
+ votosLegenda: 0,
+ votosBranco: 0,
+ votosNulo: 0,
+ votosAnulados: 0,
+ },
+ user: campaignUser,
+ overrideAccess: false,
+ }),
+ ).rejects.toThrow()
+ } finally {
+ await payload.delete({
+ collection: 'campaignUser',
+ id: campaignUser.id,
+ overrideAccess: true,
+ })
+ }
+ })
+})
diff --git a/tests/unit/electionResults.unit.spec.ts b/tests/unit/electionResults.unit.spec.ts
new file mode 100644
index 00000000..9803304b
--- /dev/null
+++ b/tests/unit/electionResults.unit.spec.ts
@@ -0,0 +1,161 @@
+// @vitest-environment node
+
+import { describe, expect, it } from 'vitest'
+
+import { bahiaMunicipalities } from '@/lib/bahiaTerritories'
+import { computeIdentityKey, normalizeIdentityPart } from '@/lib/electionCandidateIdentity'
+import {
+ assertAllCanonicalMunicipalitiesResolvable,
+ canonicalizeMunicipalityName,
+ computeWinnersByScope,
+ JERONIMO_CANDIDATE_NUMBER,
+ LULA_CANDIDATE_NUMBER,
+ mergeTallyWithWinners,
+ normalizeMunicipalityKey,
+ parseElectedStatus,
+ SOLLA_CANDIDATE_NUMBER,
+ UnknownMunicipalityError,
+ winnerKey,
+} from '@/lib/electionResults'
+import { loadTseFixtureResults, TSE_FIXTURE_EXPECTED } from '../helpers/tseFixtures'
+
+describe('electionResults municipality mapping', () => {
+ it('resolves every canonical Bahia municipality through itself', () => {
+ expect(bahiaMunicipalities).toHaveLength(417)
+ expect(() => assertAllCanonicalMunicipalitiesResolvable()).not.toThrow()
+ })
+
+ it('maps TSE ALL-CAPS names to canonical names', () => {
+ expect(canonicalizeMunicipalityName('SALVADOR')).toBe('Salvador')
+ expect(canonicalizeMunicipalityName('FEIRA DE SANTANA')).toBe('Feira de Santana')
+ expect(canonicalizeMunicipalityName("DIAS D'AVILA")).toBe("Dias d'Ávila")
+ expect(canonicalizeMunicipalityName('XIQUE-XIQUE')).toBe('Xique-Xique')
+ })
+
+ it('maps known TSE spelling aliases to canonical names', () => {
+ expect(canonicalizeMunicipalityName('CAMACÃ')).toBe('Camacan')
+ expect(canonicalizeMunicipalityName('DIAS D ÁVILA')).toBe("Dias d'Ávila")
+ expect(canonicalizeMunicipalityName('MUQUÉM DO SÃO FRANCISCO')).toBe(
+ 'Muquém de São Francisco',
+ )
+ expect(canonicalizeMunicipalityName('SANTA TEREZINHA')).toBe('Santa Teresinha')
+ })
+
+ it('fails closed on unknown municipality names', () => {
+ expect(() => canonicalizeMunicipalityName('CIDADE INEXISTENTE')).toThrow(UnknownMunicipalityError)
+ })
+
+ it('normalizes accent and apostrophe variants to the same key', () => {
+ expect(normalizeMunicipalityKey("Dias d'Ávila")).toBe(normalizeMunicipalityKey("DIAS D'AVILA"))
+ })
+})
+
+describe('electionResults elected status', () => {
+ it('maps DS_SIT_TOT_TURNO variants', () => {
+ expect(parseElectedStatus('ELEITO')).toEqual({ elected: true, electedBy: 'QP' })
+ expect(parseElectedStatus('ELEITO POR MÉDIA')).toEqual({ elected: true, electedBy: 'média' })
+ expect(parseElectedStatus('ELEITO EM 2º TURNO')).toEqual({
+ elected: true,
+ electedBy: '2º turno',
+ })
+ expect(parseElectedStatus('2º TURNO')).toEqual({ elected: false, electedBy: null })
+ expect(parseElectedStatus('NÃO ELEITO')).toEqual({ elected: false, electedBy: null })
+ expect(parseElectedStatus('#NULO#')).toEqual({ elected: false, electedBy: null })
+ })
+})
+
+describe('electionCandidateIdentity', () => {
+ it('is stable for equivalent normalized inputs', () => {
+ const a = computeIdentityKey({
+ urnaName: 'Jorge Solla',
+ birthCity: 'Salvador',
+ birthState: 'BA',
+ party: 'PT',
+ })
+ const b = computeIdentityKey({
+ urnaName: 'JORGE SOLLA',
+ birthCity: 'SALVADOR',
+ birthState: 'ba',
+ party: 'pt',
+ })
+ expect(a).toBe(b)
+ expect(a).toHaveLength(64)
+ })
+
+ it('changes when party changes', () => {
+ const base = {
+ urnaName: 'LULA',
+ birthCity: 'GARANHUNS',
+ birthState: 'PE',
+ party: 'PT',
+ }
+ expect(computeIdentityKey(base)).not.toBe(computeIdentityKey({ ...base, party: 'PL' }))
+ })
+
+ it('strips accents in identity parts', () => {
+ expect(normalizeIdentityPart('Jerônimo')).toBe('JERONIMO')
+ })
+})
+
+describe('electionResults fixture parse + winners', () => {
+ const built = loadTseFixtureResults()
+
+ it('builds expected vote totals and skips zero-vote rows', () => {
+ expect(built.unknownMunicipalities).toEqual([])
+ expect(built.votes).toHaveLength(TSE_FIXTURE_EXPECTED.voteRowCount)
+ expect(built.tallies).toHaveLength(TSE_FIXTURE_EXPECTED.tallyRowCount)
+
+ const sollaVotes = built.votes
+ .filter(
+ (row) =>
+ row.office === 'deputado_federal' && row.candidateNumber === SOLLA_CANDIDATE_NUMBER,
+ )
+ .reduce((sum, row) => sum + row.votes, 0)
+ expect(sollaVotes).toBe(TSE_FIXTURE_EXPECTED.sollaVotesTotal)
+ })
+
+ it('marks Solla, Lula and Jerônimo as elected', () => {
+ const solla = built.candidates.find(
+ (c) => c.office === 'deputado_federal' && c.candidateNumber === SOLLA_CANDIDATE_NUMBER,
+ )
+ const lula = built.candidates.find(
+ (c) =>
+ c.office === 'presidente' &&
+ c.candidateNumber === LULA_CANDIDATE_NUMBER &&
+ c.turn === '2',
+ )
+ const jeronimo = built.candidates.find(
+ (c) =>
+ c.office === 'governador' &&
+ c.candidateNumber === JERONIMO_CANDIDATE_NUMBER &&
+ c.turn === '2',
+ )
+
+ expect(solla?.elected).toBe(true)
+ expect(lula?.elected).toBe(true)
+ expect(lula?.electedBy).toBe('2º turno')
+ expect(jeronimo?.elected).toBe(true)
+ expect(jeronimo?.electedBy).toBe('2º turno')
+ expect(lula?.state).toBe('BA')
+ })
+
+ it('computes local winners per city+zone', () => {
+ const winners = computeWinnersByScope(built.votes)
+ const z1 = winners.get(winnerKey('38490', 1, 'deputado_federal', '1'))
+ const z2 = winners.get(winnerKey('38490', 2, 'deputado_federal', '1'))
+ expect(z1?.winnerCandidateNumber).toBe(
+ TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ1.candidateNumber,
+ )
+ expect(z1?.winnerVotes).toBe(TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ1.votes)
+ expect(z2?.winnerCandidateNumber).toBe(
+ TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ2.candidateNumber,
+ )
+ expect(z2?.winnerVotes).toBe(TSE_FIXTURE_EXPECTED.federalWinnerSalvadorZ2.votes)
+
+ const merged = mergeTallyWithWinners(built.tallies, winners)
+ const federalZ1 = merged.find(
+ (t) => t.office === 'deputado_federal' && t.zoneNumber === 1 && t.cityCode === '38490',
+ )
+ expect(federalZ1?.winnerCandidateName).toBe('JORGE SOLLA')
+ })
+})