Documentation

Calculations & cost model

Every formula Zone Forge uses, in one document. If you want to reproduce a prescription on a calculator, this is the page.

Hybrid philosophy decision tree

Hybrid is the default philosophy. It is a true 3-zone (build / maintain / drawdown) system: the maintenance band is target ± 10% by default, and above-target aggressiveness is configurable per field via drawdown_level.

  1. Build — soil < 0.9 × target. rate = removal × yield_goal + (target − soil) × buildup_factor / buildup_years. The buildup is deficit-scaled, so a zone testing far below target gets a much bigger bump than one just below the band — adjacent variable-rate zones never collapse to the same flat number.
  2. Maintain — 0.9 × target ≤ soil ≤ 1.1 × target. rate = removal × yield_goal. Pure crop removal: replace exactly what the crop took out so the soil holds at target.
  3. Drawdown — soil > 1.1 × target. rate = removal × yield_goal × multiplier. The multiplier comes from the drawdown_level setting (default moderate): 75% for ratio ≤ 1.25, 50% for 1.25 < ratio ≤ 1.5, 0% for ratio > 1.5, where ratio = soil / target.

Drawdown multiplier table. Pick the level that matches your operation; moderate is the default.

drawdown_levelratio ≤ 1.251.25 < ratio ≤ 1.5ratio > 1.5
none100%100%100%
light75%75%0%
moderate (default)75%50%0%
aggressive25%0%0%

Defaults: buildup_factor for P₂O₅ is 9–10 lb/ac per ppm depending on source (Iowa PM 1688 = 9, Tri-State 2020 = 10). For K₂O it's 8–9 lb/ac per ppm depending on source (Iowa PM 1688 = 8, Tri-State 2020 = 9). buildup_years defaults to 3–4 depending on source. maintenance_tolerance_pct defaults to 10. Every constant is overridable per field.

Nutrient-to-product conversion

The agronomic table gives you a nutrient rate (e.g., 74 lb P₂O₅/ac). The applicator needs a product rate (e.g., lb DAP/ac). Conversion uses the published product analysis.

Dry products

product_rate = nutrient_rate / nutrient_fraction

Example: DAP is 18-46-0 (18% N, 46% P₂O₅, 0% K₂O). To deliver 74 lb P₂O₅/ac:

74 / 0.46 = 160.87 lb DAP/ac

Liquid products

gallons_per_acre = nutrient_rate / nutrient_fraction / density_lb_per_gal

Example: UAN-32 is 32% N at 11.06 lb/gal. To deliver 200 lb N/ac:

200 / 0.32 / 11.06 = 56.5 gal/ac

Lime engine — separate pipeline

Lime is calculated separately from the build/maintain/drawdown engine. Different driver (buffer pH, not soil-test ppm), different units (tons CaCO₃-equivalent / ac, not lb of nutrient / ac), different product axis (calcitic vs dolomitic, governed by soil Mg). Every shipped recommendation source now ships a lime block, each using its state's published extension method. Penn State, for example, uses its Mehlich Buffer / Exchangeable Acidity table (lb CCE per acre indexed by exchangeable acidity in meq/100 g and pH goal) — Penn State retired the older Adams-Evans buffer-pH method. Midwest tables (Iowa PM 1688, Tri-State 2020) ship buffer-pH-keyed cross-walks for the SMP buffer / lime index reported by midwest labs, while Coastal Plain states (DE, NJ, SC, FL, AL) use the Adams-Evans method calibrated for their soils — part of Zone Forge's 50-state lime coverage.

Step 1 — Resolve the target pH

Falls back through four levels — most specific wins. The provenance of the chosen target flows through to the UI so the farmer can see why alfalfa shows 6.8 and corn shows 6.5.

  1. Explicit target_ph argument (caller override)
  2. crop_target_pH[crop] — per-crop table (alfalfa 6.8, corn/soy/wheat 6.5, oats/rye/sorghum 6.0)
  3. target_pH profile default (typically 6.5)
  4. System default of 6.5

Step 2 — CaCO₃-equivalent demand

If measured_pH ≥ target_pH, the engine returns 0 tons (no lime even if buffer pH suggests reserve acidity). Otherwise it interpolates against the source's buffer_ph_table_tons_per_ac:

tons_caco3_per_ac = interpolate(buffer_ph, profile.buffer_ph_table_tons_per_ac)

Linear interpolation between table rows. Below the lowest tabulated buffer pH the result saturates at the largest published rate — the engine never extrapolates beyond the table.

Step 3 — Calcitic vs dolomitic

Picked from soil Mg signals via the source's dolomitic_if block (default thresholds match Tri-State convention):

  • If mg_ppm < 50 or mg_base_sat_pct < 10dolomitic
  • Otherwise ⇒ calcitic
  • No Mg data provided ⇒ calcitic (safer than loading Mg into an already-balanced soil)

Step 4 — Convert to a physical product rate

Adjust for the product's ECCE (Effective Calcium Carbonate Equivalent):

physical_tons_per_ac = tons_caco3_per_ac / (ecce_pct / 100)

If your local ag lime is 80% ECCE and the engine says 2 tons CaCO₃-equivalent, you'll spread 2 / 0.80 = 2.5 tons of product per acre.

Worked example

Corn, measured pH 5.8, buffer pH 6.7, Tri-State 2020 source, Mg 40 ppm:

  • crop_target_pH["corn"] = 6.5 (provenance: crop)
  • 5.8 < 6.5 ⇒ lime required
  • Buffer 6.7 ⇒ 3.0 tons CaCO₃-eq / ac
  • Mg 40 ppm < 50 ⇒ dolomitic
  • If you pick lime_dolomitic (ECCE 85): 3.0 / 0.85 = 3.53 tons / ac

IDW interpolation

The heatmap visualization and the prescription zoning both rely on inverse-distance-weighted interpolation across your samples.

The formula

For each grid cell c with neighbors n_1, n_2, ... n_k at distances d_1, d_2, ... d_k:

value(c) = Σ (value(n_i) / d_i^p) / Σ (1 / d_i^p)

Parameters

  • k = 8 (eight nearest neighbors)
  • p = 2 (inverse-square weighting)
  • Distance metric: Euclidean in projected UTM coordinates (not raw lat/lon)
  • Cell size: ~1 acre (208.7 ft × 208.7 ft), with the grid clamped to 50–500 cells per axis depending on field size
  • Latitude scaling: longitude meters-per-degree adjusted for the field's latitude so cells are actually square in projected space

Zone classification

Once each grid cell has an interpolated value, cells are classified into the source table's soil-test classes:

  • Very Low < critical_low_threshold
  • Low [critical_low, critical]
  • Optimum [critical, target]
  • High [target, high]
  • Very High ≥ high_threshold

Each zone's rate is then computed directly from its mean soil value using the Hybrid formula above — the class is just the legend label, not the input to the rate. Zones smaller than 0.25 acres merge into adjacent zones (the smallest a typical rate controller can honor in a 5-second update interval).

Field totals

The field's total product is computed by averaging the per-cell rate (weighted by cell area) and multiplying by total acres:

field_total_lb = mean(cell.rate) × total_acres

Cost per acre

For each zone:

cost_per_acre = product_rate × product_unit_cost

Common units:

  • Dry products: $/ton, divided by 2000 for $/lb
  • Liquid products: $/gallon
  • Lime: $/ton of product (not CaCO₃-equivalent)

Where the unit cost comes from

Zone Forge resolves product_unit_cost in this priority order — the first match wins:

  1. Field-level price override — set in Field Setup, per nutrient. Use this when a single field's invoice price differs from the rest of the operation (different supplier, prepaid contract, etc.).
  2. Organization Price Book — set in Settings → Pricing → Price Book, per organization, per product. This is where most farmers enter the actual prices they pay so every field's cost estimate is accurate without per-field bookkeeping.
  3. Catalog default — illustrative shipped values (e.g. DAP $720/ton, MAP $740/ton, UAN-32 $2.85/gal). These are starting points, not live market prices.

The same chain is used at prescription generation time and when re-rendering the print sheet, so changing a price in the Price Book updates every existing prescription's cost view too.

Field cost

field_cost = Σ (zone_acres × zone_cost_per_acre)

Costs are labeled "estimated" because product price is a single input that doesn't reflect actual delivery surcharges (typically ±5%) and because yield-target misses change rates after the fact. Honesty over false precision.

Headland buffer

Optional. When enabled, a 10–20 ft inward buffer is applied to the field boundary, and the buffered region is excluded from variable-rate zoning (typically you'd apply a flat rate on headlands during turns). The buffer width is configurable per field.

Polygon smoothing & gap-fill

Raw zone polygons can be noisy (pixel-level boundaries between adjacent classes). Zone Forge applies two cleanup passes:

  1. Smoothing. A simple morphological dilate-erode pass to remove single-cell artifacts.
  2. Gap-fill. Small unzoned islands inside the field (e.g., from edge-effect classification) are auto-assigned to the adjacent zone whose product rate would be highest — i.e., the most-deficient neighbor. When the engine is uncertain about which side of a class boundary an interior pixel belongs to, it errs toward applying more fertilizer, not less, so the crop is never short-changed on uncertain ground.

Next: Prescription generation