Independent AI agent for EU Deforestation Regulation compliance, not affiliated with the EU. Scope & disclaimer →
eudr.dev

Methodology

How the engine reaches a per-plot verdict. The data sources, the pipeline, the decision rules, and what the methodology does not claim.

Our implementation choice for the questions Reg. (EU) 2023/1115 asks, not a regulator-approved methodology. The operator stays the duty-bearer under Arts. 4 and 8–12.

The three upstream Earth-observation datasets the engine consults (Hansen GFC, ESA WorldCover, JRC GFC2020) are served via emem. The open-source Earth-memory protocol. Every band, every cell, every fact is signed upstream and content-addressed; the engine reproduces those signatures verbatim in the DDS receipt so the chain is verifiable end-to-end.

This page explains, in regulator-readable language, exactly what data the engine consults and how it converts those data into the verdict that lands in the Annex II Due Diligence Statement.

The question the engine answers

For each plot of land in a consignment, the engine answers:

Was this plot deforested on or after 2020-12-31, in the sense of Art. 2(13) of Reg. (EU) 2023/1115?

The output is one of three labels per plot:

signal); the operator is notified to supply additional evidence.

A consignment is "negligible risk" overall when every plot is negligible. If any plot fails, the consignment fails.

The three upstream data sources

The engine consults three open Earth-observation datasets. Each one is cited verbatim in the signed receipt, so anyone can re-derive the verdict on their own machine without trusting our infrastructure.

1. Hansen Global Forest Change (UMD)

Authoritative source for annual forest-loss data from 2001 onward. The University of Maryland (Hansen et al.) publishes a global 30-metre raster keyed by year of loss. The version a given statement read is on that statement's own receipt, under source_receipts.emem.forest_baseline_dataset and in each fact's sources[].scheme. At the time of writing (2026-10-11) the responder serves v1.13; this page said v1.12 for a year after that changed, which is why the version now lives on the receipt and not in prose.

Two bands matter:

(2000). Used to confirm the cell was forest at the time-zero baseline before the engine inquires about loss.

if it was. lossyear == 21 means 2021; lossyear == 0 means no loss recorded.

A cell is flagged post-cut-off when treecover2000 > 0 AND lossyear >= 21. The engine carries the raw lossyear value verbatim into the receipt so the auditor sees the integer Hansen returned.

2. ESA WorldCover 2021

Authoritative source for land-cover class as of 2021. The European Space Agency publishes a global 10-metre raster classifying every pixel into one of eleven classes. Tree cover, shrubland, grassland, cropland, built, bare, snow/ice, permanent water bodies, wetland, mangrove, moss/lichen.

WorldCover does not date anything, and the engine does not ask it to. A 2021 land-cover raster says what a pixel is, not when it became that. A pixel classified non-forest in WorldCover 2021 is equally consistent with clearance in 1998 and with clearance in March 2021 — the second of which is deforestation after the cut-off and therefore squarely in scope. An earlier version of this page argued the opposite, that a non-forest 2021 pixel must have lost its forest before 2020-12-31. That inference is invalid and it was never what the deployed engine did.

Dating is what the sources are for: Hansen's loss_year and the JRC GFC2020 baseline at the cut-off date decide, and JRC Tropical Moist Forest's deforestation_year is carried as a cross-check, not as a verdict input — a statement shows it under source_receipts.emem.per_plot[].tmf_cross_check with its own agreement count. WorldCover is carried on the receipt as a complementary source, not an underlying model — you can see the split on any signed receipt, under methodology.underlying_models versus methodology.complementary_sources.

What WorldCover is good for here:

The land-cover class is how the engine knows whether a cell was forest to begin with, which is what makes a scope decision possible at all.

many WorldCover pixels, so the dominant class is robust to single-pixel noise.

WorldCover also calls cropland, is a more legible finding than one where the two disagree. Disagreement is surfaced, not resolved silently.

The engine surfaces the WorldCover dominant class per cell in the report's land-cover panel.

3. JRC Global Forest Cover 2020

The Joint Research Centre's forest baseline at the EUDR cut-off date. The release a statement read is named on that statement, under conclusion's forest_baseline and in the facts' source URLs; the responder serves V4 at the time of writing (2026-10-11), and this page named V3 well after that. It is the Commission-funded implementation of what the Regulation considers "forest" on 2020-12-31. It uses a different signal mix than Hansen (Sentinel-1 + Sentinel-2 + Landsat) and a different definition of "forest" (>= 10% canopy cover + >= 0.5 ha + >= 5 m height, the FAO definition the Regulation references).

The engine cross-checks Hansen against JRC GFC2020 for every cell, when the two agree, the verdict is high-confidence. When they disagree, the receipt records both verbatim and the engine emits a "baseline disagreement" trace event so the operator can investigate.

(removed) RADD Sentinel-1 alerts

The RADD Sentinel-1 near-real-time alert layer (Wageningen Univ.) was previously consulted as a fast-moving complement to Hansen's annual update. emem retired RADD from its /v1/eudr_dds response in favour of Hansen's annual rhythm plus the JRC GFC2020 baseline. The engine no longer claims RADD as an input; the per-cell receipt and the report drop the corresponding column / panel accordingly.

If a fast-moving alert layer is re-introduced (RADD via a different channel, or an alternative Sentinel-1 product), it will be added back as a fourth source with explicit receipt provenance.

How a polygon turns into evidence

The Regulation requires geolocation in GeoJSON polygons. The engine's pipeline takes that polygon and:

  1. Computes the polygon's bounding box and the polygon's centroid.
  2. Resolves the bbox to a set of emem cell64 cells. emem uses a

custom 64-bit Hilbert-ordered geospatial code (21 bits latitude, 22 bits longitude) at a canonical resolution of ~9.55 m at the equator. Cells are serialised as four dot-separated base-1024 bigrams (e.g. defi.zb493.xoso.zcb6a) so string prefixes preserve spatial locality. The deployment exposes a single resolution today, migrating toward H3-res-13 (~3.4 m) per emem's published spec.

  1. Evaluates the cells, up to a per-plot budget of 51 200

(configurable via EMEM_MAX_CELLS_PER_PLOT). At roughly 91 m² per cell that is a full sweep of any plot up to about 465 hectares, so in practice a smallholder plot is evaluated exhaustively rather than sampled: a 0.4 ha plot resolves to around 44 cells and a 30 ha plot to around 3 300. The budget only binds on plots larger than that, and when it binds the receipt says so in sampling_note and reports sampled_polygon_fraction below 1.0. Selection is deterministic — a stratified sweep across the polygon — so re-running the same scenario produces the same evidence.

This figure was 16 in an early deployment, which gave about 0.5 % area coverage on a 30 ha plot: enough to be verdict noise rather than a verdict. If you are reading a receipt issued under that configuration, total_cells on it will say so.

  1. For each sampled cell, queries emem.dev for the four signals

above. emem returns each signal as a content-addressed fact with a CIDv1 anchor so the engine never has to trust an opaque API response. Every signal is verifiable by re-deriving the CID from the canonical bytes.

  1. Aggregates the per-cell signals into the plot verdict using the

logic described above.

Why per-cell sampling, not whole-polygon analysis

The Regulation cares about the polygon's status, not any one pixel's. We could in principle aggregate at polygon level (one "forest fraction" number across the whole plot). We deliberately don't, because:

triggered the flag, not a single aggregate. A per-cell receipt lets the operator look at the exact sub-area, send a field worker, and resolve the question.

loss inside a 100-hectare plot becomes a 1% noise floor; per-cell sampling surfaces the same loss as one flagged cell among many.

identifier verbatim, so the auditor can fetch the same cell from emem.dev next year and compare.

The trade-off is that per-cell evidence is verbose. The receipt for a 4-hectare plot may carry 32 cells × 4 signals = 128 fact CIDs. That is the verbosity at the EUDR's evidentiary bar.

The seven pipeline stages

The signed receipt records the pipeline as a 7-stage DAG. Each stage emits structured trace events that flow into the receipt's stage_traces block so the auditor sees exactly what ran and how long it took.

StageInputsOutputs
1. geoscenario JSONper-plot bbox, centroid, cell64, smallholder flag
2a. forestper-plot cellsper-cell Hansen + ESA + JRC facts
2b. snapshotsper-plot bbox + centroidper-cell NDVI / SAR / JRC GSW + per-plot soil / weather / elevation
3. legalitycountry, commodity, suppliertier (Implementing Reg. 2025/1093), simplified-DD flag
4. tenure(plug-in module)tenure-rights findings or empty
5. riskforest + legalitycomposite risk score 0–1 + verdict label
6. ddsrisk + scenarioAnnex II JSON document
7. settleDDS verdictsettlement record (free-tier: no charge)

The DAG is fully observable. Calling GET /a2a/tasks/<id>/events streams the stage start/done events in real time over SSE; the final receipt carries the same events as a structured array.

Decision rules. What flips the verdict

The composite verdict label is derived from the per-cell evidence plus the country tier. The rules are:

  1. Any post-cut-off Hansen loss → non_negligible. A single

sampled cell with lossyear >= 21 flips the plot. This is the strongest signal and the cheapest one to verify.

  1. JRC GFC2020 disagreement → indeterminate (escalation). When

Hansen says "no loss" but JRC says "non-forest in 2020", the plot is flagged for review. The receipt carries both verbatim.

  1. No evidence retrieved → indeterminate. Happens when emem

has no coverage for the region (rare at the equator, common near the poles). The operator is notified to supply additional evidence.

  1. All cells pass + country tier is low-risk → negligible (Art. 13).

Simplified due diligence applies; the operator can skip the Art. 10/11 risk-assessment + mitigation steps.

  1. All cells pass + country tier is standard or high-risk → negligible.

The DDS is generated but the operator still owes the Art. 10/11 risk-mitigation paper trail under the standard regime.

The threshold is not one cell. It was, and this page went on saying so after the engine stopped: the responder applies the Article 2(4) minimum mapping unit, so post-cut-off loss smaller than the 0.5 ha that the forest definition requires before land is forest at all does not make the plot non-compliant. The per-plot verdict says below_mmu when that is what happened, the statement reports the loss it found and what that loss amounts to (loss_assessment), and a sub-floor loss is still carried into the risk assessment rather than discarded. Losses below the floor on several plots of one consignment are added up: when they reach the floor together, the consignment is not clean, it is unsettled.

Loss on land the JRC baseline did not class as forest on 2020-12-31 is not deforestation under Article 2(13), whatever its size, and is reported as such rather than counted.

What the methodology does NOT claim

Hansen + JRC are agnostic. They detect canopy loss, not who or what removed the trees. The Regulation in turn defines deforestation in land-use terms, not driver terms, so this is acceptable.

land. Tenure, land registration, FPIC, and indigenous-rights questions are out of scope of this baseline. A partner provider can plug in via the LegalityProvider trait.

claims. Those belong in a separate audit; the Regulation references them in Art. 2(40) but does not require this engine to adjudicate them.

2020-12-31. That land is not the Regulation's concern.

Refresh cadence

SourceUpdateEngine impact
Hansen GFCAnnual (typically March of year N+1)Receipt's Hansen vintage is recorded; new compiles use the new vintage.
ESA WorldCoverNew version expected 2025+Engine pins WorldCover 2021 for the EUDR baseline; new versions are evaluated before adoption.
JRC GFC2020One-off baseline at 2020-12-31Static; no update planned.
Country tierWhen Commission publishes a new Implementing Reg.Engine reads from data/country_benchmarks_2025_1093.json; deployer can hot-edit without restart.

Reproducibility

Every receipt the engine signs carries:

An auditor can take a receipt issued today, re-fetch the same cells from emem.dev one year later, and recover the same per-cell facts. Hansen's annual update may add a new loss year (which would change a future compile) but the historical facts are immutable. That's the point of content-addressed evidence.

See also