The previous chapters described WIA-ENE-057 one layer at a time: the record types of PHASE 1, the REST resources of PHASE 2, the streaming and messaging rules of PHASE 3 and the external connectors of PHASE 4. A working monitoring platform has to hold all of those at once, on hardware that may sit in a data centre, in a regional office with an unreliable power supply, or in a solar-powered cabinet at the edge of a degraded rangeland. This chapter turns the specification into an architecture. It shows how the components fit together, which deployment profile suits which organisation, what changes when the field link is a slow satellite modem, how the data pipeline should behave when the same reading arrives twice, and how to run the system month after month without losing the evidence an auditor will ask for. It closes with a walkthrough of one season at the running example site and a checklist of the mistakes that most often corrupt land-degradation data.
Two reminders apply throughout. First, WIA-ENE-057 standardises data shapes, interfaces and evidence; it does not prescribe a database, a cloud provider or a programming language, so product names below are examples of a component class, not requirements. Second, every number taken from the specification's JSON examples β NDVI values, rainfall totals, tree counts, risk scores β is an illustrative example value for the fictional SAHEL-REGION-001 site, not a measurement of any real place.
A WIA-ENE-057 deployment can be described as six layers. Data enters at the edge, is accepted and normalised by the ingest layer, lands in storage that matches its shape, is turned into indicators by the processing layer, and leaves through the serving layer and the integration adapters. Figure 7-1 shows the layers and the specification sections that govern each boundary.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INTEGRATION ADAPTERS LDN report builder Β· EO downloaders Β· GIS feeds β PHASE 4
β field-app sync Β· carbon-registry export β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SERVING REST /api/v1/... Β· WSS stream Β· OGC WMS/WFS/WCSβ PHASE 2 / 3
β webhooks (X-WIA-Signature) Β· discovery document β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PROCESSING index extraction Β· zonal statistics Β· trends β
β alert rules Β· risk scoring Β· report assembly β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββ€
β STORAGE β time-series store β object store β
β spatial DB (PostGIS β sensor readings, β GeoTIFF / NetCDF β
β class): locations, β device telemetry β rasters, photos, β
β polygons, records β β evidence packages β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββ€
β INGEST MQTT broker Β· HTTPS record API Β· bulk upload β PHASE 3 Β§3, Β§7
β satellite multipart ingest Β· raster pipeline β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β EDGE soil & weather sensors Β· gateways Β· ODK forms β
β mobile SDK (offline) Β· manual lab entries β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The edge layer contains everything that produces observations: in-situ soil moisture and temperature probes, rain gauges and weather stations, gateways that aggregate several probes, and people. Field staff and community monitors contribute through ODK-style survey forms and through a mobile client that works offline and synchronises when it regains a connection (PHASE 4 Β§10). Laboratory results for soil chemistry usually arrive days or weeks after sampling and are entered by hand. The edge layer's responsibility is modest but important: stamp each observation with a UTC time and a locationId, keep it until the ingest layer acknowledges it, and report its own health (battery, signal, firmware).
Four entry points cover the traffic described in PHASE 3. The MQTT broker receives sensor messages on topics of the form wia-ene-057/{locationId}/{sensorType}/{metric}. The HTTPS record API accepts complete PHASE 1 records, such as a vegetationData document posted to /api/v1/monitoring/vegetation. The bulk upload endpoints (/api/v1/bulk/upload/initiate, β¦/chunk, β¦/complete) handle backlogs and historical imports. The satellite ingest endpoint (/api/v1/ingest/satellite) takes a GeoTIFF or NetCDF file together with a JSON metadata part that names the product, acquisition date, cloud cover and coordinate reference system. Behind these sits the raster pipeline that PHASE 4 Β§4.2 describes for MODIS: download, extract the NDVI/EVI layers, reproject, clip to the area of interest, compute zonal statistics, store, and trigger trend analysis.
All four entry points should converge on one validation stage that applies the PHASE 1 Β§7.2 rules β NDVI between β1 and 1, soil moisture between 0 and 100 % with a review flag above 60 %, non-negative rainfall, valid coordinates β before anything is written. Keeping validation in one place means a gateway, a spreadsheet import and a mobile form cannot disagree about what a valid record is.
The data has three distinct shapes, and forcing them into one store usually hurts at least one of them. Sensor readings are narrow, append-mostly time series; a time-series database or a partitioned relational table indexed by (locationId, deviceId, timestamp) suits them. Locations, project polygons, parcels and the PHASE 1 records themselves have geometry and relationships; a spatial database of the PostGIS class lets the platform answer "which records fall inside this restoration area" without exporting data to a desktop GIS. Rasters, site photographs and signed evidence packages are large binary objects; an object store holds them, while the spatial database keeps only their metadata, checksums and footprints. Cloud-Optimized GeoTIFF is a sensible default raster layout because it lets the OGC WCS service and analysis jobs read windows of a file without downloading all of it.
Processing jobs turn raw inputs into the fields that PHASE 1 records and PHASE 2 responses expose: index values per location and date, trendAnalysis blocks, erosion estimates, the four-factor risk score of the composite monitoringReport, and the aggregated tables of an LDN report. These jobs are event-driven: a new record emits data.created (PHASE 3 Β§6.1), and subscribers recompute what depends on it. Section 7.4 describes that loop in detail.
The serving layer exposes the PHASE 2 REST resources, the WebSocket stream, the OGC services and outbound webhooks, and publishes the discovery document at /.well-known/wia/desertification-prevention. Integration adapters are the PHASE 4 connectors: Earth-observation downloaders, the LDN report builder, GIS feature services, field-app synchronisation and carbon-registry export. Treat each adapter as a separate deployable unit with its own credentials and its own health status, so that a failing upstream archive does not stall the REST API. The integrationHealth document in PHASE 4 Β§12.2 is a reasonable shape for publishing that status.
The reference CLI (cli/desertification-prevention.sh profiles) lists four conformance profiles. They describe who shares a runtime and how evidence leaves it, and the choice has consequences for isolation, auditing and connectivity.
| Profile | CLI description | Typical fit | Main design concern |
|---|---|---|---|
single-tenant | one runtime per organization | A national forest or land agency running its own monitoring platform | Keeping the one runtime patched and its evidence published on schedule |
multi-tenant | shared runtime, header-based isolation | A service provider hosting several NGOs or project developers | Proving that one tenant can never read or overwrite another's records |
federated | peer-to-peer mesh of runtimes | A consortium of agencies or NGOs across a region, each keeping its own data | Agreeing identifiers and profiles; publishing an aggregated manifest |
air-gapped | batch evidence package, no live connectivity | A remote field station or a sensitive national system with no live link | Moving signed packages out-of-band without breaking the chain of evidence |
Single-tenant is the simplest profile to reason about. One organisation owns the runtime, the signing key and the data, and an auditor can examine the whole system. It fits a government agency that must keep monitoring data on national infrastructure.
Multi-tenant deployments share a runtime and isolate tenants by a request header. The specification does not name the header, and a header on its own is only a claim. In practice the tenant must be derived from, or checked against, the authenticated token: a request whose tenant header disagrees with the token's organisation claim should be rejected, not quietly re-routed. Tenant scoping belongs in the data-access layer (for example row-level security keyed by tenant) rather than in each endpoint, and every negative isolation test β tenant A reading tenant B's locationId β belongs in the test-vector set described in Chapter 8.
Federated deployments suit consortia in which each member keeps authority over its own records. Annex I of the PHASE documents asks federations to publish an aggregated manifest that summarises the union and intersection of member profiles, so that a caller can route a request to the least common denominator. The hard part is not technical: members must agree on how locationId values are minted (a member prefix avoids collisions), which record types each member serves, and how duplicated restoration sites are reconciled.
Air-gapped deployments have no live connection. Records, rasters and evidence are exported as a signed batch package, carried out by courier, removable media or an occasional link, and imported elsewhere. The package must be verifiable at the destination without contacting the origin, so it should carry its own manifest, checksums and signature, and the importer should refuse any package whose signature or checksums fail. A field station with an intermittent link often sits between the connected and air-gapped profiles; the store-and-forward techniques in the next section let it behave like a connected edge most of the time and like an air-gapped site when the link is down.
Whatever the profile, Annex E of each PHASE document asks implementations to declare their operational scope in the OpenAPI document so that auditors score the deployment against the right expectations. Changing profile later β say, from single-tenant to federated β is a deliberate event: publish a new profile manifest and keep the previous one valid and signed for the deprecation window, as Annex I describes.
Desertification monitoring happens where infrastructure is thinnest. A design that assumes an always-on broadband link will lose data in exactly the places that matter most. Four techniques make the difference.
Each device and gateway keeps a durable local queue and deletes an entry only after the next hop acknowledges it. On a gateway this is usually a small embedded database or an append-only file on flash storage; on a mobile client it is the offline store of the SDK, synchronised at an interval (the PHASE 4 example uses syncInterval: 3600000, one hour). Queues should be bounded, and when they fill the device should drop the least valuable data first β routine telemetry before measurements β and record that it did so. A silent gap is worse than a documented one, because an analyst cannot tell it from a sensor that read zero.
PHASE 3 Β§3.1 maps QoS levels to data classes: QoS 0 (at most once) for routine telemetry, QoS 1 (at least once) for important measurements, QoS 2 (exactly once) for critical alerts. On a lossy link these choices have costs. QoS 1 guarantees delivery but may deliver duplicates after a reconnect, so the ingest layer must deduplicate β which it should do anyway (Section 7.4). QoS 2 adds a four-message handshake per publish; on a slow link it is often cheaper to send alerts at QoS 1 with a unique identifier and let the idempotent ingest remove duplicates. Use persistent sessions so that the broker holds messages for a gateway that has briefly disconnected, and keep the gateway's client identifier stable across reboots, otherwise the session is lost.
For backlogs β a week of readings after a link failure, or a season of field forms β the bulk protocol of PHASE 3 Β§7 is better than thousands of individual posts. The client announces the record count and size, receives an uploadId and a chunk size, sends numbered chunks, and completes the upload with a list of checksums. The specification's example shows placeholder strings ("sha256-chunk1"); a real client sends the SHA-256 digest of each chunk, and the server verifies each digest before accepting the chunk, so that a chunk corrupted in transit is re-requested rather than silently stored. Because every chunk is numbered, an interrupted upload can resume from the first missing chunk instead of starting again. The status endpoint reports per-record failures such as "Invalid NDVI value: 1.5"; the client should keep those rejected records for correction, not discard them.
The MQTT message format carries a metadata block with battery, signalStrength and firmware. Treat these as first-class data. A slowly falling battery level warns of a failing solar panel before readings stop; a sudden change in soil moisture that coincides with a firmware update is more likely a calibration change than a rainfall event. Store telemetry alongside readings and make firmware version a dimension of every quality check.
Every guarantee about ordering, trend windows and alert timing depends on clocks. PHASE-2 API-INTERFACE Β§5 requires synchronised clocks (NTPv4, stratum-2 or better), and the Annex E notes recommend PTP (IEEE 1588) only where deterministic interlocks are needed, which is rarely the case in land monitoring. Connected servers and gateways should run an NTP client against at least two independent sources. A remote gateway without internet can use its GPS receiver as a time source; if the receiver reports GPS system time rather than UTC, convert by applying the published leap-second offset, since GPS time does not include leap seconds. Battery-powered sensors that wake briefly should take their time from the gateway on each wake cycle. Whatever the source, record timestamps in UTC with an explicit Z suffix, and have the ingest layer reject or flag readings stamped implausibly far in the future β the classic symptom of a sensor whose real-time clock reset to a default date.
The processing layer is a loop: a record arrives, is validated and stored, an event announces it, dependent results are recomputed, and alerts are raised if a rule fires. Three properties keep that loop trustworthy.
Every PHASE 1 record carries an identifier β VEG-20251225-SAHEL-001, SOIL-20251225-SAHEL-001, RAIN-2025-SAHEL-001 β and that identifier should be the key of an upsert, not a hint. Receiving the same record twice must leave the store in the same state as receiving it once. For MQTT readings, which have no record identifier, use a natural key such as (deviceId, timestamp, metric). A second delivery with identical content is acknowledged and ignored; a second delivery with the same key but different content is a correction and should be stored as a new version with the previous version retained, because auditors and the LDN baseline both depend on being able to see what was known at a given time.
After a successful write the pipeline emits an event in the PHASE 3 Β§6.1 format (data.created or data.updated, with resourceType, resourceId and locationId). Consumers subscribe by resource type: a new vegetation record triggers trend recomputation for its location; new soil or rainfall data can trigger a new risk assessment; a completed assessment emits assessment.completed. Recomputation should be debounced per location β when a bulk upload delivers a thousand readings for one site, the trend should be recomputed once after the batch, not a thousand times. Carry the correlationId from the triggering event into everything derived from it so that an alert can be traced back to the exact records that caused it.
Derived values must be recomputed from their inputs, not copied from example payloads. The composite risk score is a useful test. In the PHASE 1 Β§6.1 example, the factor scores are 35.0, 72.0, 85.0 and 90.0 with weights 0.30, 0.25, 0.25 and 0.20. The weighted sum is 10.5 + 18.0 + 21.25 + 18.0 = 67.75, while the example's overallScore reads 68.5. An implementation that recomputes will disagree with the example, and that is correct: the example is internally inconsistent. The specification also does not define numeric thresholds for its risk categories, so any bands a platform uses to label a score "high" are local policy and should be documented as such.
Alerts compare a recent change against a threshold. The PHASE 2 webhook example is a good template because its numbers agree with each other: NDVI fell from 0.45 to 0.35 over 90 days, a change of β0.10 against a threshold of β0.05, which is a relative decline of about 22 %. The evaluator below implements that rule. It only uses observations that passed quality control, compares like with like (it refuses to mix composite and daily sources), requires a minimum number of observations in the window, and suppresses repeat alerts during a cool-down period. The severity mapping is an example policy, not a normative rule.
from dataclasses import dataclass
from datetime import date, timedelta
@dataclass
class Obs:
day: date
value: float # NDVI, valid range -1..1
source: str # e.g. "MOD13Q1" (16-day composite)
quality: str # "high" | "medium" | "low"
@dataclass
class Rule:
metric: str = "ndvi"
window_days: int = 90
threshold: float = -0.05 # absolute change that triggers
min_obs: int = 3
cooldown_days: int = 30
def evaluate(obs, rule, today, last_alert_day=None):
"""Return an alert dict or None. Illustrative policy, not normative."""
start = today - timedelta(days=rule.window_days)
usable = [o for o in obs
if start <= o.day <= today
and o.quality in ("high", "medium")
and -1.0 <= o.value <= 1.0]
sources = {o.source for o in usable}
if len(sources) != 1:
return None # never mix composites with daily data
if len(usable) < rule.min_obs:
return None # not enough evidence in the window
usable.sort(key=lambda o: o.day)
first, last = usable[0], usable[-1]
change = round(last.value - first.value, 4)
if change > rule.threshold:
return None
if last_alert_day and (today - last_alert_day).days < rule.cooldown_days:
return None # suppress repeats
relative = change / first.value if first.value else None
severity = "critical" if change < 2 * rule.threshold else "high"
return {
"type": "rapid-vegetation-decline",
"severity": severity,
"details": {
"metric": rule.metric,
"previousValue": first.value,
"currentValue": last.value,
"change": change,
"changePeriod": f"{(last.day - first.day).days} days",
"threshold": rule.threshold,
},
"relativeChange": None if relative is None else round(relative, 3),
}
With the example series (0.45 on 1 October, 0.40 on 1 November, 0.35 on 1 December 2025), the change is β0.10 and the relative change β0.222, matching the webhook message. The code is deliberately plain so that a land manager can read the rule as easily as a developer. Two refinements are worth adding in production. First, compare against the same period in previous years as well as against the start of the window, because in the Sahel a fall in NDVI between October and December is largely the normal end of the rainy season, not degradation. Second, record which rule version fired, so that alerts remain explainable after thresholds are tuned.
When an alert fires, the pipeline emits alert.triggered, delivers it to subscribers such as SUB-20251225-001 with an X-WIA-Signature header, and retries failed deliveries with the exponential back-off of PHASE 3 Β§9.1. Deliveries to a persistently failing endpoint should be parked behind a circuit breaker and surfaced to the subscriber, not retried forever.
A monitoring platform is judged over years, not at launch. The operational practices below come from the Annex E notes and PHASE 4 Β§11βΒ§12, with the caveat that the SLA figures in the specification are design targets, not measured performance of any existing service.
Annex E asks implementations to publish a monthly error-budget summary covering p95 latency, error rate and violation hours, without exposing tenant-specific data. PHASE 4 Β§12.1 sets the targets: 99.9 % availability, p95 response time under 200 ms, real-time data latency under five minutes, error rate under 0.1 %. A 99.9 % availability target leaves 43.2 minutes of downtime in a 30-day month; making that arithmetic visible helps teams decide whether a planned maintenance window is affordable. A useful monthly summary contains, per service:
| Measure | Target (spec design target) | How to measure |
|---|---|---|
| Availability | 99.9 % | Successful synthetic probes Γ· total probes, per endpoint group |
| Response time | p95 < 200 ms | Server-side latency histogram, excluding bulk and raster downloads |
| Data latency | < 5 min for real-time data | Ingest time minus observation timestamp, for connected devices only |
| Error rate | < 0.1 % | 5xx responses Γ· all responses; 4xx reported separately |
| Violation hours | reported, no target | Hours in which any of the above missed its target |
Measure data latency only for devices that are supposed to be connected. Readings released from a store-and-forward queue after a two-day outage are a success of the design, not a latency violation, and mixing them in makes the metric meaningless.
Each PHASE 4 adapter should report its last successful synchronisation, success and failure counts, and a data-quality indicator, in the shape of the integrationHealth example. The example values in the specification (sync success 100.0, 245 downloads, 15 shared projects) are illustrations; the useful practice is alerting on staleness β an Earth-observation downloader whose last success is older than the product's revisit cycle is broken even if it reports no errors.
Annex E gives a seven-year retention window as a baseline for audit expectations under ISO/IEC 17065 and says that where a regulator requires longer, the deployment policy must extend it. For land-degradation work there is a second reason to keep data longer: LDN assessments compare a reporting period against a baseline, and trend analysis needs long series. Distinguish three classes. Signed evidence (manifests, test results, SBOMs) and the PHASE 1 records must be kept for at least the audit window and ideally for the life of the programme. Derived products (trends, risk scores) can be regenerated, but keep the versions that were published or reported. Raw sensor telemetry can be downsampled after an agreed period once daily aggregates are stored.
PHASE 4 Β§11.1 sets API-key rotation at 90 days with a 7-day expiry warning, storage in an encrypted vault and role-based access. Automate the warning: a field gateway whose key silently expires stops reporting on a weekend and fills its queue. Manifest-signing keys follow the deployment policy; when one is rotated, the old public key stays in a published key history so that past manifests remain verifiable.
Records and rasters need different protection. PHASE 1 records, field forms, photographs and lab results are often irreplaceable β a soil sample taken in 2025 cannot be taken again β so they need regular, tested, off-site backups. Most satellite rasters can be downloaded again from the provider's archive, provided you recorded exactly which product, collection version, tile and date were used; back up that provenance and your own derived rasters, and accept that the raw scenes may be re-fetched. Be aware that providers reprocess archives into new collection versions, so a re-downloaded scene may differ slightly from the one originally used. A restore test that rebuilds one location's full history from backups at least once a year is the only reliable proof that the backup works.
The walkthrough below follows one illustrative season at the running example site through the architecture. All identifiers and values come from the specification's examples and are illustrative; some dates are chosen by this book to place the steps in order. The site is registered at latitude 14.5, longitude β4.0 with an area of 10,000 ha.
| When (illustrative) | Step | Interface | Record or event |
|---|---|---|---|
| Before the season | Register the location with its coordinates, area and baseline | POST /api/v1/locations | SAHEL-REGION-001, status active |
| Daily | Soil probes publish moisture and temperature; gateway forwards | MQTT wia-ene-057/SAHEL-REGION-001/soil/moisture | Readings from SENSOR-SOIL-001 (e.g. 12.5 % at 0β10 cm) |
| Every 16 days | New MOD13Q1 composite is downloaded, clipped and summarised | Raster pipeline; POST /api/v1/ingest/satellite | vegetationData such as VEG-20251225-SAHEL-001 |
| End of wet season (JuneβSeptember) | Seasonal rainfall summary assembled from gauges | Record API | Contributes to RAIN-2025-SAHEL-001 (annual record) |
| 1 Oct β 1 Dec 2025 | NDVI series 0.45 β 0.40 β 0.35 | Trend recomputation | β¦/vegetation/SAHEL-REGION-001/trend?period=90d |
| 25 Dec 2025 | Alert rule fires (β0.10 vs β0.05 over 90 days) | Webhook with X-WIA-Signature | ALERT-20251225-001 to SUB-20251225-001 |
| 25 Dec 2025 | Restoration progress updated for the green-belt project | PUT /api/v1/restoration/plan/{planId}/progress | Project REST-2025-SAHEL-001 |
| End of Q4 2025 | Quarterly composite report assembled | Report job | REPORT-2025-Q4-SAHEL-001 |
| After year end | Annual LDN-style report generated for the reporting period | POST /api/v1/reports/unccd-ldn | Report for 2025-01-01 to 2025-12-31 |
Registration. The location record fixes the identity every other record will reference. The baseline block in the PHASE 2 example includes desertificationRisk: 68.5; since the corresponding composite example recomputes to 67.75, a careful implementation stores its own computed baseline and notes the source.
Daily soil data. The gateway publishes each probe reading at QoS 1. During a two-day outage in August the gateway queues readings; when the link returns they arrive late, are deduplicated on (deviceId, timestamp, metric), and are excluded from the data-latency metric. A reading above 60 % would be flagged for review under PHASE 1 Β§7.2; in a sandy soil such a value usually indicates a probe fault or waterlogging at the sensor rather than true field moisture.
Vegetation composites. Every 16 days the Earth-observation adapter fetches the new MOD13Q1 tile, extracts NDVI and EVI, reprojects and clips to the site polygon, and writes zonal statistics as a vegetationData record with source, resolution and cloud-cover metadata. The record's timestamp should be the composite period it represents, not the download time.
Rainfall. Gauge readings accumulate through the wet season. The annual RAIN-2025-SAHEL-001 example reports a total of 325.4 mm with 285.2 mm in JuneβSeptember; these are example values. Its SPI of β1.8 would fall in the "severely dry" class of the usual McKee classification, not the "moderate-drought" label the example uses, so derive the category from the index rather than copying the label.
Alert. The NDVI trend over the 90 days to December triggers the rule from Section 7.4. The webhook is delivered to the subscriber, whose endpoint verifies the HMAC signature before acting. As noted, the OctoberβDecember decline coincides with the end of the rainy season, so the alert handler should present a same-season comparison with earlier years before anyone concludes that degradation has accelerated.
Restoration progress. The restoration team records progress against the plan. The PHASE 2 example uses a plan identifier (REST-PLAN-20251225-001) that differs from the project identifier in PHASE 1 (REST-2025-SAHEL-001); an implementation should store an explicit link between plan and project instead of assuming they are the same string.
Quarterly and annual reporting. At the end of the quarter the report job assembles REPORT-2025-Q4-SAHEL-001 from the stored records, recomputing the risk score and listing the alerts raised in the period. After the year closes, the LDN report endpoint produces the annual package for the reporting period. As Chapter 6 explained, that package supports, but does not replace, national reporting through the UNCCD's own process.
Most data-quality failures in land-degradation platforms are not exotic. The same handful recur, and each has a cheap check. The checklist below is a good basis for code review and for the negative test vectors of Chapter 8.
| Pitfall | What goes wrong | Check |
|---|---|---|
| Axis order | GeoJSON uses longitude, latitude; the PHASE 1 coordinates object names latitude and longitude explicitly; some OGC requests with EPSG:4326 expect latitude first. Swapping them moves SAHEL-REGION-001 (14.5, β4.0) to a point south of the equator in Central Africa, thousands of kilometres away. | Test that every write path round-trips the reference point to the same place; reject latitudes outside Β±90. |
| Unit mismatch | Soil nutrients labelled "ppm" in one record and percent in another; biomass in tonnes per hectare against carbon in tonnes of COβ. | Require an explicit unit on every quantitative field and convert on ingest to one canonical unit per quantity. |
| Percentage versus fraction | The spec mixes both: confidence: 0.92 is a fraction, cloudCover: 5 and survivalRate: 82.5 are percentages. A fraction stored as a percentage turns 92 % confidence into 0.92 %. | Name the convention per field in the schema; validate ranges (0β1 or 0β100) accordingly. |
| NDVI range | The example labels NDVI scale: "0-1", while the validation rule allows β1 to 1; clamping negatives to 0 hides water and bare wet surfaces. | Accept β1..1 as the rule states; never clamp. |
| Mixing temporal resolutions | Comparing a 16-day MOD13Q1 composite with a daily or single-scene value produces spurious jumps. | Carry source and compositing period on every record; compute trends within one product only. |
| Coverage totals | Land-cover shares that do not sum to 100 %, or built-up shares that disagree between record types for the same site. | Validate sums with a small tolerance; never combine shares from different record types. |
| Double counting trees | The same planting reported under several programmes (a national effort, a regional initiative, a tree-planting network) is counted once per programme. | Give each planting a stable identifier and link programmes to it, rather than copying counts into each programme's record. |
| Time zones and date-only fields | erosionData.assessmentDate is date-only ("2025-12-25"), while the risk API's assessmentDate is a UTC timestamp; converting a local date to UTC can shift it to the previous day. | Keep date-only fields as calendar dates; store timestamps in UTC with Z; never convert one into the other implicitly. |
| Copied derived values | Storing an example overallScore or tree count instead of computing it. | Recompute derived fields from inputs and test against hand-worked examples such as the 67.75 score above. |
A final pitfall is organisational rather than technical: treating the specification's example numbers as reference data. The book has flagged several internal inconsistencies β risk score, SPI label, carbon totals, tree counts β precisely so that implementers build tests on arithmetic and definitions, not on values that happen to appear in a JSON sample.
SAHEL-REGION-001 shows how registration, sensor data, composites, rainfall, alerts, restoration progress and reports connect β and where the specification's own examples need correcting.