All files / scripts check-election-tier.js

95% Statements 38/40
95.45% Branches 21/22
100% Functions 3/3
94.59% Lines 35/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                1x 1x               11x 11x 11x 11x   11x     11x 3x 8x 2x 6x 2x 4x 2x     2x     11x             29x 22x 15x 8x             1x 1x 1x 1x 1x   1x 1x 1x 1x       1x 1x 1x 1x 1x                  
#!/usr/bin/env node
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * Election-imminent tier checker for the scheduler workflow.
 *
 * Self-contained script (no heavy transitive dependencies) that computes the
 * current election imminence tier and determines whether the news-election-cycle
 * workflow should be dispatched based on the current day of the week and tier.
 *
 * Tier → dispatch schedule:
 *   NONE  — no auto-dispatch (annual cron + manual only)
 *   T-180 — weekly: Monday only
 *   T-90  — twice-weekly: Monday and Thursday
 *   T-30  — daily: every day
 *
 * Post-election guard: once daysToElection < 0 (election has started), no
 * dispatch occurs — prevents runaway daily dispatches after election day.
 *
 * Outputs (for GitHub Actions):
 *   tier=<NONE|T-180|T-90|T-30>
 *   days_to_election=<number>
 *   should_dispatch=<true|false>
 *
 * On any error, writes tier=NONE and should_dispatch=false (fail-closed).
 */
 
import { appendFileSync } from 'node:fs';
 
// ─── Inlined constants (from src/constants/config.ts) ───────────────────────
// Kept in sync with EP_NEXT_ELECTION_START / EP_NEXT_ELECTION_END.
const EP_NEXT_ELECTION_START = '2029-06-04';
const EP_NEXT_ELECTION_END = '2029-06-09';
 
/**
 * Compute the election imminence tier.
 * Mirrors getElectionCalendarContext() from src/mcp/ep-mcp-client.ts but
 * without transitive dependencies (MCPConnection, ProcedureSeenCache, etc.).
 */
function computeElectionTier(referenceDate) {
  const ref = referenceDate || new Date();
  const electionStart = new Date(EP_NEXT_ELECTION_START + 'T00:00:00Z');
  const diffMs = electionStart.getTime() - ref.getTime();
  const msPerDay = 1000 * 60 * 60 * 24;
  const daysToElection =
    diffMs >= 0 ? Math.ceil(diffMs / msPerDay) : Math.floor(diffMs / msPerDay);
 
  let tier;
  if (daysToElection > 180) {
    tier = 'NONE';
  } else if (daysToElection > 90) {
    tier = 'T-180';
  } else if (daysToElection > 30) {
    tier = 'T-90';
  } else if (daysToElection >= 0) {
    tier = 'T-30';
  } else {
    // Post-election: no auto-dispatch
    tier = 'NONE';
  }
 
  return { tier, daysToElection };
}
 
/**
 * Determine if the workflow should dispatch based on tier and day-of-week.
 */
function shouldDispatchForTier(tier, dayOfWeek) {
  if (tier === 'T-30') return true;
  if (tier === 'T-90') return dayOfWeek === 1 || dayOfWeek === 4;
  if (tier === 'T-180') return dayOfWeek === 1;
  return false;
}
 
/**
 * Write outputs to $GITHUB_OUTPUT (or log if not in CI).
 */
function writeOutputs(tier, days, shouldDispatch) {
  const outputFile = process.env.GITHUB_OUTPUT;
  Eif (outputFile) {
    appendFileSync(outputFile, `tier=${tier}\n`);
    appendFileSync(outputFile, `days_to_election=${days}\n`);
    appendFileSync(outputFile, `should_dispatch=${shouldDispatch}\n`);
  }
  console.log(`Election tier: ${tier}`);
  console.log(`Days to election: ${days}`);
  console.log(`Day of week (UTC): ${new Date().getUTCDay()}`);
  console.log(`Should dispatch: ${shouldDispatch}`);
}
 
// ─── Main ───────────────────────────────────────────────────────────────────
try {
  const { tier, daysToElection } = computeElectionTier();
  const dayOfWeek = new Date().getUTCDay();
  const dispatch = shouldDispatchForTier(tier, dayOfWeek);
  writeOutputs(tier, daysToElection, dispatch);
} catch (err) {
  // Fail-closed: write safe defaults so the workflow never dispatches on error
  console.error('Error computing election tier:', err.message);
  writeOutputs('NONE', -1, false);
}
 
// Exported for testing
export { computeElectionTier, shouldDispatchForTier };