A Python client for querying publicly accessible soil profile and fertilizer recommendation workflows across Morocco.
Query site-level fertilizer recommendations from geographic coordinates, inspect available crops and yield-target ranges, and scale from one field to many with a clean Python API and CLI.
turba_client is a production-friendly Python library that makes it easy to query site-specific NPK fertilizer recommendations from fertimap.ma using only a field location, with optional agronomic overrides.
It is designed for researchers, agronomists, and developers who want a reproducible way to move from:
- coordinates
- to site context
- to available crops and yield targets
- to fertilizer recommendations
- and finally to batch processing and downstream analysis
The library supports both single-field workflows and multi-field pipelines, with a clean Python interface and command-line support.
With turba_client, you can:
- query a site from its longitude and latitude
- inspect soil and administrative context returned for that site
- list the crops available at that location
- request recommendations for:
- one crop or many crops
- one target-yield level or many target-yield levels
- explicit numeric target yields
- override selected field values when needed:
crop_nametarget_yield_levelphmatiere_organique_pctp_assimilable_mgkg_p2o5k_mgkg_k2o
- process many fields at once from a DataFrame or CSV
- map user column names to the library schema before batch processing
pip install turba-clientpip install -e .[dev]from turba_client import TurbaClient
client = TurbaClient()
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level="medium",
)
print(df[["crop_name", "target_yield_level", "N_kg_ha", "P_kg_ha", "K_kg_ha"]])Expected output
crop_name target_yield_level N_kg_ha P_kg_ha K_kg_ha
0 Wheat (Rainfed) medium ... ... ...
The typical turba_client workflow is intentionally simple:
You provide coordinates.
from turba_client import TurbaClient
client = TurbaClient()
site = client.get_site_profile(
longitude=-7.616,
latitude=33.589,
)
print(site)Expected output
SiteProfile(
longitude=-7.616,
latitude=33.589,
region='...',
province='...',
commune='...',
soil_type='...',
texture_globale='...',
ph=...,
matiere_organique_pct=...,
p_assimilable_mgkg_p2o5=...,
k_mgkg_k2o=...
)
This step answers: What does turba-client know about this field?
crops = client.list_crops(
longitude=-7.616,
latitude=33.589,
)
print(crops[["crop_id", "crop_name", "target_yield_min", "target_yield_max", "target_yield_step", "target_yield_unit"]])Expected output
crop_id crop_name target_yield_min target_yield_max target_yield_step target_yield_unit
0 1 Wheat (Rainfed) ... ... ... ...
1 2 Wheat (Irrigated) ... ... ... ...
2 3 Barley (Rainfed) ... ... ... ...
...
This step answers: Which crops are valid here, and what are the allowed target-yield ranges?
You can request recommendations using:
- a standard target level:
"low","medium","high" - several target levels at once
- one or more explicit numeric target yields
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level="medium",
)df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level=["low", "medium", "high"],
)df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield=45,
)df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield=[35, 45, 55],
)If a numeric
target_yieldfalls outside the valid crop range returned by turba-client for that site,turba_clientraises a validation error.
This step answers: What production target am I optimizing for?
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level="high",
)
print(df[[
"crop_name",
"target_yield_level",
"target_yield_value",
"N_kg_ha",
"P_kg_ha",
"K_kg_ha",
]])Expected output
crop_name target_yield_level target_yield_value N_kg_ha P_kg_ha K_kg_ha
0 Wheat (Rainfed) high ... ... ... ...
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name=["Wheat (Rainfed)", "Barley (Rainfed)"],
target_yield_level="medium",
)If crop_name is omitted, the client can return recommendations across the valid crop space for the requested target strategy.
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
target_yield_level="medium",
)This step answers: What N, P, and K recommendations should I use under the chosen crop and target settings?
For scenario testing, controlled comparisons, or curated field data, you can override the values used in the recommendation request.
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level="medium",
ph=7.8,
matiere_organique_pct=1.9,
p_assimilable_mgkg_p2o5=28,
k_mgkg_k2o=210,
)This is useful when:
- you want to compare default vs curated values
- you have field-lab measurements that should replace parsed defaults
- you want reproducible sensitivity analyses
turba_client supports batch processing from pandas DataFrames and CSV files.
import pandas as pd
from turba_client import TurbaClient
client = TurbaClient()
sites = pd.DataFrame([
{
"longitude": -7.616,
"latitude": 33.589,
"crop_name": "Wheat (Rainfed)",
"target_yield_level": "medium",
},
{
"longitude": -6.832,
"latitude": 33.972,
"crop_name": "Barley (Rainfed)",
"target_yield_level": ["low", "high"],
"matiere_organique_pct": 1.5,
"p_assimilable_mgkg_p2o5": 20,
"k_mgkg_k2o": 160,
},
])
results = client.get_recommendations_batch(sites)
print(results.head())If your input data uses different column names, map them first.
prepared = client.prepare_input_table(
sites,
column_map={
"longitude": "lon",
"latitude": "lat",
"crop_name": "crop",
"target_yield_level": "target_level",
},
)
results = client.get_recommendations_batch(prepared)This step answers: How do I move from one field to a full operational dataset?
from turba_client import TurbaClientCreate a client.
client = TurbaClient(
timeout=20,
sleep_seconds=0.2,
max_retries=3,
user_agent="turba-client/0.1.0",
)Inspect the site-level administrative and soil context.
client.get_site_profile(longitude=-7.616, latitude=33.589)List available crops and target-yield ranges for a site.
client.list_crops(longitude=-7.616, latitude=33.589)Get recommendations for one site.
client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Wheat (Rainfed)",
target_yield_level="medium",
)Supports:
- one or more crops
- one or more
target_yield_levelvalues - one or more
target_yieldvalues - optional overrides
Normalize input tables before batch runs.
client.prepare_input_table(df, column_map={...})Process multiple rows in a batch.
client.get_recommendations_batch(df)The package installs a command-line tool named turba-client.
turba-client recommend-site \
--longitude -7.616 \
--latitude 33.589 \
--crop-name "Wheat (Rainfed)" \
--target-yield-level mediumturba-client recommend-site \
--longitude -7.616 \
--latitude 33.589 \
--crop-name "Wheat (Rainfed)" \
--target-yield-level low \
--target-yield-level medium \
--target-yield-level highturba-client recommend-site \
--longitude -7.616 \
--latitude 33.589 \
--crop-name "Wheat (Rainfed)" \
--target-yield 45turba-client recommend-many \
--input-file sites.csv \
--output results.csvThe library validates:
- coordinate ranges
- accepted target level names
- target-yield bounds against crop-specific allowed ranges
- required columns for batch processing
Typical exceptions include:
ValidationErrorCropNotFoundErrorSiteDataNotFoundErrorUpstreamResponseError
Example:
from turba_client import (
TurbaClient,
ValidationError,
CropNotFoundError,
SiteDataNotFoundError,
UpstreamResponseError,
)
client = TurbaClient()
try:
df = client.get_recommendations(
longitude=-7.616,
latitude=33.589,
crop_name="Non Existing Crop",
)
except ValidationError as e:
print("Invalid input:", e)
except CropNotFoundError as e:
print("Crop not found:", e)
except SiteDataNotFoundError as e:
print("No site data found:", e)
except UpstreamResponseError as e:
print("Unexpected upstream response:", e)The examples/ directory contains end-to-end scripts:
basic_usage.pycustom_targets.pybatch_with_column_mapping.pyuser_workflow.py
These examples are intended to mirror real user journeys:
- inspect a site
- list crops
- request recommendations
- run batch processing
- export results
pytestpython -m buildThis creates wheel and source-distribution artifacts in dist/.
This library builds on the valuable work of the fertimap.ma, which developed and maintains the underlying platform and recommendation service. turba_client is an independent Python client created to make fertimap.ma workflows more accessible for developers, researchers, and applied agronomy use cases. It does not claim ownership of the platform, its data, or its recommendation engine.