A pipeline to build structured datasets of geopolitical events from the GDELT database.
1
stars
6
commits
Jupyter Notebook
primary language
Jun 17, 2026
updated
An automated pipeline that turns global news into a structured, deduplicated database of geopolitical incidents — across five domains: cyberattacks military aid, military offensives, sanctions, and international summits.
It discovers articles from GDELT via BigQuery, filters and enriches them with a hybrid machine-learning + LLM stack, clusters related reports into distinct real-world incidents, and publishes JSON/Parquet datasets.
This is a research project. The repository is made public to show a method to turn press articles into structured event datasets. This is not intended to be a fully finished software.
For each domain, the pipeline creates two datasets reports (individual enriched articles) and incidents (deduplicated real-world events aggregating their reports), plus time-windowed slices (14 days, 1/2/5 years) for fast querying. A cross-domain join merges all five into one unified schema.
An incident looks roughly like this (illustrative):
{
"incident_id": "20240712-cyber-0-3",
"incident_type": "Cyberattack",
"incident_start_date": "2024-07-12",
"initiating_countries": ["Russia"],
"receiving_countries": ["Ukraine", "European Union"],
"sectors": ["Energy", "Government"],
"threat_actors": ["Sandworm"],
"summary": "A coordinated cyberattack targeted Ukrainian energy-sector operators ...",
"num_reports": 14
}
Five domains run as parallel tasks of a single Cloud Run Job (CLOUD_RUN_TASK_INDEX 0–4). Each domain flows through the same 12-stage, incremental pipeline; a separate job joins the outputs.
flowchart TD
subgraph domain["Per-domain pipeline (x5: cyber, military aid, military offensive, sanctions, summits)"]
A["GDELT via BigQuery<br/>(multilingual keyword filters)"] --> B[translate<br/>langdetect + Google Translate]
B --> C[classify<br/>embeddings + sklearn logistic regression]
C --> D[add_content<br/>full-article extraction]
D --> E[llm_classify<br/>LLM precision filter]
E --> F[summarize<br/>local Gemma LLM]
F --> G[label<br/>LLM extraction + PolyFuzz normalization]
G --> H[cluster<br/>temporal sliding-window embedding clustering]
H --> I[add_source_info<br/>outlet metadata join]
I --> J[incident_summarize<br/>aggregate reports - incident summary]
J --> K[compute_embeddings<br/>per-incident embeddings]
end
K --> L[(Domain JSON / Parquet<br/>databases in GCS)]
L --> M[join_datasets<br/>cross-domain schema unification]
M --> N[(Unified geopolitical<br/>events database)]
[geoinsights_data/utils/cluster.py](./geoinsights_data/utils/cluster.py).geoinsights_data/
├── pipeline/ # Main orchestrator (5 parallel domain tasks) + Dockerfile
├── sources/ # News-outlet metadata enrichment job + Dockerfile
├── join_datasets/ # Cross-domain merge job + Dockerfile
├── cyber/ military_aid/ military_offensive/ sanctions/ summits/
│ # Per-domain prompts.py + keywords.yaml
└── utils/ # Shared stages: collect, translate, classify, add_content,
# llm, local_llms, cluster, incident_summarize, rules
tests/ # Unit tests
Python 3.12 · GCP (Cloud Run Jobs, BigQuery, Cloud Storage, Artifact Registry, Cloud Scheduler) · scikit-learn · PyTorch · HDBSCAN · OpenAI (embeddings + LLMs) · local Gemma via llama-cpp · newspaper3k · PolyFuzz · MITRE ATT&CK (attackcti) · pandas / pyarrow · Docker.
The pipeline is configured entirely through environment variables (no secrets in the repo):
| Variable | Purpose |
|---|---|
BUCKET_NAME | GCS bucket holding all pipeline state, models, and outputs |
PROJECT_ID | GCP project for BigQuery |
OPENAI_API_KEY | OpenAI API key (embeddings + chat) |
MODEL_NAME | OpenAI chat model used for classification/labeling |
EMBEDDING_MODEL | Embedding model (e.g. text-embedding-3-small) |
CLOUD_RUN_TASK_INDEX | Selects the domain (0–4) when running the pipeline job |
*_CLASSIFICATION_THRESHOLD | Per-domain classifier thresholds (e.g. CYBER_CLASSIFICATION_THRESHOLD) |
LLAMA_MODEL_PATH, LLAMA_N_CTX, HF_HOME | Local LLM (summarization) settings |
Trained classifier models (*.pkl) and all CSV/JSON state live in the GCS bucket, not in the repo.
See DEPLOYMENT.md for the full Cloud Run Jobs + Artifact Registry + Cloud Scheduler setup.
This started as a solo product and is shared as-is. Notably: limited test coverage, no formal accuracy/evaluation harness for the classifiers and clustering, and some duplication across the per-domain branches. Contributions welcome — see CONTRIBUTING.md.
6 commits
Jupyter Notebook
77.9%
Python
21.6%
A pipeline to build structured datasets of geopolitical events from the GDELT database.
1
stars
6
commits
Jupyter Notebook
primary language
Jun 17, 2026
updated
An automated pipeline that turns global news into a structured, deduplicated database of geopolitical incidents — across five domains: cyberattacks military aid, military offensives, sanctions, and international summits.
It discovers articles from GDELT via BigQuery, filters and enriches them with a hybrid machine-learning + LLM stack, clusters related reports into distinct real-world incidents, and publishes JSON/Parquet datasets.
This is a research project. The repository is made public to show a method to turn press articles into structured event datasets. This is not intended to be a fully finished software.
For each domain, the pipeline creates two datasets reports (individual enriched articles) and incidents (deduplicated real-world events aggregating their reports), plus time-windowed slices (14 days, 1/2/5 years) for fast querying. A cross-domain join merges all five into one unified schema.
An incident looks roughly like this (illustrative):
{
"incident_id": "20240712-cyber-0-3",
"incident_type": "Cyberattack",
"incident_start_date": "2024-07-12",
"initiating_countries": ["Russia"],
"receiving_countries": ["Ukraine", "European Union"],
"sectors": ["Energy", "Government"],
"threat_actors": ["Sandworm"],
"summary": "A coordinated cyberattack targeted Ukrainian energy-sector operators ...",
"num_reports": 14
}
Five domains run as parallel tasks of a single Cloud Run Job (CLOUD_RUN_TASK_INDEX 0–4). Each domain flows through the same 12-stage, incremental pipeline; a separate job joins the outputs.
flowchart TD
subgraph domain["Per-domain pipeline (x5: cyber, military aid, military offensive, sanctions, summits)"]
A["GDELT via BigQuery<br/>(multilingual keyword filters)"] --> B[translate<br/>langdetect + Google Translate]
B --> C[classify<br/>embeddings + sklearn logistic regression]
C --> D[add_content<br/>full-article extraction]
D --> E[llm_classify<br/>LLM precision filter]
E --> F[summarize<br/>local Gemma LLM]
F --> G[label<br/>LLM extraction + PolyFuzz normalization]
G --> H[cluster<br/>temporal sliding-window embedding clustering]
H --> I[add_source_info<br/>outlet metadata join]
I --> J[incident_summarize<br/>aggregate reports - incident summary]
J --> K[compute_embeddings<br/>per-incident embeddings]
end
K --> L[(Domain JSON / Parquet<br/>databases in GCS)]
L --> M[join_datasets<br/>cross-domain schema unification]
M --> N[(Unified geopolitical<br/>events database)]
[geoinsights_data/utils/cluster.py](./geoinsights_data/utils/cluster.py).geoinsights_data/
├── pipeline/ # Main orchestrator (5 parallel domain tasks) + Dockerfile
├── sources/ # News-outlet metadata enrichment job + Dockerfile
├── join_datasets/ # Cross-domain merge job + Dockerfile
├── cyber/ military_aid/ military_offensive/ sanctions/ summits/
│ # Per-domain prompts.py + keywords.yaml
└── utils/ # Shared stages: collect, translate, classify, add_content,
# llm, local_llms, cluster, incident_summarize, rules
tests/ # Unit tests
Python 3.12 · GCP (Cloud Run Jobs, BigQuery, Cloud Storage, Artifact Registry, Cloud Scheduler) · scikit-learn · PyTorch · HDBSCAN · OpenAI (embeddings + LLMs) · local Gemma via llama-cpp · newspaper3k · PolyFuzz · MITRE ATT&CK (attackcti) · pandas / pyarrow · Docker.
The pipeline is configured entirely through environment variables (no secrets in the repo):
| Variable | Purpose |
|---|---|
BUCKET_NAME | GCS bucket holding all pipeline state, models, and outputs |
PROJECT_ID | GCP project for BigQuery |
OPENAI_API_KEY | OpenAI API key (embeddings + chat) |
MODEL_NAME | OpenAI chat model used for classification/labeling |
EMBEDDING_MODEL | Embedding model (e.g. text-embedding-3-small) |
CLOUD_RUN_TASK_INDEX | Selects the domain (0–4) when running the pipeline job |
*_CLASSIFICATION_THRESHOLD | Per-domain classifier thresholds (e.g. CYBER_CLASSIFICATION_THRESHOLD) |
LLAMA_MODEL_PATH, LLAMA_N_CTX, HF_HOME | Local LLM (summarization) settings |
Trained classifier models (*.pkl) and all CSV/JSON state live in the GCS bucket, not in the repo.
See DEPLOYMENT.md for the full Cloud Run Jobs + Artifact Registry + Cloud Scheduler setup.
This started as a solo product and is shared as-is. Notably: limited test coverage, no formal accuracy/evaluation harness for the classifiers and clustering, and some duplication across the per-domain branches. Contributions welcome — see CONTRIBUTING.md.
6 commits
Jupyter Notebook
77.9%
Python
21.6%