teqo-backup/scripts/cityReportSnapshot.mjs
Francisco Solla 321ac7b872
Some checks failed
Deploy / preflight (push) Has been cancelled
Deploy / verify (push) Has been cancelled
Deploy / deploy staging (teqo-staging) (push) Has been cancelled
Deploy / requeue when main advanced (push) Has been cancelled
Deploy / deploy production (teqo-1313) (push) Has been cancelled
feat(relatorio-cidade): foca o relatório na mobilização do 2º turno
A federal foi decidida em 04/10; o documento de cidade passa a servir
inteiro à eleição de Lula. Saem conta eleitoral/cadeira de Solla,
concorrentes e pré-candidatos, emendas e entregas (sem fetch do Portal
da Transparência no builder), acervo de falas, panorama regional e
sinais/demandas; demografia, economia e transporte ficam como contexto
da visita.

A página 1 vira só mobilização: KPIs oficiais do artefato TSE 2026
(Lula/Δ, X₁/X₂, Solla), papel determinístico da cidade, quem mobiliza,
movimento local do 2º turno, riscos e o painel "O que pedir e falar"
(pedido Lula 13; guardrail de votos em jogo no lugar do defeso).

O checklist cai de 10 para 8 itens (emendas_web e quem_investe saem;
relacao_campo/disputa_local reancorados no 2º turno); listas
preCandidates/investments/emendasIndicators saem do contrato (arquivos
antigos seguem aceitos em replay). O snapshot enxuga para rede +
conjuntura + demografia (cityReportTerritory.mjs removido).

Prova: unit 5.232 verdes, int do snapshot sem PII e sem os grupos
aposentados, lint/format/typecheck/knip limpos e rebuild da fixture de
Cruz das Almas (9 seções, 16 páginas contra 21, página 1 fechando sem
fallback).
2026-10-10 16:57:20 -03:00

109 lines
3.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Snapshot composer for the city report (C163).
*
* Runs on the homeserver against production, read-only: composes the same
* loaders the `/campanha` detail/dossier use (no parallel source of truth),
* projects out PII and fields the report does not need, and returns plain
* JSON. The render step never touches the database again.
*
* Since the 2026-10-10 refocus the report serves the 2º turno (Lula × Flávio):
* the snapshot carries the network (leaderships, advisors, pledges), the base
* strategy (conjuncture) and the IBGE demographics — the official 2º-turno
* numbers come from the committed artifact, not from the database.
*
* Integration layer, not a pure lib: it imports Payload-coupled utilities and
* is exercised by `tests/int/cityReportSnapshot.int.spec.ts`.
*/
import { campaignRoleLabels } from '../src/utilities/campaignUserProfile.ts'
import { loadMunicipalityDossierData } from '../src/utilities/municipality/municipalityDossierData.ts'
import {
getMunicipalityDetailViewModel,
resolveAccessibleMunicipalityContext,
} from '../src/utilities/municipality/municipalityPageData.ts'
import { loadAdvisorSummaries } from '../src/utilities/municipality/municipalityViewModels.ts'
/**
* Composes the snapshot. `actor` must be a real `campaignUser` doc (the CLI
* resolves and fails closed); loaders run with `overrideAccess: false` so the
* snapshot is exactly what that role may read.
*
* @param {object} params
* @param {import('payload').Payload} params.payload
* @param {import('../src/payload-types.ts').CampaignUser} params.actor
* @param {string} params.slug
* @param {string} [params.readAt]
* @param {string | null} [params.codeSha]
* @param {string | null} [params.database]
*/
export const composeCityReportSnapshot = async ({
payload,
actor,
slug,
readAt = new Date().toISOString(),
codeSha = null,
database = null,
}) => {
const context = await resolveAccessibleMunicipalityContext(payload, actor, slug)
const detail = await getMunicipalityDetailViewModel(payload, context, actor)
const dossier = await loadMunicipalityDossierData(payload, actor, detail)
const advisors = await loadAdvisorSummaries(payload, actor, detail.advisorIDs)
const strategy = detail.strategy
return {
meta: {
source: 'Base Teqo (produção, leitura read-only)',
readAt,
readOnly: true,
database,
codeSha,
actorId: actor.id,
actorName: actor.name ?? null,
actorRole: actor.role,
actorRoleLabel: campaignRoleLabels[actor.role] ?? actor.role,
},
municipality: {
id: context.id,
slug: context.slug,
name: detail.name,
kind: detail.kind,
region: detail.region,
ibgeCode: detail.ibgeCode,
},
pledges: {
declaredTotal: dossier.pledgeAggregate.declaredTotal,
lastPledgeAt: dossier.pledgeAggregate.lastPledgeAt,
},
leaderships: {
totalCount: dossier.leaderships.totalCount,
rows: dossier.leaderships.rows.map((row) => ({
id: row.id,
name: row.name,
supportStatus: row.supportStatus,
organizationNames: row.organizationNames,
hasAppAccess: row.hasAppAccess,
updatedAt: row.updatedAt,
})),
},
advisors: advisors.map((advisor) => ({ id: advisor.id, name: advisor.name })),
conjuncture: strategy
? {
priority: strategy.priority,
politicalTrend: strategy.politicalTrend,
engagementLevel: strategy.engagementLevel,
levelNote: strategy.levelNote,
strengths: strategy.strengths,
risks: strategy.risks,
dobradinhaNotes: strategy.dobradinhaNotes,
nextSteps: strategy.nextSteps,
stateDeputies: strategy.stateDeputies.map((deputy) => ({
id: deputy.id,
name: deputy.name,
party: deputy.party,
})),
}
: null,
demographics: dossier.demographics,
}
}