diff --git a/src/lib/suggestionCatalog.ts b/src/lib/suggestionCatalog.ts index ca931ade..c55dcdc6 100644 --- a/src/lib/suggestionCatalog.ts +++ b/src/lib/suggestionCatalog.ts @@ -95,8 +95,8 @@ export type MunicipalityTriggerInput = { coverageRatio: number | null coverageDeficit: number - /** Adversary signal within the window, if any (P1 variant) — presence reads as confirmed. */ - adversarySignal: { present: true } | null + /** Adversary signal within the window (P1 variant) — presence reads as confirmed. */ + hasAdversarySignal: boolean /** Any non-draft activity recently held or upcoming (the "agenda" leg of P1). */ hasRecentOrUpcomingActivity: boolean /** Activities `realizado` within the effort window (K-A's "esforço no ciclo"). */ @@ -314,7 +314,7 @@ const patternP1: SuggestionPattern = { evaluate: (input) => { if (input.territorialClass !== 'reduto' || !input.inCoreBlock) return null - const threatened = input.adversarySignal !== null + const threatened = input.hasAdversarySignal const own2018 = input.ownVotesByYear[YEAR_2018] ?? 0 const own2022 = input.ownVotesByYear[YEAR_2022] ?? 0 const dropped = own2018 > 0 && own2022 <= own2018 * SUGGESTION_ANCHORS.voteDropRatio @@ -339,7 +339,7 @@ const patternP1: SuggestionPattern = { } if (dormant) factors.push(`${signalAgeFactor(input.lastSignalAgeDays)}, sem agenda no período`) - const triageLevel: SuggestionTriageLevel = input.adversarySignal ? 1 : 3 + const triageLevel: SuggestionTriageLevel = input.hasAdversarySignal ? 1 : 3 return { patternId: 'P1', triageLevel, factors } }, } diff --git a/src/utilities/municipality/municipalityTriggers.ts b/src/utilities/municipality/municipalityTriggers.ts index 8ff58fce..0ed1c3cf 100644 --- a/src/utilities/municipality/municipalityTriggers.ts +++ b/src/utilities/municipality/municipalityTriggers.ts @@ -284,11 +284,11 @@ export const loadMunicipalitySuggestions = async ( ]) // Any adversary signal in the window reads as confirmed (B62: sinal = tipo + texto). - const adversarySignalByMunicipality = new Map() + const adversarySignalMunicipalityIDs = new Set() for (const signal of signals.docs) { const id = relationshipId(signal.municipality) if (id === null) continue - adversarySignalByMunicipality.set(id, { present: true }) + adversarySignalMunicipalityIDs.add(id) } const agendaByMunicipality = new Map() @@ -369,7 +369,7 @@ export const loadMunicipalitySuggestions = async ( lastPledgeAgeDays: municipalitySignalAgeInDays(pledgeAggregate.lastPledgeAt, now), coverageRatio: coverage.coverageRatio, coverageDeficit: coverage.deficit, - adversarySignal: adversarySignalByMunicipality.get(municipality.id) ?? null, + hasAdversarySignal: adversarySignalMunicipalityIDs.has(municipality.id), hasRecentOrUpcomingActivity: agenda?.hasAgenda ?? false, completedActivityCount: agenda?.completedCount ?? 0, } diff --git a/tests/e2e/campaignMunicipalities.e2e.spec.ts b/tests/e2e/campaignMunicipalities.e2e.spec.ts index 48feed19..e6d36d1d 100644 --- a/tests/e2e/campaignMunicipalities.e2e.spec.ts +++ b/tests/e2e/campaignMunicipalities.e2e.spec.ts @@ -301,11 +301,9 @@ test.describe('Municípios — jornadas por papel', () => { await expect(page.getByText('Cadastre apoiadores pelo celular.')).toBeVisible() // The leader redirect aborts `goto`'s load event (ERR_ABORTED); the - // redirect itself is the assertion, so navigate and wait for the target. - await Promise.all([ - page.waitForURL(`${campaign.baseURL}/campanha/contatos`), - page.goto(`${campaign.baseURL}/campanha/municipios`).catch(() => {}), - ]) + // redirect itself is the assertion, and `toHaveURL` retries onto it. + await page.goto(`${campaign.baseURL}/campanha/municipios`).catch(() => {}) + await expect(page).toHaveURL(`${campaign.baseURL}/campanha/contatos`) const supporterName = fixtures.value('Apoiador Liderança') const supporterPhone = fixtures.phone() diff --git a/tests/unit/suggestionCatalog.unit.spec.ts b/tests/unit/suggestionCatalog.unit.spec.ts index e62adbf1..2997bd8b 100644 --- a/tests/unit/suggestionCatalog.unit.spec.ts +++ b/tests/unit/suggestionCatalog.unit.spec.ts @@ -42,7 +42,7 @@ const input = (overrides: Partial = {}): MunicipalityT lastPledgeAgeDays: 3, coverageRatio: 0.6, coverageDeficit: 400, - adversarySignal: null, + hasAdversarySignal: false, hasRecentOrUpcomingActivity: false, completedActivityCount: 0, ...overrides, @@ -93,7 +93,7 @@ describe('P1 — reduto dormente ou ameaçado', () => { }) it('escalates to level 1 on any adversary signal — presence reads as confirmed', () => { - expect(evaluate('P1', reduto({ adversarySignal: { present: true } }))?.triageLevel).toBe(1) + expect(evaluate('P1', reduto({ hasAdversarySignal: true }))?.triageLevel).toBe(1) }) it('fires on a 2018→2022 vote drop even with a fresh signal', () => { @@ -315,7 +315,7 @@ describe('evaluateMunicipalityTriggers — ordering and silence', () => { territorialClass: 'reduto', inCoreBlock: true, lqByYear: { 2014: 2.5, 2018: 2.6, 2022: 2.4 }, - adversarySignal: { present: true }, + hasAdversarySignal: true, coverageRatio: 1.2, }), )