This file provides guidance to Claude Code when working with code in this repository.
Research companion repo for the paper "A Bibliometric and Semantic ATLAS of AI Bias Research: Trends, Disparities, and Knowledge Graph Perspectives" (under revision, target: DMKD). Contains analysis scripts for the survey corpus, a metadata extraction notebook, and an interactive Next.js dashboard deployed to https://biasatlas.cair-nepal.org (custom domain; repo renamed to CAIRNepal/biasatlas).
Current corpus: 692 papers (deduped from 703: 7 exact duplicates + 4 near-duplicate pairs removed, 1 corrupted metadata row fixed via OpenAlex re-fetch). All 692 have abstracts (688 via OpenAlex, 4 manually curated for papers with no DOI).
algorithmic-bias-survey/
├── evaluation/analysis/ # Python scripts → figures + CSVs
│ ├── papers_new.csv # Primary corpus (692 papers, actively maintained)
│ ├── semantic_similarity.py # SBERT embeddings + UMAP → semantic_clusters.csv + figures
│ ├── embedding_comparison.py # Compare 7 embedding models for domain separation
│ ├── umap_grid_search.py # Grid search n_neighbors × min_dist → best UMAP params
│ └── figures_new/ # Output figures (PNG/PDF)
├── generate_atlas_umap.py # 3 SBERT models × 2D+3D UMAP + HDBSCAN → atlas_data.csv
├── openalex_enriched.csv # OpenAlex metadata (abstracts, citations, countries, OA)
├── extract_publication_metadata.ipynb # Jupyter notebook: fetches OpenAlex metadata by DOI
├── dashboard/ # Next.js 15 dashboard
│ ├── public/
│ │ ├── atlas_data.csv # Generated by generate_atlas_umap.py
│ │ ├── semantic_clusters.csv # Generated by semantic_similarity.py
│ │ └── papers.csv # Copy of papers_new.csv for dashboard
│ └── src/app/ # React components
└── .github/workflows/
├── deploy.yml # Auto-deploys dashboard on push to main
└── update-atlas.yml # Manually triggered: regenerates atlas_data.csv in CI
evaluation/analysis/papers_new.csv— 692 papers (actively maintained, all fields complete)- Columns:
SN, Paper Title, DOI, Authors, Author Regions, Affiliations, Year, Focus Region, Domain, Source, ORC ID - Five domains:
Health & Clinical AI,General Fairness & Bias Mitigation,Graph-Based Fairness & Bias Mitigation,LLMs & NLP,Recommender Systems - Country names normalized: UK→United Kingdom, DZ→Algeria, IE→Ireland, Finlad→Finland, Dubai→UAE
- SN 633 author fix: Shloka Desai (not Sudarshan Desai); SN 35 author fix: Pessach/Tassa/Shmueli (not Oneto/Chiappa)
Author RegionsandAffiliationsmust have the same number of semicolon-separated entries asAuthors— a mismatch silently breaks per-author attribution
- Columns:
openalex_enriched.csv— metadata fetched via OpenAlex API (692 rows, all fields filled)- Columns:
SN, corpus_doi, openalex_id, doi, title, year, cited_by_count, abstract, referenced_works, is_oa, oa_status, oa_url, openalex_authors, openalex_affiliations, openalex_countries, keywords - DOI used as primary identifier for matching (rekeyed by normalized DOI, not by SN — SN alone was previously found unreliable across the two files)
- All 692 papers have abstracts (688 via OpenAlex, 4 manually curated for the 6 papers with no DOI — 2 of those 6 also lack an abstract entirely)
- Columns:
-
dashboard/public/atlas_data.csv— generated bygenerate_atlas_umap.py- 692 rows, 38 columns (688 embedded — 4 papers with no abstract are excluded from clustering)
- Per-model 2D coords:
umap_x_mpnet,umap_y_mpnet,umap_x_minilm,umap_y_minilm,umap_x_specter,umap_y_specter - Per-model 3D coords:
umap_x3_mpnet,umap_y3_mpnet,umap_z3_mpnet(+ minilm, specter variants) - Default coords (mpnet):
umap_x,umap_y,umap_x3,umap_y3,umap_z3 - Cluster:
cluster(int, -1 = noise),cluster_label(auto-labeled from top keywords) - Current: 4 clusters (0:93, 1:127, 2:301, 3:163), 8 noise points (1.2%)
-
dashboard/public/semantic_clusters.csv— generated bysemantic_similarity.py- 692 rows (regenerated, complete)
- Columns:
SN, Paper Title, DOI, Domain, Year, umap_x, umap_y, umap_x_minilm, umap_y_minilm, umap_x_mpnet, umap_y_mpnet, umap_x_specter, umap_y_specter, cited_by_count, is_oa, oa_status, oa_url
CSV quoting: Use quoting=csv.QUOTE_ALL in pandas to avoid breaking Papa Parse.
Three SBERT models used throughout:
| Key | Model ID | Dims | Notes |
|---|---|---|---|
mpnet |
all-mpnet-base-v2 |
768 | Default/best — highest domain separation gap (0.111) |
minilm |
all-MiniLM-L6-v2 |
384 | Fast, general purpose (gap 0.100) |
specter |
allenai-specter |
768 | Scientific paper trained (gap 0.059) |
UMAP params (optimal, main analysis, MPNet): n_neighbors=30, min_dist=0.0, metric='cosine', random_state=42
- Grid search confirmed these optimal (embedding silhouette=0.2564; MiniLM optimal at n_neighbors=15/min_dist=0.0, silhouette=0.2137; SPECTER optimal at n_neighbors=10/min_dist=0.05, silhouette=0.2052)
- Grid search results are saved every run to
evaluation/analysis/figures_new/embedding_grid_search_summary.csvandembedding_best_params_summary.csv— check these rather than re-deriving from stdout
UMAP params (robustness check): n_neighbors=15, min_dist=0.1 (library defaults)
- Used in
evaluation/analysis/umap_default_validation.py - Result: 3 clusters, 17 noise (2.5%), clustering silhouette=0.5331, ARI=0.36, NMI=0.41 (post-hoc vs domain labels)
HDBSCAN params (main): min_cluster_size=20, min_samples=10, metric='euclidean' — confirmed via grid search over the full corpus (do not assume min_samples=5 is still optimal after any corpus change; re-run the grid search in semantic_similarity.py, saved to hdbscan_grid_search_summary.csv)
HDBSCAN params (robustness): same as main (min_cluster_size=20, min_samples=10)
- Result: 4 clusters, 8 noise (1.2%), clustering silhouette=0.6852, ARI=0.53, NMI=0.47
- Run on mpnet 2D coords only — cluster labels fixed regardless of which model is displayed
- Domain labels are NEVER used in clustering; ARI/NMI computed post-hoc for validation only
- Domain × cluster percentages should use the all-domain-papers denominator (matches the heatmap figures), not clustered-points-only — the two give different percentages when noise points exist, and only the all-domain version matches what's plotted
evaluation/analysis/images/domain_cluster_heatmap.png/.pdf— main Domain×Cluster heatmap (optimal params). Run:cd algorithmic-bias-survey && python evaluation/analysis/domain_cluster_heatmap.pyevaluation/analysis/figures_new/robustness_heatmap.png/.pdf— robustness check (default UMAP). Run:python evaluation/analysis/umap_default_validation.py- Tex figure: 3 subfigures — (a)
semantic_landscape_mpnet.png, (b)domain_cluster_heatmap.png, (c)robustness_heatmap.png
For the contributor-facing version of this (with a data-flow diagram and
common pitfalls), see CONTRIBUTING.md at the repo root.
# 1. Add rows to evaluation/analysis/papers_new.csv
# 2. Fetch OpenAlex metadata for new papers
# Open extract_publication_metadata.ipynb and run for new DOIs
# → updates openalex_enriched.csv
# 3. Regenerate semantic clusters (SBERT + UMAP, 3 models)
python3 evaluation/analysis/semantic_similarity.py
# → dashboard/public/semantic_clusters.csv
# → evaluation/analysis/figures_new/semantic_landscape_{mpnet,minilm,specter}.png
# 4. Regenerate atlas (SBERT + UMAP 2D+3D + HDBSCAN, 3 models)
python3 generate_atlas_umap.py
# → dashboard/public/atlas_data.csv
# 5. Update dashboard/public/papers.csv
python3 -c "
import pandas as pd, csv
df = pd.read_csv('evaluation/analysis/papers_new.csv')
df.to_csv('dashboard/public/papers.csv', index=False, quoting=csv.QUOTE_ALL)
"
# 6. Commit and push → deploy.yml auto-deploys to GitHub Pages
git add dashboard/public/ evaluation/analysis/papers_new.csv openalex_enriched.csv
git commit -m "Add N new papers, regenerate atlas and semantic clusters"
# Note: dashboard/public/papers.csv already has 692 rows with normalized country names
git pushNote: Steps 3 and 4 each take ~3–5 minutes locally (SBERT encoding + UMAP).
| Script | What it produces | Needs Affiliations? |
|---|---|---|
semantic_similarity.py |
UMAP figures (3 models) + semantic_clusters.csv |
No |
embedding_comparison.py |
Per-model UMAP plots + domain separation ranking | No |
umap_grid_search.py |
Grid search heatmap + best params | No |
all_first_author_count_country_paper.py |
Country bar charts | No |
country_country_collab_heatmap.py |
Country collaboration matrix | No |
country_domain_heatmap_participation_all_authors_paper.py |
Country × Domain heatmap | No |
country_domain_heatmap_participation_first_author_paper.py |
Country × Domain heatmap (first author) | No |
domain_evolution_over_time.py |
Domain trends over years | No |
overall_domain_distribution_all_country.py |
Domain pie chart | No |
top_n_university_domain_all_author_paper.py |
Top 20 institutions heatmap | Yes |
top_n_university_domain_first_author_paper.py |
Top 20 institutions (first author) | Yes |
Note: Affiliations column in papers_new.csv is now populated for all 692 papers — the institution scripts run normally.
cd dashboard
npm install
npm run dev # localhost:3000If you see Cannot find module './NNN.js': stale build cache. Run rm -rf .next then restart.
| Route | Component | Data source |
|---|---|---|
/ |
AbstractAtlas |
public/atlas_data.csv |
/atlas |
AbstractAtlas (same as home) |
public/atlas_data.csv |
/analytics |
BiasResearchDashboard |
public/papers.csv |
/advanced |
AdvancedAnalytics |
public/papers.csv |
-
AbstractAtlas.tsx— Full-screen semantic atlas. Features:- 2D canvas UMAP (pan/zoom/lasso selection)
- 3D canvas UMAP (auto-rotate, arcball drag, zoom)
- Embedding model toggle: MPNet / MiniLM / SPECTER (switches both 2D and 3D coords)
- World choropleth map + collaboration panel
- Left sidebar: Domain + Cluster filters
- Right panel: Filters & Analytics or DetailPanel (abstract, related papers, citation percentile, geography, cluster context)
- Toolbar: search, color-mode (domain/cluster), lasso, map-view toggle, CSV download
-
BiasResearchDashboard.tsx— Research analytics. Loadspapers.csv. -
AdvancedAnalytics.tsx— Extended analytics. Loadspapers.csv. -
SemanticClusterChart.tsx— Dead file, not imported anywhere. Can be deleted.
atlas_data.csv (Papa Parse, dynamicTyping: true)
→ papers[] (AtlasPaper type)
→ embedModel state ('mpnet' | 'minilm' | 'specter')
→ embedModelRef (synced each render for canvas callbacks)
→ dataRange / dataRange3 (recompute on embedModel change)
→ toScreen2 / project3 (useCallback on dataRange)
→ render2Ref / render3Ref (updated each render, called via rAF)
→ filteredPapers (domain + cluster + lasso + country + author + year + OA)
Stale-closure fix: render2/render3 captured in refs updated every render. All RAF and auto-rotate loops call renderXRef.current().
Automatic via .github/workflows/deploy.yml on push to main. Builds static export → GitHub Pages.
// Base path handling (updated for custom domain)
const base = process.env.NEXT_PUBLIC_BASE_PATH || '';
Papa.parse(`${base}/atlas_data.csv`, ...);Custom domain biasatlas.cair-nepal.org requires:
dashboard/public/CNAMEwith contentbiasatlas.cair-nepal.org- Empty
basePath: ""innext.config.js - Custom domain enabled in GitHub repo Settings → Pages
- Card style:
rounded-2xl border border-gray-100 shadow-sm - Page background:
bg-slate-50 - Domain colours: Health=
#e63946, General=#457b9d, Graph=#2a9d8f, LLMs=#e9c46a, Recommender=#f4a261 - Cluster colours: Tol-bright 14-colour palette (colour-vision-friendly)
- Atlas full-screen:
height: calc(100vh - 72px),body overflow: hidden
main— Production, triggers GitHub Pages deploy on pushrevision— Active revision branch for paper resubmission work
- Renamed to
CAIRNepal/biasatlas— update remote if needed:git remote set-url origin https://github.com/CAIRNepal/biasatlas.git - Dataset uploaded to Hugging Face:
cair-nepal/ai-bias-research-landscape(dataset card:DATASET_CARD.md) - Dataset archived on Zenodo: https://doi.org/10.5281/zenodo.21221283
- Anchor script output paths to the script's own location
(
Path(__file__).resolve().parent), never a bare relative string. A bare relative path resolves against whatever directory you happen to run the script from — this previously caused several scripts to silently write to the repo root instead ofevaluation/analysis/figures_new/, leaving stale data being read from the wrong place for an entire session. - Don't assume a previously-tuned hyperparameter is still optimal after
the corpus changes.
HDBSCAN min_sampleswas hardcoded at 5 from an earlier corpus size; re-running the grid search after dedup showedmin_samples=10was actually optimal. Re-checkhdbscan_grid_search_summary.csvandembedding_best_params_summary.csvafter any corpus change rather than reusing old hardcoded values. - Domain × cluster overlap percentages have two possible denominators (clustered-points-only vs. all-domain-papers) that give different numbers when noise points exist. The heatmap figures use all-domain-papers — match that convention when reporting percentages elsewhere, or the numbers won't agree with the figure.