grainPlan grainPlan logo

grainPlan is the grain-specific decision orchestrator of the ORCHESTRA agricultural analytics stack -- the practitioner last-mile that turns an upstream member's inference result into an actionable grain-production plan.

It is a thin grain-semantics layer on top of decideR: it reuses decideR's decision engine, its manifest-native pipeline tail, its grade-band loss library, and its grounding firewall, and adds the grain economics those decisions kept re-deriving. grainPlan never reinvents decision theory, manifest reading, or the Independent Oracle Principle firewall; it composes them with grain meaning.

What it gives you

Related packages

grainPlan starts where these leave off. It takes draws from a fitted model, such as yield at each nitrogen rate or variety merit across trials, and prices them with grain prices, protein bands and malting premiums to give a recommendation. When the evidence is marked unverified, it declines and keeps the status quo instead of recommending.

Installation

# decideR is grainPlan's base; install it first (it is not on CRAN).
# install.packages("remotes")
remotes::install_github("max578/decideR")
# remotes::install_github("max578/grainPlan")
install.packages("grainPlan", repos = c('https://max578.r-universe.dev', 'https://cloud.r-project.org'))

The firewall, in one example

A nitrogen-rate decision on grounded yield evidence decides; the same decision on unverified evidence abstains to the status quo. This is the inherited Independent Oracle Principle firewall -- a grain decision is never minted on a fact the upstream producer did not ground.

library(grainPlan)

set.seed(1L)
rates <- seq(0, 200, by = 25)
mu    <- 3 + 2.5 * (1 - exp(-rates / 80))               # diminishing returns
yld   <- vapply(mu, function(m) rnorm(1500L, m, 0.3), numeric(1500L))

# grounded evidence -> a decision
plan_nitrogen_rate(yld, rates, price_grain = 350, price_n = 1.3,
                   crop = "wheat",
                   grounding = decideR::grounding_grounded())
#> <grain_decision> RECOMMENDED  [grounded]
#>   crop   : wheat
#>   kind   : nitrogen_rate
#>   action : 175 kg N/ha
#>   why    : Apply 175 kg N/ha to the wheat crop: it maximises expected profit ($1596.9/unit area) and clearly beats the status quo on the posterior.

# the SAME draws, unverified -> abstain to the status quo
plan_nitrogen_rate(yld, rates, price_grain = 350, price_n = 1.3,
                   crop = "wheat",
                   grounding = decideR::grounding_unverified())
#> <grain_decision> ABSTAINED  [ungrounded]
#>   crop   : wheat
#>   kind   : nitrogen_rate
#>   action : 0 kg N/ha
#>   why    : Held at the status-quo rate: the yield evidence is unverified, so the Independent Oracle Principle firewall declines to act on it.

The orchestra

grainPlan is a member of the ORCHESTRA federation: it consumes the orchestra_manifest contract through decideR's duck-typed tail and depends only on decideR (the orchestra DAG stays acyclic, with grainPlan a leaf at the decision tail). See CONSTELLATION_MEMBER.md for the full roster.

API stability

grainPlan is a small but consequential API: the meaning of a recommendation, an abstention, and a grounding token can drive a paddock decision. While the package is on a 0.x series it follows a conservative stability rule.

Contributing

Bug reports and suggestions are welcome through the issue tracker.

Citation

citation("grainPlan")

Licence

MIT (c) 2026 Max Moldovan.