- Read the User Manual to understand the system
- Read the Architecture Design to understand the three-layer hierarchy
- All contributions must be Apache-2.0 compatible (see LICENSE)
git clone https://github.com/agstack/opensource-pestmodels.git
cd opensource-pestmodels
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v| Contribution type | Where it goes | Who reviews |
|---|---|---|
| New weather/accumulation model | src/agstack_pnd/models/weather/ |
AgStack core maintainers |
| New agronomic/derived model | src/agstack_pnd/models/agronomic/ |
OpenAgri + AgStack maintainers |
| New disease/pest risk model | src/agstack_pnd/models/disease/ |
OpenAgri maintainers (required) |
| New threat definition (crop-pest pairing) | src/agstack_pnd/catalog/data/pest_catalog.json |
OpenAgri maintainers (required) |
| New weather provider | src/agstack_pnd/providers/ |
AgStack core maintainers |
| New MCP tool | src/agstack_pnd/server/mcp_server.py |
AgStack core maintainers |
| Bug fix | Wherever the bug is | Depends on area (see CODEOWNERS) |
| Documentation | docs/ |
Both teams |
-
Choose the correct layer:
- Weather (
models/weather/): Raw accumulation (GDD, chill, precip) - Agronomic (
models/agronomic/): Derived features (leaf wetness, VPD, phenology) - Disease (
models/disease/): Risk assessment using lower-layer outputs
- Weather (
-
Create your model file and subclass
PestModelBase:
from agstack_pnd.foundation.base_model import PestModelBase
from agstack_pnd.foundation.types import (
ModelMetadata, ModelLayer, ModelResult, ThreatDefinition, WeatherDataFrame,
)
from uuid import UUID
class MyModel(PestModelBase):
metadata = ModelMetadata(
uuid=UUID("..."), # Generate with: python -c "import uuid; print(uuid.uuid4())"
name="my_model",
display_name="My Model",
version="1.0.0",
layer=ModelLayer.DISEASE,
description="What this model does",
supported_crops=["apple"],
required_weather_fields=["air_temperature", "relative_humidity"],
citation="Author (Year). Journal, vol, pages.",
)
def validate_parameters(self, parameters):
return dict(parameters) if parameters else {}
def calculate(self, weather_data, parameters=None, threat=None):
params = self.validate_parameters(parameters)
# Your model logic here -- pure computation, no I/O
...- Register via entry point in
pyproject.toml:
[project.entry-points."agstack_pnd.models"]
my_model = "agstack_pnd.models.disease.my_model:MyModel"-
Write tests in
tests/unit/test_my_model.pyusing theseven_day_weatherorthirty_day_weatherfixtures fromconftest.py. -
Add a citation to the model's
ModelMetadata. This is required for all disease models (guardrail G-MODEL-001).
Add an entry to src/agstack_pnd/catalog/data/pest_catalog.json:
{
"scientific_name": "Venturia inaequalis",
"common_name": "Apple scab",
"threat_type": "fungus",
"crop": "apple",
"bio_params": {
"t_base": 0.0,
"t_lethal_min": -5.0,
"t_lethal_max": 40.0,
"t_optimal_min": 15.0,
"t_optimal_max": 24.0,
"min_streak": 3,
"min_wetness_hours_critical": 20.0,
"min_wetness_hours_high": 14.0
},
"fuzzy_rules": [
{"hum_lo": 90, "hum_hi": 100, "temp_lo": 15, "temp_hi": 24, "rain_min": 2.0, "risk_level": "critical"},
{"hum_lo": 80, "hum_hi": 100, "temp_lo": 10, "temp_hi": 28, "rain_min": 1.0, "risk_level": "high"},
{"hum_lo": 70, "hum_hi": 90, "temp_lo": 6, "temp_hi": 30, "rain_min": 0.0, "risk_level": "moderate"},
{"hum_lo": 0, "hum_hi": 70, "temp_lo": 0, "temp_hi": 35, "rain_min": 0.0, "risk_level": "low"}
],
"special_rules": {}
}All biological parameters must reference published literature (guardrail G-MODEL-003). OpenAgri maintainers review all threat catalog changes.
Subclass WeatherProvider in src/agstack_pnd/providers/:
from agstack_pnd.foundation.weather_provider import WeatherProvider
from agstack_pnd.foundation.types import WeatherDataFrame
class MyProvider(WeatherProvider):
async def get_hourly_data(self, latitude, longitude, start_date, end_date, fields):
# Map standardized field names to your provider's native names
# Fetch data and return as WeatherDataFrame
...
async def get_daily_data(self, latitude, longitude, start_date, end_date, fields):
...
def supported_fields(self):
return ["air_temperature", "relative_humidity", ...]
def provider_name(self):
return "My Weather Service"See src/agstack_pnd/providers/noaa.py for a complete reference implementation.
- Four automated CI gates run (all must pass before merge):
| Gate | What it checks |
|---|---|
| Gate 1: Lint & Format | ruff check + ruff format |
| Gate 2: Type Check | mypy --strict |
| Gate 3: Tests | pytest -- all unit and integration tests |
| Gate 4: Catalog & FATFD | Threat catalog schema, UUID uniqueness, FATFD validator |
-
CODEOWNERS review is requested based on which files you changed:
- Disease models, agronomic models, threat catalog -> OpenAgri maintainers must approve
- Foundation, providers, CI/CD -> AgStack core maintainers must approve
- See CODEOWNERS for the full mapping
-
Once all gates pass AND reviewers approve, the PR can be merged.
- Run
pytest tests/ -vlocally before pushing - Run
ruff check src/ tests/ && ruff format --check src/ tests/to catch lint issues - Include a citation for any new disease model
- Add tests that verify expected outputs for known weather conditions
- Keep PRs focused -- one model or one threat definition per PR is ideal
If your contribution should be queryable by AI agents, add a tool to src/agstack_pnd/server/mcp_server.py. See the MCP Server Guide for details.
- User Manual -- How to use models, API, and MCP
- MCP Server Guide -- AI agent integration
- Architecture Design -- Technical architecture
- OpenAgri Migration Guide -- OpenAgri team's role as maintainers
- Deployment Guide -- Running the server