All files / generators/strategies breaking-news-strategy.ts

100% Statements 12/12
100% Branches 2/2
100% Functions 3/3
100% Lines 12/12

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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                              8x                                                   13x   13x                                 3x 1x     3x           3x                     6x                                   6x 6x 6x                     8x  
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Generators/Strategies/BreakingNewsStrategy
 * @description Article strategy for the Breaking News article type.
 * Fetches OSINT intelligence signals (voting anomalies, coalition dynamics,
 * voting report) and renders a real-time alert article.
 */
 
import type { EuropeanParliamentMCPClient } from '../../mcp/ep-mcp-client.js';
import { ArticleCategory } from '../../types/index.js';
import type { LanguageCode } from '../../types/index.js';
import { BREAKING_NEWS_TITLES, getLocalizedString } from '../../constants/languages.js';
import {
  fetchVotingAnomalies,
  fetchCoalitionDynamics,
  fetchVotingReport,
} from '../pipeline/fetch-stage.js';
import { buildBreakingNewsContent } from '../breaking-content.js';
import type { ArticleStrategy, ArticleData, ArticleMetadata } from './article-strategy.js';
 
/** Keywords shared by all Breaking News articles */
const BREAKING_NEWS_KEYWORDS = [
  'European Parliament',
  'breaking news',
  'voting anomalies',
  'coalition dynamics',
] as const;
 
// ─── Data payload ─────────────────────────────────────────────────────────────
 
/** Data fetched and pre-processed by {@link BreakingNewsStrategy} */
export interface BreakingNewsArticleData extends ArticleData {
  /** Raw voting anomaly text from MCP */
  readonly anomalyRaw: string;
  /** Raw coalition dynamics text from MCP */
  readonly coalitionRaw: string;
  /** Raw voting statistics report text from MCP */
  readonly reportRaw: string;
}
 
// ─── Strategy implementation ──────────────────────────────────────────────────
 
/**
 * Article strategy for {@link ArticleCategory.BREAKING_NEWS}.
 * Aggregates OSINT signals from MCP and surfaces political intelligence.
 */
export class BreakingNewsStrategy implements ArticleStrategy<BreakingNewsArticleData> {
  readonly type = ArticleCategory.BREAKING_NEWS;
 
  readonly requiredMCPTools = [
    'detect_voting_anomalies',
    'analyze_coalition_dynamics',
    'generate_report',
  ] as const;
 
  /**
   * Fetch all OSINT signals in parallel.
   *
   * @param client - MCP client or null
   * @param date - ISO 8601 publication date
   * @returns Populated breaking news data payload
   */
  async fetchData(
    client: EuropeanParliamentMCPClient | null,
    date: string
  ): Promise<BreakingNewsArticleData> {
    if (client) {
      console.log('  📡 Fetching OSINT intelligence data from MCP...');
    }
 
    const [anomalyRaw, coalitionRaw, reportRaw] = await Promise.all([
      fetchVotingAnomalies(client),
      fetchCoalitionDynamics(client),
      fetchVotingReport(client),
    ]);
 
    return { date, anomalyRaw, coalitionRaw, reportRaw };
  }
 
  /**
   * Build the breaking news HTML body for the specified language.
   *
   * @param data - Breaking news data payload
   * @param lang - Target language code used for editorial strings
   * @returns Article HTML body
   */
  buildContent(data: BreakingNewsArticleData, lang: LanguageCode): string {
    return buildBreakingNewsContent(
      data.date,
      data.anomalyRaw,
      data.coalitionRaw,
      data.reportRaw,
      '',
      lang
    );
  }
 
  /**
   * Return language-specific metadata for the breaking news article.
   *
   * @param data - Breaking news data payload
   * @param lang - Target language code
   * @returns Localised metadata
   */
  getMetadata(data: BreakingNewsArticleData, lang: LanguageCode): ArticleMetadata {
    const titleFn = getLocalizedString(BREAKING_NEWS_TITLES, lang);
    const { title, subtitle } = titleFn(data.date);
    return {
      title,
      subtitle,
      keywords: [...BREAKING_NEWS_KEYWORDS],
      category: ArticleCategory.BREAKING_NEWS,
      sources: [],
    };
  }
}
 
/** Singleton instance for use by the strategy registry */
export const breakingNewsStrategy = new BreakingNewsStrategy();