Four new data sources and fetch scripts via bundesAPI community project: - DS-00011 Lobbyregister: 6,799 registrants, €0.86–0.91B declared lobbying (FY2024) - DS-00012 SMARD Strommarkt: 60.2% renewable 2024, Wind Onshore #1 (107 TWh) - DS-00013 Bundeshaushalt: €474.75B Ist-Wert 2024, 38.2% Soziales, 10.6% Verteidigung - DS-00014 DIP Bundestag: 7,605 Drucksachen WP21, 12,507 Vorgänge, 83 Plenarprotokolle Each integration: live-data fetch script (bun/TypeScript) + DATASET-TEMPLATE markdown + CSV outputs. Scripts idempotent — re-run for current data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
179 lines
6.6 KiB
Plaintext
Executable File
179 lines
6.6 KiB
Plaintext
Executable File
#!/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<string, string | number> = {}
|
|
): Promise<number> {
|
|
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);
|
|
});
|