Skip to content

Add interpellations article type for parliamentary debate coverage#1133

Merged
pethers merged 4 commits into
mainfrom
copilot/aw-no-op-runs-tracking
Mar 13, 2026
Merged

Add interpellations article type for parliamentary debate coverage#1133
pethers merged 4 commits into
mainfrom
copilot/aw-no-op-runs-tracking

Conversation

Copilot AI commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

No dedicated article type or recurring workflow existed for interpellation debates (interpellationsdebatter) — the strongest parliamentary accountability tool where ministers must personally respond in chamber. The MCP client already had fetchInterpellations() but it was only consumed by the week-ahead generator as supplementary data.

New article type: interpellations

  • Type system: Added to ArticleType union and VALID_ARTICLE_TYPES
  • Orchestrator generator: generateInterpellations() in generators.ts using shared MCP client with document enrichment
  • Content routing: 'interpellations' case in data-transformers shares the motions content generator (same document structure)
  • Metadata: Keywords/topics for interpellations (accountability, parliamentary questions) with dedicated interpellationsTag localization key in all 14 languages
  • Localization: Added interpellationsTag to ContentLabels type and all 14 language label files (e.g., "Interpellation Debates", "Interpellationsdebatter", "Débats d'interpellation", "质询辩论", etc.)

Workflow

  • .github/workflows/news-interpellations.md — weekdays at 07:00 UTC, filling the gap in the schedule:
UTC Workflow
04 committee-reports
05 propositions
06 motions
07 interpellations

Tests

Updated ARTICLE_TYPE_WORKFLOWS, expectedTypes, and weekday stagger arrays in workflow-architecture.test.ts. Added interpellationsTag to required content label keys in data-transformers.test.ts. All 2920 tests pass.

npx tsx scripts/generate-news-enhanced.ts --types=interpellations --languages=en,sv
Original prompt

This section details on the original issue you should resolve

<issue_title>[aw] No-Op Runs</issue_title>
<issue_description>This issue tracks all no-op runs from agentic workflows in this repository. Each workflow run that completes with a no-op message (indicating no action was needed) posts a comment here.

📘 What is a No-Op?

A no-op (no operation) occurs when an agentic workflow runs successfully but determines that no action is required. For example:

  • A security scanner that finds no issues
  • An update checker that finds nothing to update
  • A monitoring workflow that finds everything is healthy

These are successful outcomes, not failures, and help provide transparency into workflow behavior.

🎯 How This Helps

This issue helps you:

  • Track workflows that ran but determined no action was needed
  • Distinguish between failures and intentional no-ops
  • Monitor workflow health by seeing when workflows decide not to act
📚 Resources

[!TIP]
To stop a workflow from posting here, set report-as-issue: false in its frontmatter:

safe-outputs:
  noop:
    report-as-issue: false

This issue is automatically managed by GitHub Agentic Workflows. Do not close this issue manually.

No action to take - Do not assign to an agent.

  • expires on Apr 12, 2026, 10:17 AM UTC</issue_description>

<agent_instructions>why no coverage of https://www.riksdagen.se/sv/aktuellt/kalendersida/kalenderhandelse/interpellationsdebatt/2026/mars/13/interpellationsdebatter_rdhdc120260313ip/ for today

interpellationsdebatter do need some news coverage. give realtime more options for different articles types.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

Custom agent used: agentic-workflows
GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI and others added 2 commits March 13, 2026 11:56
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] [aw] Track no-op runs from agentic workflows Add interpellations article type for parliamentary debate coverage Mar 13, 2026
Copilot AI requested a review from pethers March 13, 2026 12:01
@pethers pethers requested a review from Copilot March 13, 2026 12:02
@github-actions github-actions Bot added documentation Documentation updates workflow GitHub Actions workflows ci-cd CI/CD pipeline changes testing Test coverage refactor Code refactoring news News articles and content generation agentic-workflow Agentic workflow changes size-l Large change (250-1000 lines) labels Mar 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new interpellations article type for covering Swedish parliamentary interpellation debates — formal questions demanding ministerial responses in chamber. The change plugs a gap in the automated news generation schedule by adding a weekday 07:00 UTC workflow between the existing motions (06:00) and week-ahead workflows.

Changes:

  • New article type interpellations added to the type system, config, content routing, metadata, and orchestrator generator
  • New standalone module scripts/news-types/interpellations.ts with 14-language titles, MCP tool integration, and validation (though unused by the pipeline)
  • New GitHub Actions agentic workflow .github/workflows/news-interpellations.md scheduled weekdays at 07:00 UTC

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/types/article.ts Adds 'interpellations' to ArticleType union
scripts/generate-news-enhanced/config.ts Adds 'interpellations' to VALID_ARTICLE_TYPES
scripts/generate-news-enhanced/generators.ts New generateInterpellations() orchestrator function using shared MCP client
scripts/generate-news-enhanced/index.ts Wires interpellations into the generation switch and re-exports
scripts/generate-news-enhanced.ts Updates barrel re-export and doc comment
scripts/data-transformers/index.ts Routes 'interpellations' to motions content generator
scripts/data-transformers/metadata.ts Adds interpellations metadata case (keywords, topics, tags)
scripts/news-types/interpellations.ts Standalone generator module (not imported anywhere)
.github/workflows/news-interpellations.md Agentic workflow for scheduled interpellation article generation
tests/workflow-architecture.test.ts Updates test arrays for new article type and workflow

Comment thread scripts/data-transformers/metadata.ts Outdated
keywords.push(kw('interpellations'), kw('parliamentary questions'), kw('parliament'), kw('accountability'));
topics.push('parliament', 'accountability');
{
const tagVal = L(lang, 'oppMotionsTag');
Comment thread scripts/news-types/interpellations.ts Outdated
Comment on lines +1 to +341
/**
* @module ContentGeneration/InterpellationAnalysis
* @category ContentGeneration
*
* @title Interpellation Debates Article Generator - Parliamentary Accountability Intelligence
*
* @description
* **INTELLIGENCE OPERATIVE PERSPECTIVE**
*
* This module generates analysis articles on interpellation debates (interpellationsdebatter),
* which are formal parliamentary questions demanding a minister's personal response in
* the Riksdag chamber. Interpellations represent the strongest tool for opposition
* accountability and ministerial scrutiny, making them critical for understanding
* government pressure points and opposition strategy.
*
* **PARLIAMENTARY CONTEXT - SWEDISH INTERPELLATION SYSTEM:**
* Interpellations (interpellationer) are formal questions from individual MPs to
* government ministers that require a written response AND a chamber debate. Unlike
* written questions (skriftliga frågor), interpellations guarantee floor time and
* public confrontation, making them the primary tool for opposition accountability.
*
* **INTERPELLATION INTELLIGENCE VALUE:**
* Interpellations reveal:
* 1. **Government Pressure Points**: Which ministers face the most scrutiny
* 2. **Opposition Priorities**: What issues opposition parties consider most urgent
* 3. **Ministerial Vulnerability**: How well ministers defend their positions
* 4. **Policy Failures**: Areas where government policy is perceived as inadequate
* 5. **Cross-Party Accountability**: Which parties cooperate on oversight
*
* **MCP DATA SOURCE:**
* Primary tool: get_interpellationer
* Additional tools (with graceful degradation):
* - search_dokument_fulltext: Full-text interpellation analysis
* - search_anforanden: Debate context and minister responses
* - get_calendar_events: Scheduled interpellation debate times
*
* @author Hack23 AB (Parliamentary Accountability & Oversight Intelligence)
* @license Apache-2.0
*/

import { MCPClient } from '../mcp-client.js';
import {
generateArticleContent,
extractWatchPoints,
generateMetadata,
calculateReadTime,
generateSources,
filterFreshDocuments,
type RawDocument
} from '../data-transformers.js';
import { generateArticleHTML } from '../article-template.js';
import type { Language } from '../types/language.js';
import type { ArticleCategory, GeneratedArticle, GenerationResult, MCPCallRecord } from '../types/article.js';

/**
* Required MCP tools for interpellation articles
*/
export const REQUIRED_TOOLS: readonly string[] = [
'get_interpellationer',
'search_dokument_fulltext',
'search_anforanden',
];

export interface TitleSet {
title: string;
subtitle: string;
}

export interface InterpellationsValidationResult {
hasInterpellations: boolean;
hasMinimumSources: boolean;
hasAccountabilityAnalysis: boolean;
passed: boolean;
}

export interface ArticleInput {
content?: string;
sources?: string[];
[key: string]: unknown;
}

export interface GenerationOptions {
languages?: Language[];
limit?: number;
writeArticle?: ((html: string, filename: string) => Promise<void>) | null;
}

/**
* Format date for article slug
*/
export function formatDateForSlug(date: Date = new Date()): string {
return date.toISOString().split('T')[0] ?? '';
}

/**
* Calculate the current Swedish riksmöte (parliamentary session) string.
* The session runs September–August: e.g. September 2025 → "2025/26".
*/
export function getCurrentRiksmote(date: Date = new Date()): string {
const year = date.getFullYear();
const month = date.getMonth(); // 0-based; September = 8
const startYear = month >= 8 ? year : year - 1;
const endYY = String(startYear + 1).slice(-2);
return `${startYear}/${endYY}`;
}

/**
* Generate Interpellation Debates article
*/
export async function generateInterpellations(options: GenerationOptions = {}): Promise<GenerationResult> {
const { languages = ['en', 'sv'], limit = 15, writeArticle = null } = options;

console.log('🔔 Generating Interpellation Debates article...');

const mcpCalls: MCPCallRecord[] = [];

try {
const client = new MCPClient();

console.log(' 🔄 Fetching interpellations from riksdag-regering-mcp...');
const interpellations = filterFreshDocuments(await client.fetchInterpellations({ limit }) as RawDocument[]);
mcpCalls.push({ tool: 'get_interpellationer', result: interpellations });
console.log(` 📊 Found ${interpellations.length} interpellations`);

if (interpellations.length === 0) {
console.log(' ℹ️ No new interpellations found, skipping');
return { success: true, files: 0, mcpCalls };
}

// Tool 2: search_dokument_fulltext — full-text interpellation analysis
try {
const topTitle = interpellations[0]?.titel || interpellations[0]?.title || '';
if (topTitle) {
const ftResponse = await client.request('search_dokument_fulltext', { query: topTitle, limit: 3 });
const ftDocs = (ftResponse['dokument'] ?? ftResponse['results'] ?? []) as RawDocument[];
mcpCalls.push({ tool: 'search_dokument_fulltext', result: ftDocs });
console.log(` 📄 Full text: ${ftDocs.length} results`);
const primaryInterp = interpellations[0] as Record<string, unknown> | undefined;
if (primaryInterp && ftDocs.length > 0 && !primaryInterp['fullText']) {
const bestDoc = ftDocs[0] as Record<string, unknown>;
primaryInterp['fullText'] = (bestDoc['fullText'] as string) || (bestDoc['summary'] as string) || '';
}
}
} catch (err) {
console.warn(' ⚠ search_dokument_fulltext unavailable:', (err as Error).message);
mcpCalls.push({ tool: 'search_dokument_fulltext', result: [] });
}

// Tool 3: search_anforanden — debate context and minister responses
try {
const debateQuery = interpellations[0]?.titel || interpellations[0]?.title || '';
if (debateQuery) {
const speeches = await client.searchSpeeches({ text: debateQuery, rm: getCurrentRiksmote(), limit: 10 }) as Array<Record<string, unknown>>;
mcpCalls.push({ tool: 'search_anforanden', result: speeches });
console.log(` 🗣 Debate speeches: ${speeches.length} found`);
if (speeches.length > 0) {
for (const interp of interpellations) {
const m = interp as Record<string, unknown>;
if (!m['speeches']) {
m['speeches'] = speeches.slice(0, 3).map((s: Record<string, unknown>) => ({
talare: s['talare'],
parti: s['parti'],
text: (s['anforande_text'] as string | undefined)?.slice(0, 300),
anforande_nummer: s['anforande_nummer'],
}));
break;
}
}
}
}
} catch (err) {
console.warn(' ⚠ search_anforanden unavailable:', (err as Error).message);
mcpCalls.push({ tool: 'search_anforanden', result: [] });
}

const today = new Date();
const slug = `${formatDateForSlug(today)}-interpellation-debates`;
const articles: GeneratedArticle[] = [];

for (const lang of languages) {
console.log(` 🌐 Generating ${lang.toUpperCase()} version...`);

// Reuse motions content generator — interpellations share the same document structure
const content: string = generateArticleContent({ motions: interpellations }, 'interpellations', lang);
const watchPoints = extractWatchPoints({ motions: interpellations }, lang);
const metadata = generateMetadata({ motions: interpellations }, 'interpellations', lang);
const readTime: string = calculateReadTime(content);
const sources: string[] = generateSources([
'get_interpellationer',
'search_dokument_fulltext',
'search_anforanden',
]);

const titles: TitleSet = getTitles(lang, interpellations.length, interpellations);

const html: string = generateArticleHTML({
slug: `${slug}-${lang}.html`,
title: titles.title,
subtitle: titles.subtitle,
date: today.toISOString().split('T')[0] ?? '',
type: 'analysis' as ArticleCategory,
readTime,
lang,
content,
watchPoints,
sources,
keywords: metadata.keywords,
topics: metadata.topics,
tags: metadata.tags
});

articles.push({
lang,
html,
filename: `${slug}-${lang}.html`,
slug: `${slug}-${lang}`
});

if (writeArticle) {
await writeArticle(html, `${slug}-${lang}.html`);
console.log(` ✅ ${lang.toUpperCase()} version generated`);
}
}

return {
success: true,
files: languages.length,
slug,
articles,
mcpCalls,
crossReferences: {
event: `${interpellations.length} interpellations`,
sources: ['interpellationer', 'fulltext', 'speeches']
}
};

} catch (error: unknown) {
console.error('❌ Error generating Interpellations:', (error as Error).message);
return {
success: false,
error: (error as Error).message,
mcpCalls
};
}
}

function getTitles(lang: Language, count: number, _documents: RawDocument[] = []): TitleSet {
const titles: Record<Language, TitleSet> = {
en: {
title: `Interpellation Debates: Holding Government to Account`,
subtitle: `Analysis of ${count} interpellation debates demanding ministerial accountability`
},
sv: {
title: `Interpellationsdebatter: Regeringen ställs till svars`,
subtitle: `Analys av ${count} interpellationsdebatter som kräver ministersvar`
},
da: {
title: `Interpellationsdebatter: Regeringen stilles til ansvar`,
subtitle: `Analyse af ${count} interpellationsdebatter`
},
no: {
title: `Interpellasjonsdebatter: Regjeringen stilles til ansvar`,
subtitle: `Analyse av ${count} interpellasjonsdebatter`
},
fi: {
title: `Välikysymyskeskustelut: Hallitus tilivelvollisena`,
subtitle: `Analyysi ${count} välikysymyskeskustelusta`
},
de: {
title: `Interpellationsdebatten: Regierung in der Verantwortung`,
subtitle: `Analyse von ${count} Interpellationsdebatten`
},
fr: {
title: `Débats d'interpellation: Le gouvernement sommé de répondre`,
subtitle: `Analyse de ${count} débats d'interpellation`
},
es: {
title: `Debates de interpelación: El gobierno rinde cuentas`,
subtitle: `Análisis de ${count} debates de interpelación`
},
nl: {
title: `Interpellatiedebatten: Regering ter verantwoording`,
subtitle: `Analyse van ${count} interpellatiedebatten`
},
ar: {
title: `مناقشات الاستجواب: محاسبة الحكومة`,
subtitle: `تحليل ${count} مناقشات استجواب`
},
he: {
title: `דיוני אינטרפלציה: הממשלה נדרשת לתת דין וחשבון`,
subtitle: `ניתוח ${count} דיוני אינטרפלציה`
},
ja: {
title: `質問主意書討論:政府の説明責任を追及`,
subtitle: `${count}件の質問主意書討論の分析`
},
ko: {
title: `대정부 질의 토론: 정부 책임 추궁`,
subtitle: `${count}건의 대정부 질의 토론 분석`
},
zh: {
title: `质询辩论:追究政府责任`,
subtitle: `${count}场质询辩论分析`
}
};

return titles[lang] || titles.en;
}

export function validateInterpellations(article: ArticleInput): InterpellationsValidationResult {
const hasInterpellations = checkInterpellations(article);
const hasMinimumSources = countSources(article) >= 2;
const hasAccountabilityAnalysis = checkAccountabilityAnalysis(article);

return {
hasInterpellations,
hasMinimumSources,
hasAccountabilityAnalysis,
passed: hasInterpellations && hasMinimumSources && hasAccountabilityAnalysis
};
}

function checkInterpellations(article: ArticleInput): boolean {
if (!article || !article.content) return false;
return article.content.toLowerCase().includes('interpellation') ||
article.content.toLowerCase().includes('minister') ||
article.content.toLowerCase().includes('accountability');
}

function countSources(article: ArticleInput): number {
if (!article || !article.sources) return 0;
return Array.isArray(article.sources) ? article.sources.length : 0;
}

function checkAccountabilityAnalysis(article: ArticleInput): boolean {
if (!article || !article.content) return false;
const keywords = ['accountability', 'minister', 'interpellation', 'parliamentary'];
return keywords.some(keyword =>
(article.content as string).toLowerCase().includes(keyword)
);
}
@pethers

pethers commented Mar 13, 2026

Copy link
Copy Markdown
Member

@copilot apply changes based on the comments in this thread

- Added dedicated interpellationsTag to ContentLabels with 14-language
  translations instead of reusing oppMotionsTag
- Fixed metadata.ts to use interpellationsTag for interpellations articles
- Removed dead scripts/news-types/interpellations.ts (341 lines) that
  duplicated generators.ts logic and was never imported
- Updated data-transformers test to include interpellationsTag in required keys

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>

Copilot AI commented Mar 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Applied both review comments in ad4a745:

  1. interpellationsTag localization — Added a dedicated interpellationsTag key to ContentLabels with proper translations for all 14 languages (e.g., "Interpellation Debates", "Interpellationsdebatter", "Débats d'interpellation", etc.) instead of reusing oppMotionsTag. Updated metadata.ts to use L(lang, 'interpellationsTag').

  2. Removed dead module — Deleted scripts/news-types/interpellations.ts (341 lines). The orchestrator uses generateInterpellations() from generators.ts which is the only wired implementation. The standalone module was never imported anywhere.

@pethers pethers requested a review from Copilot March 13, 2026 12:24
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new interpellations article type for covering parliamentary interpellation debates (interpellationsdebatter) — formal parliamentary questions where ministers must personally respond. The implementation follows the established patterns from the existing motions article type.

Changes:

  • Added interpellations to the ArticleType union, VALID_ARTICLE_TYPES, and content routing (sharing the motions content generator), with a new generateInterpellations() orchestrator function in generators.ts
  • Added interpellationsTag localization key to all 14 language label files and the ContentLabelSet type, plus metadata generation for the new type
  • Created .github/workflows/news-interpellations.md workflow running weekdays at 07:00 UTC, and updated tests to cover the new article type

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/types/article.ts Added 'interpellations' to ArticleType union
scripts/types/content.ts Added interpellationsTag to ContentLabelSet interface
scripts/generate-news-enhanced/config.ts Added 'interpellations' to VALID_ARTICLE_TYPES
scripts/generate-news-enhanced/generators.ts New generateInterpellations() function mirroring generateMotions()
scripts/generate-news-enhanced/index.ts Import/export and orchestrator case for interpellations
scripts/generate-news-enhanced.ts Barrel re-export of generateInterpellations and updated doc comment
scripts/data-transformers/index.ts Routes 'interpellations' to motions content generator
scripts/data-transformers/metadata.ts Metadata keywords/topics/tags for interpellations
scripts/data-transformers/constants/content-labels-part1.ts interpellationsTag for EN, SV, DA, NO, FI, DE, FR
scripts/data-transformers/constants/content-labels-part2.ts interpellationsTag for ES, NL, AR, HE, JA, KO, ZH
.github/workflows/news-interpellations.md New AWF workflow for interpellation debates
tests/workflow-architecture.test.ts Updated type/workflow arrays and stagger test
tests/data-transformers.test.ts Added interpellationsTag to required label keys

@pethers pethers marked this pull request as ready for review March 13, 2026 12:30
@pethers pethers merged commit a111c4c into main Mar 13, 2026
21 checks passed
@pethers pethers deleted the copilot/aw-no-op-runs-tracking branch March 13, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentic-workflow Agentic workflow changes ci-cd CI/CD pipeline changes documentation Documentation updates news News articles and content generation refactor Code refactoring size-l Large change (250-1000 lines) testing Test coverage workflow GitHub Actions workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aw] No-Op Runs

3 participants