All files / src/mcp/ep election-calendar.ts

100% Statements 15/15
100% Branches 12/12
100% Functions 1/1
100% Lines 15/15

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                                                                                                        13x 13x 13x 13x   13x     13x 2x 11x 3x 8x 3x   5x     13x 13x   13x              
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module MCP/ep/election-calendar
 * @description Election calendar context for EP10 → EP11 transition analysis.
 */
 
import {
  EP_NEXT_ELECTION_START,
  EP_NEXT_ELECTION_END,
  EP_CURRENT_TERM,
  EP_NEXT_TERM,
} from '../../constants/config.js';
 
/**
 * Election imminence tier derived from days-to-election.
 * Matches the T-180 / T-90 / T-30 tiers defined in
 * `analysis/methodologies/electoral-cycle-methodology.md`.
 */
export type ElectionImminentTier = 'NONE' | 'T-180' | 'T-90' | 'T-30';
 
/**
 * Context about the upcoming EP election window, used by long-horizon article
 * workflows (`news-quarter-ahead`, `news-year-ahead`, `news-term-outlook`,
 * `news-election-cycle`) to calibrate electoral-cycle analysis depth.
 */
export interface ElectionCalendarContext {
  /** Current or upcoming parliamentary term identifier */
  termId: 'EP10' | 'EP11';
  /** Start/end ISO dates of the next election window */
  nextElectionWindow: { start: string; end: string };
  /** Days until the election window starts (negative if window already started) */
  daysToElection: number;
  /** Imminence tier based on days-to-election */
  electionImminentTier: ElectionImminentTier;
}
 
/**
 * Compute the election calendar context for the EP10 → EP11 transition.
 * Returns deterministic output for any reference date (defaults to `new Date()`).
 *
 * Tier mapping (per `analysis/methodologies/electoral-cycle-methodology.md`):
 * - `daysToElection > 180`  → `NONE`
 * - `180 ≥ d > 90`          → `T-180`
 * - `90 ≥ d > 30`           → `T-90`
 * - `30 ≥ d`                → `T-30`
 *
 * @param referenceDate - Date to compute from (default: now)
 * @returns Election calendar context with term, window, days, and tier
 */
export function getElectionCalendarContext(referenceDate?: Date): ElectionCalendarContext {
  const ref = referenceDate ?? new Date();
  const electionStart = new Date(EP_NEXT_ELECTION_START + 'T00:00:00Z');
  const diffMs = electionStart.getTime() - ref.getTime();
  const millisecondsPerDay = 1000 * 60 * 60 * 24;
  const daysToElection =
    diffMs >= 0 ? Math.ceil(diffMs / millisecondsPerDay) : Math.floor(diffMs / millisecondsPerDay);
 
  let electionImminentTier: ElectionImminentTier;
  if (daysToElection > 180) {
    electionImminentTier = 'NONE';
  } else if (daysToElection > 90) {
    electionImminentTier = 'T-180';
  } else if (daysToElection > 30) {
    electionImminentTier = 'T-90';
  } else {
    electionImminentTier = 'T-30';
  }
 
  const electionEnd = new Date(EP_NEXT_ELECTION_END + 'T23:59:59Z');
  const termId = ref.getTime() > electionEnd.getTime() ? EP_NEXT_TERM : EP_CURRENT_TERM;
 
  return {
    termId,
    nextElectionWindow: { start: EP_NEXT_ELECTION_START, end: EP_NEXT_ELECTION_END },
    daysToElection,
    electionImminentTier,
  };
}