inflation-observer

Africa Data Hub Inflation Observer

The Africa Data Hub Inflation Observer helps journalists, researchers, and civil society organisations explore consumer price inflation (CPI) in African countries and compare it with neighbouring nations. It is a React app that charts year-on-year percentage change for headline CPI and COICOP categories, using data from the IMF, national statistical agencies, and Africa Data Hub.

What the dashboard does

The app has two views, chosen from the query string:

On a country page you can:

All chart values are percentage change, year on year. Historical coverage starts in 2008.

Country URLs use a slug of the name in src/data/countries.json (spaces become hyphens). Special cases: cote-d-ivoire and guinea-bissau.

Embedding

The dashboard is not a standalone site. Africa Data Hub pages (Webflow) inject the built CSS and JS from GitHub Pages into a #adh-embed node. React then mounts on .app inside that node.

yarn build scopes every CSS rule under a unique class (currently unique-oo0kza in post-build-script.js) so Webflow styles do not leak in or out. The host page must wrap .app in that same class.

Host-page loader (Webflow)

This is the Embed / script block on the Webflow page. Production uses these working files on GitHub Pages:

If the host URL contains webflow, the same loader swaps in inflation-observer.dev.css / inflation-observer.dev.js. Paths that contain climate-observer load the CO2 widget instead. Any other path falls back to a Pym.js iframe whose src is the Webflow CMS field Resource embed URL.

The loader injects style overrides so Webflow .container / .col / .card rules do not break the widget, then the CSS, the module script, and the scoped DOM tree (unique-oo0kza → .html → .body → .app).

<div id="adh-embed"></div>
<script src="https://pym.nprapps.org/pym.v1.min.js"></script>
<script>
if(location.pathname.includes('inflation-observer') || location.pathname.includes('climate-observer')) {

	let customClass, cssFile_href, jsFile_src;

	if(location.pathname.includes('inflation-observer')) {
  	customClass = 'unique-oo0kza';
    
    if(window.location.href.includes('webflow')) {
    	cssFile_href = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.dev.css';
    jsFile_src = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.dev.js';
    } else {
	    cssFile_href = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.css';
    	jsFile_src = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.js';
    }
  } else {
  	customClass = 'unique-lpxjij';
    
    if(window.location.href.includes('webflow')) {
    	cssFile_href = 'https://africadatahub.github.io/co2/dist/co2-dev.css';
     	jsFile_src = 'https://africadatahub.github.io/co2/dist/co2-dev.js';
    } else {
	    cssFile_href = 'https://africadatahub.github.io/co2/dist/co2.css';
  	  jsFile_src = 'https://africadatahub.github.io/co2/dist/co2.js';
    }
  }

	let styleOverride = document.createElement('style');
  styleOverride.innerHTML = `
  .${customClass} .container {
  display: block
  }
  .${customClass} .col {
 	padding-bottom: unset;
  }
  .${customClass} .card {
  color: unset;
  background-color: unset;
  border-radius: unset;
  padding: unset;
  text-decoration: unset;
  display: unset;
  box-shadow: unset;
	}`;

	let adhEmbed = document.getElementById("adh-embed");
  
  let cssFile = document.createElement('link');
  cssFile.href= cssFile_href;
  cssFile.rel="stylesheet"
  let jsFile = document.createElement('script');
	jsFile.src= jsFile_src;
  jsFile.type= "module";
  adhEmbed.append(styleOverride);
	adhEmbed.append(cssFile);
	adhEmbed.append(jsFile);
  
  const div1 = document.createElement('div');
  div1.className = customClass;
	const innerDiv1 = document.createElement('div');
  innerDiv1.className = 'html';
  const bodyDiv1 = document.createElement('div');
  bodyDiv1.className = 'body';
  const appDiv1 = document.createElement('div');
  appDiv1.className = 'app';
  bodyDiv1.appendChild(appDiv1);
  innerDiv1.appendChild(bodyDiv1);
  div1.appendChild(innerDiv1);
  document.getElementById("adh-embed").appendChild(div1);


} else {
	var pymParent = new pym.Parent('adh-embed', '', {});
}



</script>

SEO helper

embed.js (also in this repo as embed.js) runs on the host page. When the URL contains ?country=…, it rewrites the title, meta tags, Schema.org dataset markup, and an intro paragraph using the bundled annual-rate figures.

Visitor iframe

The in-app Embed button copies:

<iframe width="700" height="400" src="{current-url}" frameBorder="0"></iframe>

Data

There are two data layers.

Layer What it powers Source
Monthly time series The country chart CKAN Datastore API, live at runtime
Annual headline rates “CPI for full year YYYY was X%”, plus SEO intro copy Bundled JSON in this repo (also duplicated in embed.js)

Chart indicators and the CKAN resource id live in src/data/settings.json. The default series is IMF code PCPI_PC_CP_A_PT (Consumer Price Index, all items). Other options are the standard COICOP groups.

The country page queries CKAN with the country’s ISO-3 code, then pivots rows (one per indicator) into date records. Date columns in the datastore look like unsafe_2008_01_31 and are turned into 2008-01-31.

Where it lives

File format

Annual rates (src/data/annual-rates.csv / .json) — one row per country:

Field Meaning
country_name Display name
country_code ISO-3 (JSON only; the working CSV annual-rates-edit-csv also has this column)
2019 … 2022 Full-year headline CPI, percent. Empty string or #N/A when missing
last_full_year Latest year with a usable annual figure
Extra_notes Optional caveat shown under the headline rate

CKAN monthly resource — one row per country × indicator. Identifier fields include indicator_code and the country ISO-3; remaining keys are unsafe_YYYY_MM_DD month-end values.

Country list (src/data/countries.json):

{ "iso_code": "KEN", "location": "Kenya" }

Regenerating the data

Monthly data is not generated in this repo. Update the CKAN resource through the inflation-database pipeline; this app will pick up the new series on the next page load.

Annual rates are static. To refresh them:

  1. Update src/data/annual-rates.csv (or src/data/annual-rates-edit-csv, which includes country_code).
  2. Convert that table to src/data/annual-rates.json (same objects: year keys, country_name, country_code, last_full_year, Extra_notes). There is no conversion script in this repo.
  3. Copy the same array into the country_inflation_data variable in embed.js (and into the SEO copy of that file if it is hosted separately).
  4. Rebuild and redeploy so the host page and the iframe stay in sync.

If you add or rename a country, update src/data/countries.json as well. Slugs are derived from location.

Coverage caveats

Questions or corrections: info@africadatahub.org.

Local development

  1. Install dependencies:

     yarn
    
  2. Add a .env in the project root with a valid CKAN API key. The country page reads process.env.CKAN:

     CKAN=<provided-key-here>
    
  3. Start Parcel:

     yarn dev
    

Then open a country URL such as http://localhost:1234/?country=kenya.

Project structure

├── embed.js                 # Host-page SEO / intro script (country query param)
├── post-build-script.js     # Scopes built CSS under the unique embed class
├── netlify.toml             # SPA fallback to index.html
├── src/
│   ├── index.html           # #adh-embed mount + SEO embed.js
│   ├── index.js             # React bootstrap
│   ├── App.jsx              # Home vs Country from ?country=
│   ├── app.scss
│   ├── pages/
│   │   ├── Home.jsx         # Country picker
│   │   └── Country.jsx      # Chart, downloads, metadata
│   ├── components/
│   │   ├── CountrySelect.jsx
│   │   └── SocialMedia.jsx  # Share + iframe snippet
│   ├── data/
│   │   ├── settings.json    # Indicators, CKAN URL / resource ids, copy
│   │   ├── countries.json
│   │   ├── annual-rates.json
│   │   └── annual-rates.csv
│   └── utils/func.js        # location ↔ URL slug
└── dist/                    # Built CSS/JS served from GitHub Pages

src/pages/DataExplorer.jsx and src/components/DataExplorer.jsx are leftover and are not mounted.

Hosting & deployment

The live widget is the Parcel build on GitHub Pages (iframe branch, dist/), embedded by Webflow on africadatahub.org. netlify.toml is present for an optional SPA host.

Built assets are renamed before publish:

Environment Files
Staging (africadatahub.webflow.io / africadata.webflow.io) inflation-observer.dev.css, inflation-observer.dev.js
Production (GitHub Pages → africadatahub.org) inflation-observer.css, inflation-observer.js

Deploy steps:

  1. Open a PR that merges src changes into the iframe branch.
  2. Confirm the app on that branch with yarn dev.
  3. Stop the dev server. Delete dist/ contents and .parcel-cache if a previous build is stale.
  4. Run yarn build. The post-build script prints the scoped class (for example unique-oo0kza) and wraps .app in that class.
  5. Rename or copy the hashed CSS/JS in dist/ to the staging or production names above, then push so GitHub Pages updates.

yarn build copies logo/favicon assets into dist/, runs Parcel, then post-build-script.js (CSS scoping and rem → px).

Third-party services

Service Role
ADH CKAN Monthly inflation Datastore API
IMF CPI database Historical core of the CKAN dataset
National statistics offices / central banks Monthly updates scraped into the CKAN dataset
GitHub Pages Hosts dist/ and the SEO embed.js
Webflow Production and staging Africa Data Hub pages that embed the widget
Font Awesome kit Share-button icons on the country page
OpenUp “Powered by” credit on the country page

Reuse of the data requires attribution to the IMF and Africa Data Hub. The ADH project is supported by the Bill & Melinda Gates Foundation.