-
-
At the field: one quick check, what each result means, and a form. The finding becomes new evidence.
-
Each case is sealed with SHA-256 and a Merkle tree. All 68 cases roll up into one area root that can be anchored on Ethereum Sepolia.
-
Bhumi tells field officers which crop alerts need a visit, and settles the rest from the office.
-
Field 128 shown as its real 10 m pixels, with the likely cause in plain words and the next best check.
-
Every ranked cause lists the evidence for and against it, and each line cites an evidence ID.
-
150 real fields in Niphad, Nashik, on Sentinel-2 imagery. The Visit first list puts the most urgent fields at the top.

Satellites flag the field. Bhumi says why, names the one check that settles it, and seals the evidence so nobody can quietly change it later.
Track: Climate Data & Environmental Monitoring. Everything below runs on real Sentinel-2 imagery and Open-Meteo weather for 150 fields in Niphad, Nashik (Maharashtra, India).
Inspiration
Niphad is grape and onion country. A field officer at a farmer producer organisation there looks after a few hundred farms, and most of those families farm about one hectare. When unseasonal rain or a pest outbreak hits, there is no second field to make up for the lost one.
Sentinel-2 photographs every field on Earth about every five days, for free. So the imagery is not the problem. The problem is the step after the picture. In our Niphad demo area, 68 of 150 fields lost green in three weeks. A red patch cannot say whether that is a cloud, a harvest on schedule, a heatwave hitting the whole district, or a failing drip line on one farm. An officer cannot visit 68 farms, so they stop trusting the alerts, and the farmer finds out when the leaves curl.
There is a second weak link we kept running into while reading about climate programmes. Soil-carbon, water-stewardship and crop-insurance schemes all pay out on field records. Kept in an ordinary database, those records can be edited after the fact, cherry-picked to the good satellite passes, or claimed twice for the same field and season. That is the double-counting and verification problem carbon markets keep hitting.
We wanted one tool that fixes both: explain the alert honestly, and make the record impossible to bend quietly.
What it does
For every alert, Bhumi asks the same three questions an experienced agronomist would:
| Stage | Question | Example from Niphad |
|---|---|---|
| 1 | Is the signal real? Cloud, shadow, haze, pixels mixing with the land next door. | Field 095 dropped on a hazy Oct 2 pass. Dismissed, with the reason on screen. |
| 2 | Is it expected, or shared with the area? Harvest season, neighbouring fields, the weather. | 17 fields dropped together in one hot spell: one area-wide alert, not 17 visits. |
| 3 | If it is this field alone, what fits? Rank the causes, then pick the cheapest check that tells the top two apart. | Field 128: pest or disease spread, likely. Check: photograph the leaf undersides, about 4 minutes. |
Result: 42 of the 68 alerts are explained without anyone leaving the office. The other 26 arrive with a likely cause, what else it could be, and one cheap check to do on site.
Then Bhumi seals the case. Every piece of evidence is fingerprinted with SHA-256, the fingerprints become a Merkle tree, every case root rolls up into one area root, and that root is anchored on Ethereum Sepolia. When the officer records what they found, the finding becomes new evidence, the ranking updates, and the root changes. Anyone can open the Proof tab and recompute the case in one click.
Three promises
- It shows its own false alarms, with reasons. Showing the cloud and the harvest it dismissed is what makes the real hits believable.
- The language model never decides. Deterministic code turns pixels into evidence and ranks causes. Gemini only writes the plain-language summary, must cite evidence IDs that exist, and is replaced by a template if it does not. Without an API key the app works the same.
- When it does not know, it asks. If no cause clears the bar, the answer is "ask or visit" with the cheapest question, never a forced guess.


How we built it
Real data in. Sentinel-2 L2A at 10 m through Google Earth Engine, with cloud and shadow masked per pixel by Cloud Score+. Weather from Open-Meteo (ERA5 and ERA5-Land). Field boundaries from Fields of the World, cropland from ESA WorldCover. No case is hand-picked: an alert is any zone at least 0.10 NDVI below the field's own baseline on a clear pass in the last 21 days.
Seven evidence tools. Plain Python turns pixels and weather into numbered evidence (E1 to E7): scene quality, NDVI change by zone, a boundary check with a 10 m inner buffer, comparison with neighbouring fields, the spatial pattern of the loss, weather, and the crop calendar.
A rule engine, not a black box. A rule table scores 11 causes in the three stages above. A missing fact counts neither way and is shown as unknown, so the same field always gives the same answer.
The next best check is computed. Every on-site check is scored by how cleanly its outcomes separate the top two causes $A$ and $B$, per minute of the officer's time:
$$ \text{score}(c) = \frac{\text{split}_{A,B}(c)}{\text{minutes}(c)}, \qquad \text{split} = \begin{cases} 1 & \text{outcomes can favour either cause} \ 0.5 & \text{only one direction} \ 0 & \text{otherwise} \end{cases} $$
For Field 128, photographing the leaf undersides (4 min, score 0.125) beats checking the drip line (5 min, 0.1).
The proof layer. Each evidence item becomes a leaf, and leaves are paired up the tree with domain-separated hashes:
$$ \text{leaf}_i = H(\texttt{"bhumi:leaf:v1:"} \,|\, \text{json}(e_i)), \qquad \text{node} = H(\texttt{"bhumi:node:v1:"} \,|\, L \,|\, R) $$
On top of the case root we add a satellite-only root (the tree before any field visit, so you can see exactly what the officer added), a location commitment (the field ID and its centroid rounded to about 100 m, so the ledger never publishes an exact boundary), and a season marker, a nullifier:
$$ \text{marker} = H(\text{location commitment} \,|\, \text{alert date}) $$
One fixed ID per field and satellite pass means a registry can refuse a second claim. Our Solidity contract, BhumiClimateRegistry, does exactly that: attestFieldFinding reverts with NullifierAlreadyUsed if the marker was already spent.
Stack: Python, FastAPI and SQLite for the engine and API; React, TypeScript, Vite and Leaflet for the app, built to work on a phone in a field as well as on a desk; Solidity for the registry, compiled with solc-js and tested in an in-memory EVM (ethereumjs); ethers and MetaMask for anchoring on Sepolia; Gemini for the summaries. 70 backend tests and 2 contract tests pass.
Bhumi grows out of FieldDx, our earlier satellite crop-diagnosis project. The diagnosis engine and data pipeline come from there. Everything on the proof side is new: evidence leaves, Merkle roots, location commitment, season marker, area root, in-app verification, Sepolia anchoring, the registry contract, the ledger, and a redesigned interface.
Challenges we ran into
- Evidence rules that looked right but were not. Running the real Field 128 case showed a working drip line being counted as evidence for pests. Ruling one cause out is not evidence for another. Fixing that rule changed which check the app recommends, and we let the computed answer stand rather than tuning it back to the demo we had planned.
- Findings used to close the case. Recording one finding marked a cause as confirmed and could revive a cause that had been ruled out. We rebuilt it so a finding only moves the causes it actually supports or contradicts, and the case stays open until the officer has enough to act.
- Monsoon cloud and tiny fields. Many passes are cloudy, and many fields are under a hectare. Cloudy passes are kept as gaps, never turned into numbers, and small fields are read as one zone with lower confidence instead of pretending we can see inside them.
- Keeping the LLM honest. A summary is only safe if it cannot invent evidence. We validate every evidence ID Gemini cites, retry once, then fall back to a template.
- Hashing that anyone can repeat. A Merkle root is only useful if someone else can rebuild it. We hash canonical JSON with sorted keys and version-tagged prefixes so the same evidence always gives the same root.
- Shipping the contract. The registry hit stack-too-deep in the legacy Solidity pipeline; it needs
viaIRwith the optimizer on.
Accomplishments that we're proud of
- 42 of 68 real alerts explained remotely, with the reason for every dismissal on screen.
- A field visit visibly changes the proof: the satellite-only root and the final root sit side by side.
- A double-counting guard that works end to end, from the season marker in Python to the revert in the contract.
- The whole app runs without an API key and uses only free, global data.
What we learned
- Being trustworthy is mostly about showing your rejections. The false alarms Bhumi throws out, with reasons, are what make its real alerts credible.
- Seal the inputs to an AI decision, not only its output. If the evidence is fixed, anyone can rerun the reasoning and check the conclusion.
- Planning before coding pays off. Writing down exactly what a finding should do surfaced the "ruling out is not confirming" bug before it reached the demo.
- 10 m pixels see patterns across a field, not individual plants, and it is better to say that on screen than to hide it.
What's next
- Deploy
BhumiClimateRegistryon Sepolia so anchoring calls the contract instead of writing calldata only. - Replace the coarse location commitment with a salted or zero-knowledge commitment.
- Add red-edge (NDRE) and moisture (NDMI) evidence from the 20 m bands, so fewer cases need a leaf check.
- Use recorded crop data from India's AgriStack instead of inferring the crop from NDVI.
- More areas and languages. Every data source is already global, so nothing is tuned to Niphad.
Honest limits
Anchoring today puts the area root into a Sepolia transaction's calldata; the contract is written and tested but not yet deployed. Without a wallet, anchoring is simulated and the app says so. The location commitment hides a field's exact boundary but is not secret to someone who can guess the field and the 100 m cell.
Built With
- era5
- esa-worldcover
- ethereum
- ethereumjs
- ethers.js
- fastapi
- fields-of-the-world
- gemini
- google-earth-engine
- leaflet.js
- merkle-tree
- metamask
- node.js
- open-meteo
- python
- react
- sentinel-2
- sepolia
- sha-256
- solc
- solidity
- sqlite
- typescript
- vite

Log in or sign up for Devpost to join the conversation.