Skip to content

Latest commit

 

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Crop Oracle

Crop Oracle is a map-first tool for recording weed observations and producing simple weed-pressure predictions. The first pilot workspace is Park Vartopo in Sofia, Bulgaria, with public-area observations treated as rough ecological field notes rather than precise private farm coordinates.

The current version provides a FastAPI API, SQLAlchemy models, SQLite local storage, CRUD for fields and weed observations, a rule-based prediction endpoint, seeded map workspace data, GeoJSON output for layers, and a Leaflet map UI.

Local Setup

Use Python 3.11 or newer.

python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt

Run

uvicorn app.main:app --reload

The API will be available at http://127.0.0.1:8000.

The map UI is served at:

http://127.0.0.1:8000/

Interactive docs:

http://127.0.0.1:8000/docs

Test

pytest
ruff check .
ruff format .

API Examples

Health check:

curl http://127.0.0.1:8000/health

Create a field:

curl -X POST http://127.0.0.1:8000/fields \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": 1,
    "name": "Vartopo observation plot",
    "location_label": "Park Vartopo, Sofia",
    "area_square_meters": 250
  }'

Create a weed observation:

curl -X POST http://127.0.0.1:8000/fields/1/weed-observations \
  -H "Content-Type: application/json" \
  -d '{
    "observed_at": "2026-05-04",
    "species": "Chenopodium album",
    "confidence": 0.8,
    "coverage_percent": 35,
    "density_class": "medium",
    "status": "monitor",
    "plant_family": "Amaranthaceae",
    "height_cm": 14,
    "growth_stage": "seedling",
    "is_flowering": false,
    "is_seeding": false,
    "moisture_class": "normal",
    "disturbance_class": "bare_soil",
    "light_class": "full_sun",
    "soil_exposure_percent": 30,
    "tags": ["edge", "annual"],
    "latitude": 42.6581,
    "longitude": 23.2852
  }'

Run a rule-based prediction:

curl -X POST http://127.0.0.1:8000/fields/1/predict \
  -H "Content-Type: application/json" \
  -d '{
    "target_year": 2026,
    "recent_rainfall_mm": 25,
    "recent_mean_temp_c": 20,
    "disturbed_soil": true,
    "crop_established": false
  }'

Fetch observation GeoJSON for the default Park Vartopo workspace:

curl http://127.0.0.1:8000/map/workspaces/1/observations.geojson

Create a drawn workspace observation with a photo:

curl -X POST http://127.0.0.1:8000/map/workspaces/1/observations \
  -H "Content-Type: application/json" \
  -d '{
    "observed_at": "2026-06-15",
    "species": "Urtica dioica",
    "confidence": 0.95,
    "coverage_percent": 80,
    "density_class": "high",
    "status": "aggressive",
    "plant_family": "Urticaceae",
    "height_cm": 80,
    "growth_stage": "flowering",
    "is_flowering": true,
    "is_seeding": false,
    "moisture_class": "moist",
    "disturbance_class": "footpath_edge",
    "light_class": "partial_shade",
    "soil_exposure_percent": 0,
    "tags": ["nettle", "edge_patch"],
    "geometry_geojson": "{\"type\":\"Polygon\",\"coordinates\":[[[23.284,42.658],[23.285,42.658],[23.285,42.659],[23.284,42.659],[23.284,42.658]]]}",
    "notes": "Large nettle patch",
    "photos": [
      {
        "url": "https://example.com/nettle.jpg",
        "thumbnail_url": "https://example.com/nettle-thumb.jpg",
        "taken_at": "2026-06-15T09:30:00"
      }
    ]
  }'

Implemented Endpoints

  • GET /health
  • POST /fields
  • GET /fields
  • GET /fields/{field_id}
  • POST /fields/{field_id}/weed-observations
  • GET /fields/{field_id}/weed-observations
  • POST /fields/{field_id}/predict
  • GET /fields/{field_id}/predictions/latest
  • POST /map/workspaces
  • GET /map/workspaces
  • GET /map/workspaces/{workspace_id}
  • POST /map/workspaces/{workspace_id}/observations
  • GET /map/workspaces/{workspace_id}/observations.geojson
  • GET /map/workspaces/{workspace_id}/layers
  • POST /map/workspaces/{workspace_id}/layers
  • GET /map/workspaces/{workspace_id}/layers/{layer_id}/geojson

Frontend

The frontend lives in frontend/:

frontend/
  index.html
  app.js
  style.css

It uses Leaflet with OpenStreetMap, marker clustering, Leaflet.Draw, and a heatmap plugin. The first screen is the Park Vartopo map.

Implemented map behavior:

  • clustered weed observation markers and drawn geometries
  • popups with species, coverage, density, confidence, photo count, notes, and date
  • initial layer toggles for observations, predicted weeds, terrain, moisture, disturbance, experiment plots, walking paths, grid, and heatmap
  • drawing tools for points, polygons, rectangles, and lines
  • workspace observation form with optional photo URL, thumbnail URL, timestamp, and notes
  • status and family coloring modes
  • month-based time slider
  • client-generated weed-pressure heatmap
  • client-generated 25 m grid overlay with species richness, weed density, last visit, and predicted emergence placeholder

Observation Fields

Extended observation data now supports:

  • status: beneficial, neutral, monitor, aggressive, or unknown
  • plant family
  • height in centimeters
  • growth stage
  • flowering and seeding flags
  • moisture class
  • disturbance class
  • light class
  • soil exposure percent
  • tags as a list, stored internally as SQLite-compatible JSON text

The frontend form does not expose all of these fields yet. The API and GeoJSON export already support them, because naturally the backend has learned new words before the UI has learned where to put them.

Map And GeoJSON Notes

The app seeds one default map workspace:

name: Park Vartopo
location_label: Sofia, Bulgaria
workspace_type: public_observation_area

Geometry is stored as GeoJSON text for the SQLite prototype. Weed observations may use approximate latitude and longitude, rough geometry_geojson, or no coordinates at all. The GeoJSON endpoint returns only observations with usable geometry.

Everything map-visible should move through the same layer path over time:

MapLayer -> GeoJSON endpoint -> frontend renderer

Current environmental layers are seeded as layer records, but most return empty GeoJSON until real sources are imported. This keeps observations, predictions, soil samples, weather stations, photos, experiment plots, walking routes, and future rasters aligned behind one map-layer model.

Example GeoJSON response:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [23.2852, 42.6581]
      },
      "properties": {
        "species": "Chenopodium album",
        "density_class": "medium",
        "status": "monitor",
        "coverage_percent": 35,
        "moisture_class": "normal",
        "disturbance_class": "bare_soil",
        "light_class": "full_sun",
        "tags": ["edge", "annual"],
        "photos": []
      }
    }
  ]
}

Prototype Database Notes

SQLite create_all() does not alter existing tables. The app includes a small SQLite-only schema guard for the newly added observation fields so existing local prototype databases can continue to run without manual deletion.

This is not a replacement for real migrations. Use Alembic before treating this as anything more serious than a living prototype held together by good intentions and SQL strings.

Known Limitations

  • Predictions are rule-based and only use stored observations plus request context.
  • There is no authentication yet.
  • SQLite stores geometry as plain GeoJSON text; PostGIS is a later target.
  • Terrain, moisture, disturbance, experiment plot, and walking path layers are placeholders until imported data exists.
  • The heatmap and grid are client-generated from observation GeoJSON.
  • Weather, soil, crop history, DEM, NDVI, and terrain models are not implemented yet.
  • The frontend form does not yet expose every extended observation field.

Next Steps

  • Add the extended observation fields to the frontend form.
  • Add client-side filters for species, status, density, growth stage, and confidence.
  • Add crop season and soil observation endpoints.
  • Add field-level prediction GeoJSON.
  • Import environmental layers: DEM, slope, aspect, soil, weather, land cover, NDVI, paths, streams, and canopy.
  • Persist generated grid cells server-side for modeling.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages