# electricitymaps-contrib: the parsers behind the carbon intensity map

> An AGPL licensed Python repository holding one parser per grid operator, feeding the data layer that Electricity Maps turns into carbon intensity numbers you can act on.

**electricitymaps/electricitymaps-contrib** — The open source repository for Electricity Maps data parsers that powers the world's most comprehensive electricity data platform

- Repository: https://github.com/electricitymaps/electricitymaps-contrib
- Website: https://app.electricitymaps.com
- Stars: 4,041 · Forks: 1,065
- Language: Python
- License: AGPL-3.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/electricitymaps-electricitymaps-contrib

## One parser per grid operator, all producing the same shape

The central design problem in this repository is heterogeneity. Every transmission system operator publishes generation and exchange data in its own format: different URLs, different timestamp conventions, different units, and different levels of regional detail. The repository solves this by writing one parser per source and normalising every one of them into a common output, so that everything downstream can treat a Danish wind farm and a Japanese thermal plant the same way.

The package is called `electricitymap-contrib` on the index and lives in the `electricitymap/` directory, with the entry points declared in `pyproject.toml`:

```toml
[project.scripts]
test-parser = "test_parser:test_parser"
test_parser = "test_parser:test_parser"
capacity_update = "capacity_update:capacity_update"
check = "scripts.tooling:check"
format = "scripts.tooling:format"
lint = "scripts.tooling:lint"
test = "scripts.tooling:test"
```

Seven console scripts, and two of them are the same function under two spellings, which tells you this has been a package that has been through more than one packaging convention. The `capacity_update` entry point handles the region capacity changes that show up constantly in the release notes, and the tooling aliases wrap linting, formatting and test running behind one name each.

The repository tree shows the rest of the structure. `config/` holds zone and exchange definitions, `geo/` holds geographic data, `scripts/` holds tooling, `tests/` holds the test suite, and `libs/types/` is a workspace member holding a separate types package. `test_parser.py` at the root and `parsers.dockerfile` are there so a parser can be exercised in isolation, which matters when the source is a PDF or an image and you need a reproducible container to debug it.

## Heavy parsing dependencies are an optional extra

The dependency split in `pyproject.toml` is the clearest statement of what this code actually does. The core install is small: `pydantic` for validating parser output, `PyYAML` and `ruamel-yaml` for the config files, `tzdata` for timezones, `numpy` for numeric work, and the separate types package. That is the runtime for a parser that reads JSON or XML.

Everything needed to parse harder formats is behind the `parsers` extra, and the list is a catalogue of what national grid operators actually publish:

```toml
[project.optional-dependencies]
parsers = [
    "beautifulsoup4~=4.6.0",
    "lxml>=6.1.0,<7",
    "pandas>=1.5, <3",
    "requests~=2.33.0",
    "xlrd>=2.0.1,<3",
]
```

`pandas` and `xlrd` cover spreadsheet downloads, `beautifulsoup4` and `lxml` cover scraped HTML and XML, and the rest of the extra is where the awkward cases live: `pytesseract` and `opencv-python` for reading numbers out of images, `demjson3` and `pydataxm` for the JSON dialects and XML formats that break strict parsers, `pycountry` and `odfpy` for region codes and OpenDocument files, and `freezegun` for pinning time during tests. `signalr-client-threads` is there for the one source that only exposes its data over a SignalR connection.

Moving that list into an extra, which a release note records as making parsers an extra, means the ordinary install stays quick for anyone who just wants the config and the model definitions.

## Consumption based accounting and why per capita is missing

The README spends more words defending methodology than documenting the code, and that is the point. Carbon intensity here is calculated for consumption rather than production, which is a policy choice with a specific argument behind it: a country should not carry the emissions of everything it exports, only of what it consumes. The README also notes that this accounting is harder to game, since dirty production cannot be made to look clean by moving a factory across a border while continuing to export to it.

The per capita metric is deliberately absent, and the reasoning is concrete. A small country with heavy industry looks bad on CO2 per capita, which creates an incentive to relocate the factory and export the product instead of the electricity, improving the number without changing anything physical. The README calls per capita an incomplete metric for exactly that reason: it divides by population without counting the emissions of the goods that population imports.

Emission factors are also documented as life cycle rather than stack emissions, covering construction, fuel production, operation and decommissioning. The methodology lives on the company's website rather than in this repository, which is a fair division: the parsing belongs here, the modelling rationale belongs where it can be updated without a code review.

## A community project with four kinds of contribution

The contributing section is unusually concrete about what help is wanted. Four categories are named: build parsers for new countries, fix broken parsers, improve the accuracy of existing data sources, and update region capacities. The fourth sounds trivial and is not, because capacity limits determine how much cross-border flow a model will attribute, and stale capacities quietly distort the result.

Guidance for contributors lives in `CONTRIBUTING.md`, with `DATA_SOURCES.md` and `EMISSION_FACTORS_SOURCES.md` recording where every input comes from. Those two files are arguably the most valuable documents in the repository for anyone auditing the results, since a carbon intensity number is only as good as its provenance. `CODE_OF_CONDUCT.md`, an `Earthfile` and a `.pre-commit-config.yaml` alongside a `.devcontainer/` show a fairly conventional modern Python workflow.

The release notes show what a normal month of maintenance looks like, and it is mostly data corrections: exchange capacity updates for AU and Italy to Slovenia, a capacity change for Japan from 1200 to 2100, an emission factor fix for the ES to MA exchange, retirement of old Indian zones, a retry added to the ESIOS parser for HTTP and connection errors, and 2026 energy data for Uzbekistan. Each of those is a small, verifiable change to a specific parser.

## What is open and what moved behind the login

Two boundaries are worth stating plainly. The map frontend is no longer open source: the README says it used to be, and that the app was rewritten onto the company's own platform to expand features and keep the experience consistent across products. What remains open here is the data layer, not the visualisation.

The API is also not served from this repository. Access is described as a commercial matter handled on the company website, with a data portal for downloading historical datasets. So the practical situation is: the parsers are readable and forkable, the data they collect is reachable through a portal, and the API that serves it to applications is a paid product. For a climate data company that is a coherent split, and for someone who wanted a free real time carbon intensity feed, it is the deal breaker to check before investing time.

The alternatives in this space are worth naming in the same breath. WattTime appears in the search terms alongside this project, and the usual DIY route is building your own estimates from Ember and country level data, which is more work and less accurate at grid resolution. Grid resolution is the whole value here: an hourly, zone-level number is a different product from a yearly national average.

## Packaging details that say how this project is consumed

The build configuration in `pyproject.toml` is worth reading if you plan to depend on the package rather than fork it. Python support runs from 3.10 up to but not including 3.14, which is a deliberately current floor and ceiling. The licence is `AGPL-3.0-or-later`, matching the README's statement that the repository has been AGPL since v1.5.0 and that contributions before commit `cb9664f` were under MIT.

There is a uv workspace with a single member, `libs/types`, and the types package is resolved from that workspace rather than from an index during development. The index configuration is equally deliberate: PyPI is the default for consumers of the published package, while the project's own Artifact Registry at a Google Cloud URL is marked `explicit`, meaning it is only used when a dependency asks for it. A package that is both on PyPI and in a private registry needs that distinction spelled out, and it is.

```toml
[tool.uv.workspace]
members = [".", "libs/types"]

[tool.uv]
required-version = ">=0.4.0"
```

Required tooling is pinned as well, with `ruff` at 0.16.3 in the dev group. The repository has 4034 stars and 1059 forks, 124 open issues, and a last push on 2026-09-21, with the newest release being v1.322.0 from 2026-04-28.

## Conclusion

electricitymaps-contrib is the unglamorous half of a product people see as a map, and it is the half that decides whether the map is right. Each grid operator publishes its own shape of data, and the repository's job is to normalise all of them into production, exchange and price records that a flow tracing algorithm can consume. If you are thinking of using this data, the parsers extra with pandas and lxml installed is the practical entry point, and the historical datasets come from the data portal rather than from running the parsers yourself. If you want to contribute, the maintainers list four jobs: new country parsers, broken parser fixes, source accuracy improvements and region capacity updates, with contribution guidelines in the repository. The last push was on 2026-09-21 and the latest release line is v1.322.0, so the parsers are being maintained on the cadence you would expect from infrastructure.

## FAQ

### How do electricity maps work?

Raw production, exchange and price data is collected from official government and transmission system operator sources, then standardised by the parsers in this repository. That data feeds a flow tracing algorithm, estimation models and a forecast engine, which is what produces the carbon intensity figures shown on the map.

### What does an electricity map show?

It shows the carbon intensity of electricity in each grid zone, calculated for consumption rather than production. That means it attributes emissions to the country where the electricity is used, and it deliberately leaves out emissions per capita, since that metric can be improved by moving factories rather than by changing the grid.

### How do I find out where my electricity comes from?

The map at app.electricitymaps.com breaks a country's electricity down by zone and by source, and shows the resulting carbon intensity. For historical data rather than a live view, the project also offers a data portal where datasets can be downloaded, and the parsers behind the current figures are open source in this repository.

## Sources

- [electricitymaps/electricitymaps-contrib on GitHub](https://github.com/electricitymaps/electricitymaps-contrib)
- [License: AGPL-3.0](https://github.com/electricitymaps/electricitymaps-contrib/blob/master/LICENSE)
- [Project website](https://app.electricitymaps.com)
- [README](https://github.com/electricitymaps/electricitymaps-contrib/blob/master/README.md)
- [Releases](https://github.com/electricitymaps/electricitymaps-contrib/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/electricitymaps-electricitymaps-contrib
