#!/usr/bin/env bun /** * Get DE Parliament Activity Data (DIP Bundestag) * * Fetches Drucksachen counts by type from the DIP (Dokumentations- und * Informationssystem für Parlamentsmaterialien) API and produces: * - Data/DE-Parliament-Activity/drucksachen-wp21.csv * * API: https://search.dip.bundestag.de/api/v1 * Docs: https://dip.bundestag.de/%C3%BCber-dip/hilfe/api * * API KEY NOTE: The key below is a temporary public key valid until end of 05/2026. * To request a permanent individual API key, contact: * parlamentsdokumentation@bundestag.de */ // Bun on macOS may lack the Bundestag CA in its bundled cert store. // This flag allows the fetch to proceed against the known-good government endpoint. process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; import { writeFileSync, mkdirSync } from "fs"; import { join } from "path"; const API_BASE = "https://search.dip.bundestag.de/api/v1"; const API_KEY = "OSOegLs.PR2lwJ1dwCeje9vTj7FPOt3hvpYKtwKkhw"; const WAHLPERIODE = 21; const OUT_DIR = join(__dirname, "Data/DE-Parliament-Activity"); interface DipResponse { numFound: number; documents: unknown[]; cursor?: string; } async function fetchCount( endpoint: string, params: Record = {} ): Promise { const url = new URL(`${API_BASE}/${endpoint}`); url.searchParams.set("limit", "1"); for (const [k, v] of Object.entries(params)) { url.searchParams.set(k, String(v)); } // Pass key both as header and query param for compatibility url.searchParams.set("apikey", API_KEY); const res = await fetch(url.toString(), { headers: { Authorization: `ApiKey ${API_KEY}`, Accept: "application/json", }, }); if (!res.ok) { throw new Error(`HTTP ${res.status} for ${endpoint}: ${res.statusText}`); } const data = (await res.json()) as DipResponse; return data.numFound; } function csvEscape(value: string | number): string { const s = String(value); if (s.includes(",") || s.includes('"') || s.includes("\n")) { return `"${s.replace(/"/g, '""')}"`; } return s; } const DRUCKSACHE_TYPES: Array<{ type: string; description_de: string }> = [ { type: "Antrag", description_de: "Anträge der Fraktionen" }, { type: "Kleine Anfrage", description_de: "Kleine Anfragen" }, { type: "Große Anfrage", description_de: "Große Anfragen" }, { type: "Schriftliche Fragen", description_de: "Schriftliche Fragen" }, { type: "Gesetzentwurf", description_de: "Gesetzentwürfe" }, { type: "Unterrichtung", description_de: "Unterrichtungen (Berichte der Bundesregierung)", }, { type: "Entschließungsantrag", description_de: "Entschließungsanträge", }, { type: "Beschlussempfehlung", description_de: "Beschlussempfehlungen der Ausschüsse", }, ]; async function main() { console.log( `Fetching DIP Bundestag data for Wahlperiode ${WAHLPERIODE}…\n` ); mkdirSync(OUT_DIR, { recursive: true }); // ── Drucksachen total ──────────────────────────────────────────────────── const totalDrucksachen = await fetchCount("drucksache", { "f.wahlperiode": WAHLPERIODE, }); console.log(`Total Drucksachen (WP${WAHLPERIODE}): ${totalDrucksachen}`); // ── By type ─────────────────────────────────────────────────────────────── const typeResults: Array<{ type: string; count: number; description_de: string; }> = []; for (const { type, description_de } of DRUCKSACHE_TYPES) { const count = await fetchCount("drucksache", { "f.wahlperiode": WAHLPERIODE, "f.drucksachetyp": type, }); console.log(` ${type}: ${count}`); typeResults.push({ type, count, description_de }); } // ── Vorgänge ────────────────────────────────────────────────────────────── const totalVorgaenge = await fetchCount("vorgang", { "f.wahlperiode": WAHLPERIODE, }); console.log(`\nVorgänge (WP${WAHLPERIODE}): ${totalVorgaenge}`); // ── Plenarprotokolle ────────────────────────────────────────────────────── const totalPlenar = await fetchCount("plenarprotokoll", { "f.wahlperiode": WAHLPERIODE, }); console.log(`Plenarprotokolle (WP${WAHLPERIODE}): ${totalPlenar}`); // ── Write CSV ───────────────────────────────────────────────────────────── const header = "type,count,description_de"; const rows: string[] = [ [ csvEscape("Gesamt_Drucksachen"), totalDrucksachen, csvEscape(`Alle Drucksachen Wahlperiode ${WAHLPERIODE}`), ].join(","), ...typeResults.map((r) => [csvEscape(r.type), r.count, csvEscape(r.description_de)].join(",") ), [ csvEscape("Vorgaenge"), totalVorgaenge, csvEscape(`Gesetzgebungsvorgänge Wahlperiode ${WAHLPERIODE}`), ].join(","), [ csvEscape("Plenarprotokolle"), totalPlenar, csvEscape(`Plenarprotokolle Wahlperiode ${WAHLPERIODE}`), ].join(","), ]; const csvPath = join(OUT_DIR, "drucksachen-wp21.csv"); writeFileSync(csvPath, [header, ...rows].join("\n") + "\n"); console.log(`\nWrote ${csvPath}`); // ── Summary ─────────────────────────────────────────────────────────────── const topType = [...typeResults].sort((a, b) => b.count - a.count)[0]; const gesetzentwuerfe = typeResults.find((r) => r.type === "Gesetzentwurf")?.count ?? 0; console.log(`\n── Summary ──────────────────────────────────────────────`); console.log(`Wahlperiode: ${WAHLPERIODE} (seit Oktober 2025)`); console.log(`Total Drucksachen: ${totalDrucksachen}`); console.log(`Most common type: ${topType.type} (${topType.count})`); console.log(`Gesetzentwürfe: ${gesetzentwuerfe}`); console.log(`Vorgänge: ${totalVorgaenge}`); console.log(`Plenarprotokolle: ${totalPlenar}`); console.log(`API key expires: 05/2026 — renew at parlamentsdokumentation@bundestag.de`); } main().catch((err) => { console.error("Error:", err.message); process.exit(1); });