Skip to main content
The Alerts and Risk Monitoring modules do not run on prose. They run on a structured layer that sits between raw signals (indices, weather, phenology) and the diagnosis pages in this documentation. That layer is a library of rule cards: one machine-readable file per stress, disease, pest, or nutrient disorder, each backed by a full literature page. This page documents the model so agronomists, engineers, and integrators share the same vocabulary.

The four layers

1. Signal layer

Continuously ingested inputs: vegetation indices (NDVI, NDRE, NDMI, LST, CWSI, Salinity Index), weather observations and forecasts (temp, humidity, leaf-wetness hours, rainfall, ET), phenology (days after sowing or transplanting, derived stage), and optional in-field sensors and scout observations.

2. Rule cards

One YAML card per condition (e.g. rice_blast, rice_water_stress, rice_bph). Each card lists the crop, valid stage window, driver thresholds, severity formula, yield-impact curve, mitigation window, and recommended actions. Every card links back to its literature page.

3. Engines

The alert engine fires on threshold crossings against the signal layer. The risk engine matches active alerts + forecasts against rule cards to produce forward-looking risk cards with yield-at-risk, estimated loss, and days to mitigate.

4. AI advisor

Retrieval-augmented generation over the literature. Given an alert or risk payload, the advisor retrieves the matching rule card and diagnosis page and returns a grounded answer with four fixed sections: what’s happening, parameters, what to do, how to do it.

Data flow

How to read this diagram
  • Solid arrows are data movement: satellite pass to risk card to grounded answer.
  • Dashed arrows are references or lookups (rule cards being read, RAG retrieval, cross-links).
  • Rule cards feed both engines, never the other way around. The alert engine reads driver thresholds and alert_bindings; the risk engine reads the same cards plus yield_impact and mitigation. Cards are inputs, not outputs.
  • Literature pages feed the RAG index at build time, not at query time. The advisor queries the index, then references the underlying pages via literature_ref on the rule card.
  • The risk card is passed to the advisor as context when the user clicks Ask the advisor, so the LLM sees the same numbers the operator sees.
  • Scout observations close the loop back into the signal layer and into farm_history calibration for the next season.

Rule card schema

Rule cards live alongside their literature page as YAML frontmatter under a rule_card: key, so a single .mdx file is both human-readable documentation and the source of truth for the engines.

Field reference

How a risk card is produced

1

Alerts fire from the signal layer

Example: A2 water stress unresolved (NDMI 0.19 below 0.30 healthy band) and night_temp_low from the weather forecast.
2

Risk engine matches active rule cards

For field A2 (rice, booting stage), the engine loads all rule cards where crop = rice and stage_window contains booting, then evaluates each card’s drivers against the current signal state.
3

Severity and impact are computed

The severity.formula produces a score (e.g. 0.72 → HIGH). The engine resolves yield_impact layer by layer for this field: if ecosystem = irrigated_lowland and variety_group = susceptible, it takes by_ecosystem.irrigated_lowland.booting.high = 25%, then applies the susceptible multiplier × 1.3 = 32.5%. Multiplied by the field’s expected yield and area, this becomes ~5.8 t estimated loss.
4

Mitigation window is resolved

The engine reads mitigation.window_days_by_severity[high] = 3 and applies any window_days_stage_modifier for the current stage. This becomes the “Days to mitigate” number on the card.
5

Risk card is rendered

The Risk Monitoring module displays hazard, driver summary, yield-at-risk %, estimated loss, mitigation.window_days, and the top action from mitigation.actions.
6

AI advisor grounds its answer

Clicking Ask the advisor sends the risk payload plus the referenced literature page to the LLM with a fixed output schema: whats_happening, parameters, what_to_do, how_to_do.

Yield-impact layers

A single flat yield-loss table is never right for every field. The same disease behaves very differently across ecosystems and varieties. yield_impact is therefore a layered lookup, from most general to most specific. The engine resolves them in order, and the first match wins:
Ship v0 for every rule this quarter. Add v1 (ecosystem) for the top 3-5 rules per crop next. Do not block the module launch on v3 or v4 - they are refinements, not prerequisites.

Ecosystem is a field-data prerequisite

For crops where ecosystem meaningfully changes risk (rice is the clearest example), ecosystem must be a captured property on the field before the risk engine can apply v1 overrides. If the field record is missing an ecosystem tag, the engine falls back to default and marks the risk card as “baseline estimate” so the operator knows precision is limited. For crops where ecosystem is less discriminating (oil palm, pineapple, rubber), use by_region or by_soil_type in place of by_ecosystem. The layer key is generic on purpose.

Provenance and governance

Every yield_impact block carries source and citations. Values are one of:
  • literature_v0 - drawn from published references (IRRI, FAO, national extension).
  • agronomy_reviewed - validated by the internal agronomy team.
  • farm_calibrated - refined against the platform’s own field-history observations.
Cards flagged literature_v0 are surfaced in the agronomy review queue and re-checked annually.

Agronomic rate bands

The Alerts and Risk engines produce prescriptions. Before a prescription can be exported or applied, the VRA Maps Input validation guardrail requires every per-zone rate to fall inside a crop-specific agronomic band. The Semai Advisor uses the same band as its safety-critical write gate, and Failure Modes trips a degradation when a requested rate would exceed it. Those three references land here. A band is one row keyed on crop × product with a minimum, a maximum, a unit, a basis (per pass, per season, or per year), and a source. Anything else is out of scope for this table: recommended rate curves live in the crop diagnosis pages, not here. This is the refusal boundary, not the recipe.

Units policy

Every band on this page is stated in kg of product per hectare (kg product/ha), never kg of nutrient. The two differ by the product’s nutrient fraction (urea is ~46% N, MOP is ~60% K₂O, kieserite is ~27% MgO), and a validator that silently accepts one for the other corrupts every rate it passes. Where a Malaysian source publishes a recommendation in kg nutrient/ha, the conversion to the specified product form is applied and the source figure is preserved in the citation. Three exceptions, all explicit:
  • Ethephon (pineapple forcing) is stated in mL of formulated product per plant at the labelled active-ingredient concentration, because that is how the Malaysian label directs it and how it is applied. It is not a hectare rate.
  • Foliar borate on oil palm is stated in g product per palm, because MPOB extension publishes it per palm and per-hectare conversion depends on stand density.
  • The three sourced oil palm peat rows are stated in kg product per palm per year, because MPOB publishes them that way. Added 13 August 2026 — this paragraph said “two exceptions” after those rows had already landed.
A unit mismatch is refused, never converted. Per-hectare against a per-palm band needs a stand density the platform does not hold as a field, so the conversion would be an invented figure. The refusal names the unit the band expects. The unit is part of the band. A prescription submitted in a different unit is rejected before the min/max check even runs.

Basis: per pass, per season, per year

Sources publish rates on three different time bases and they are not interchangeable:
  • Per pass — a single application event. This is what the prescription file actually executes. VRA validation compares against this basis.
  • Per season — the whole-crop-cycle total, split across passes. Used by DOA and MARDI for rice.
  • Per year — the annual total for perennials. Used by MPOB for oil palm and RRIM for rubber, typically split across 2-4 rounds.
A season or annual total is never validated as a single-pass rate. Where the sourced figure is a season or annual total, the band declares that basis, and the platform derives a per-pass ceiling from total / expected_passes for the field’s declared application schedule. Fields without a declared schedule fall back to a conservative per-pass cap of total / 3 (rice) or total / 4 (oil palm, rubber), documented on the field record so the operator knows what is being enforced.

Stage and tier keying

Two structural decisions the handoff called out explicitly:
  • Growth stage. Rice N at tillering and rice N at panicle initiation are different recommendations. Where a source gives stage-specific rates, the band carries a by_stage map with a default fallback, identical in shape to yield_impact.by_ecosystem. Where a source only gives a season total, the band carries basis: season and the per-pass derivation above applies; no stage keying is invented.
  • Ecosystem and variety. These are the same tiers the yield-impact resolver already uses. Where a Malaysian source distinguishes (for example, MPOB’s inland-mineral vs coastal-clay vs peat soil groups; DOA’s granary-scheme differences), the band carries by_ecosystem or by_soil_group in the same layered-lookup shape. Where the source does not distinguish, the band carries a single row and the resolver returns it for every tier. No parallel structure is introduced.

Band schema

Bands live in product_bands.yml alongside the rule cards. One entry per crop-product pair.

Bands

Almost every band below is status: decision_required, and must be filled by an agronomist with the primary documents in hand, one row at a time, and marked sourced with an exact citation before validation is enabled in production. Until a row is marked sourced, the platform treats it as undefined and applies the fail-closed rule below.Amended 11 August 2026. An earlier draft of this warning said no Malaysian primary source publishes a min/max for any crop-product pair. That is now known to be too strong, and the exceptions are recorded on the three peat rows below.The general finding still holds for mineral soils, where every schedule retrieved — including the nine-soil-group replant schedules — is a table of single point values. What changed is that MPOB publishes ranges and ceilings for oil palm on peat, with stated agronomic reasons.A second correction, on rice. It was previously recorded that the Department of Agriculture’s Leaf Colour Chart nitrogen lookup is unpublished. It was published, in Pakej Teknologi Padi (2008), and removed from the 2022 successor, which mentions the LCC five times without a number and defers to a mobile application. See the rice note under the table.

The three rows that are sourced — oil palm on peat

These carry a different unit and a different key from the rows above, and that is the point rather than an inconsistency: the unit is per palm, not per hectare, and the row is keyed by soil group. Per the units policy, a prescription submitted per hectare is rejected before the min/max check runs. Source for all three: Hasnol Othman, Agronomy & Geospatial Unit, MPOB, Fertilizer Recommendation and Best Management of Oil Palm on Peat Soil, Workshop of MPOB–SOPPOA 2016, Sibu, Sarawak, 10 March 2016, section 8. Verbatim: “The optimum rate of K fertilizer for oil palm on peat recommended at 4.0 - 6.0 kg MOP palm⁻¹ yr⁻¹.” · “The optimum rate of N fertilizer application is less than 1.0 kg palm⁻¹ yr⁻¹ of urea (0.5 to 0.6 kg).” · “The optimum rate of P fertilizer for oil palm on peat should not exceed 1.0 kg RP palm⁻¹ yr⁻¹.” Both ceilings carry a stated reason, which is what makes them defensible rather than conventional. The N ceiling is an emissions limit — “Excessively high N inputs will lead to high nitrous oxide and methane emissions”. The P ceiling is pathological — “Excessive P fertilizer application should be avoided to ensure lower Ganoderma basal stem rot incidence”. Two limits on how far these travel. They are peat only, and applying them to a mineral-soil field would be the same class of error as reading a young stand against a prime band. And the same source defers magnesium and mature-palm micronutrients to leaf analysis rather than publishing a rate — “Mg fertilizer requirement should be base on leaf analysis results” — so the absence of an Mg row here is MPOB’s position, not a gap in our sourcing.

Rice — the LCC lookup was published in 2008 and withdrawn in 2022

This is a documented point recommendation with a trigger, not a band, so it does not fill a row above. It is recorded because the previous note that no lookup exists was wrong, and because it is the nitrogen rule DOA’s own method actually uses. Jabatan Pertanian Malaysia, Pakej Teknologi Padi, Cetakan Pertama 2008, ISBN 978-983-047-139-6, Jadual 11 (tabur terus, p. 29) and Jadual 12 (mengubah, p. 30):
  • Threshold — “Tahap kritikal, Leaf Color Chart (LCC) No.3 atau bacaan SPAD kurang 32”
  • Dose — “Baja Urea (Tambahan) … 50 kg/ha (luar musim) atau 75 kg/ha (musim utama)
Applied at 35 HLT and again at 50 HLT (direct-seeded) or 55 HLT (transplanted). Four caveats travel with those figures and none may be dropped:
  • The rate is kilograms of urea product, not kilograms of N. The document never states urea’s nitrogen content. Converting silently would be a fabricated figure.
  • The chart’s panel count is never stated. Shade 3 on a four-panel chart is a different colour from shade 3 on a six-panel chart, and the two are not interchangeable.
  • It is variety-blind. No variety column, and MR263 and MR269 do not appear in the document.
  • Two Malaysian sources disagree. IADA Pulau Pinang (Manual Tanaman Padi, KPKM, 2019) uses No. 3 for direct-seeded and No. 4 for transplanted, and prints 76 rather than 75 kg/ha. Decision required before either is used.
The 2022 successorRice Check: Padi, ISBN 978-983-047-315-4 — mentions the LCC five times, states no threshold and no dose, and defers to the “aplikasi LCC Padi”. Its own rate column is headed Bantuan Pemberian Kerajaan, a subsidy allocation rather than an agronomic optimum. One behaviour that is safe to encode now. Main season carries more nitrogen than off-season — MARDI +15%, DOA +50% on the LCC top-up — and the published reason is leaching under higher main-season rainfall, not the yield gap. Main season yields less and receives more. Any interface implying more fertiliser, more yield reads backwards here.

Decision required

Surfaced explicitly so they are not lost in the table:
  • Decision required: For rice, does MARDI/DOA publish stage-specific N min/max (tillering, panicle initiation), or only a season total? This determines whether the band uses by_stage or basis: season with per-pass derivation. Partly answered, 11 August 2026: stage-specific, and as points rather than a min/max. DOA gives four discrete events with named stages and day windows; MARDI gives a season total of 120:70:80 kg N, P₂O₅, K₂O per hectare with N raised 15% for the main season. Three mutually inconsistent national rates are now on record — DOA 170:80:150, MARDI 120:70:80, and 90–120 N / 12–18 P / 90–120 K cited in the literature — and none is a function of a soil test. Which one the platform treats as the reference is a decision, not a lookup.
  • Decision required: For oil palm N, K, and Mg, does MPOB publish bands by soil group (inland mineral, coastal clay, peat), or a single band? This determines whether the row uses by_ecosystem / by_soil_group or a flat band. Partly answered, 11 August 2026. Peat is handled as its own regime and does carry ranges — see the three sourced rows above. Mineral soils are keyed by soil series, not by the three-way inland / coastal / peat split assumed here, and every mineral-soil schedule retrieved is points. The remaining question is narrower: what does MPOB Oil Palm Bulletin 72 contain? It is reachable at palmoilis.mpob.gov.my/publications/OPB/opb72-afandi.pdfno longer a dead link — but it is a scanned document with no text layer, as are the MPOB TT series. Index snippets show a table classifying nutrients very low to very high and a nutrient-balance table. OCR of that one document is the highest-value outstanding action on this page.
  • Decision required: For rubber, do RRIM recommendations distinguish immature (pre-tapping) vs mature (tapped) stands within the min/max, or only in the recommended rate? Stands change the band, not just the recipe.
  • Decision required: For pineapple ethephon, is there a Malaysian primary source (MARDI or the product label registered with Department of Agriculture) that publishes a maximum per-plant dose that can be defended, or should this product be left undefined and therefore refused? If no defensible source, leave undefined; the platform will refuse ethephon prescriptions rather than accept any rate.
  • Decision required: For every row above, the exact citation (document title, year, page or section) that will be recorded on the band. Recommendations change across MARDI/DOA/MPOB revisions; the band must name the revision it comes from.

Fail-closed rule for undefined combinations

Where no band exists for a crop and product, the platform declines the prescription. It does not fall back to “any rate is acceptable.” This is the rule that keeps the guardrail honest while the table is being filled in. Concretely:
Amended 13 August 2026. The three bullets below were written before Attribution existed, and said flatly that a missing band rejects. A missing band alone no longer rejects — a missing band and no named author does. They are corrected here so this section and the next agree.
  • The VRA Maps Input validation guardrail rejects a prescription whose (crop, product) pair has no product_bands entry, or has an entry with status: decision_required or status: undefined, and whose rate carries no named entitled author. The rejection cites the missing band by id and links here. Where an entitled author has set the rate, it passes as attributed and is labelled as such on the prescription.
  • The Semai Advisor treats a missing band with no author as a hard refusal, not a confirmation prompt. Confirmation only applies where a band exists and the requested rate is outside it. The advisor may never supply the author itself — a pre-filled rate is unattributed until a person sets or accepts it, so the advisor’s own drafts refuse until someone signs them.
  • The Failure Modes trigger “requests an action that would exceed agronomic min/max rates” extends to “requests an action for a crop-product pair with no defined band and no named author.” Both fire the same degradation.
  • The unit is checked before the value. A rate whose unit does not match its band — a per-hectare figure against a per-palm band — is refused on the unit, and no min/max comparison is attempted. Converting silently would fabricate a figure.
A missing guardrail fails closed. That is the difference between we have not sourced this yet and any rate is acceptable here, and the second one puts fertiliser on the ground.

Attribution: the second route to a permitted rate

The rule above is correct and it has a consequence worth stating plainly: while every band is decision_required, the guardrail permits nothing. A guardrail that has never let a prescription through is not yet a working feature, and the table may stay unfilled for a long time — no Malaysian authority publishes a min/max for any crop-product pair, only point recommendations. So there are two routes to a permitted rate, and only two: A rate with neither a band nor an author is still refused. That is the fail-closed rule, unchanged. What changes is that no source and no accountability stop being the same condition.

What an attributed rate must carry

Every attributed rate records set_by, set_by_role, set_at, and a stated reason. These are not a band: a band is a range that validates many prescriptions, and an attributed rate is one figure on one map. Do not add a fifth status value to the bands table for this — the band stays decision_required until somebody sources it, and attribution is recorded on the prescription instead.

Attribution never substitutes for a regulatory limit

Attributed rates apply to fertiliser and soil amendments only. They are refused, regardless of who signs, for:
  • restricted products — pesticides and high-nitrogen formulations, per the existing safety floor on VRA Maps;
  • anything constrained by regulation rather than agronomy — buffer zones near waterways, regulated boundaries, and statutory maximum application rates.
Where the constraint is law, a name is not authority. The safety floor is unchanged by this section.

An attributed rate does not become a default

A figure entered once will be copied to the next map, and to the one after that, until it is a house standard nobody has read the reasoning for. That is the same fabricated default this page refuses, arrived at by drift instead of by decision. So a rate carried forward from an earlier map carries its origin with it — who set it, when, and on which map — and the reader is told it is being reused rather than decided. Reuse is not authorship.

The difference is visible where the rate is used

A sourced rate and an attributed rate must be distinguishable on the prescription itself, not only in a settings table. The bands table will hold a mix for a long time — the one genuine published range found to date is ethephon on pineapple — and a citation and a judgement that look identical at the point of use are worse than either alone. The wording changes with the route, and both remain answers:
  • No band, no author“No published recommendation exists for this crop and product, and nobody has set a rate for it. This prescription is refused.”
  • No band, named author“This rate is not sourced from any published recommendation. It was set by Siti Rahman on 3 August and applies to this map only.”

Who may set one

Decided 11 August 2026. agronomist, approver and estate_manager may set an attributed rate. scout may not — its documented function is ground-truth work, not diagnosis, which is the same reason it cannot raise a finding above medium. The mismatch that blocked this is resolved: the role matrix on VRA Maps named Operator and Read-only, neither of which is one of the eight. No ninth role was added. Operator’s permissions were distributed — as-applied upload to scout, generation to agronomist and above — and Read-only became viewer with its review permission removed, since attaching a comment is a write. And the entitlement now has a second half: the approver may not be the author. A rate resting on one person’s judgement, approved by that same person, is a single unchecked point of decision on a physical operation — which is precisely what attribution exists to prevent. The rule and its fallback are on VRA Maps → Approval is a second person.

Provenance

Each row on the bands table carries the same provenance tags as yield_impact:
  • sourced — attributed to a named Malaysian primary source with an exact citation. Enforced.
  • decision_required — schema exists, numbers do not. Rejected by the fail-closed rule.
  • undefined — the agronomy team has explicitly declined to publish a band for this pair (e.g. product not supported in Malaysia, or no defensible source). Rejected by the fail-closed rule with an explanatory message.
  • agronomy_reviewed — a band adjusted from the primary source by the internal agronomy team, with rationale recorded.
Bands, like rule cards, are re-checked annually and whenever the underlying Malaysian source publishes a revision.

Docs Delta

New section on this page: Agronomic rate bands (schema, units policy, basis rules, stage and tier keying, band table, fail-closed rule, provenance). The following pre-existing references now resolve to a live definition on this page:
  • VRA Maps — Input validation and Refusals rows in the guardrails table. “Rates must fall within the crop-specific agronomic min/max declared in the Risk Model” now points at the bands table above.
  • Semai Advisor overview — safety-critical writes list. “Rates outside agronomic min/max” now resolves to the same bands.
  • Semai Advisor failure modes — degradation trigger for “exceed agronomic min/max rates” now resolves to the same bands.
Fail-closed rule (repeated here so it is not lost inside the section): a crop-product pair with no sourced band and no named author produces a refusal, not a silent pass. This applies to VRA validation, Semai Advisor writes, and the failure-modes trigger, uniformly. Attribution is the second route, added above: where no band exists, a rate may proceed if it carries a named author who was entitled to set it, and it is then rendered as a judgement rather than as a citation. It does not apply to restricted products or to anything constrained by regulation, where the safety floor stands unchanged. Both decisions that section opened are now closedagronomist, approver and estate_manager may set an attributed rate, and the approver may not be the author. prescription-maps was reconciled with the role model in the same pass: Operator distributed across the eight, Read-only resolved to viewer, no ninth functional role added. Not changed: no new guardrail categories, no new telemetry types. Attribution is recorded on the prescription and does not add a status value to the bands table — a band remains decision_required until it is sourced.

Days to mitigate

The “Days to mitigate” number on the risk card is not a static rule constant. It answers a specific question: how long does the operator have before the recommended action stops being effective? The engine resolves it in two steps:
  1. Pick the base window from mitigation.window_days_by_severity using the current severity band.
  2. Add any window_days_stage_modifier value for the current phenology stage (usually negative, tightening the window at critical stages like heading or flowering).
A HIGH-severity blast risk at heading with the schema above resolves to 3 + (-2) = 1 day. That is honest: at heading, once conditions are right for neck blast, the preventive-spray window is very short. Rules that describe slow-developing conditions (nutrient deficiencies, salinity buildup) can keep windows in the 10-30 day range. Rules for fast-moving weather-driven diseases should tighten aggressively at HIGH severity.

Activity bindings

alert_bindings covers data-driven inputs. activity_bindings covers team activity - the human side of the platform. Together they let a rule card react to both a falling NDVI and a completed scout report in the same way. Each binding declares an event, an optional when condition, and an effect on the rule’s current severity for a field.

Effect vocabulary

Bindings are evaluated in order; the first match applies. All severity changes are logged with the triggering event so operators can audit why a card was promoted.
Every activity_bindings entry becomes a row in the Activity & Alerts feed with a “why this changed” summary. This is the single mechanism that lets a human scout report tighten (or relax) an AI risk forecast.

Authoring rule cards

  1. Start from the existing diagnosis page (for example, Blast).
  2. Extract the numeric thresholds already stated in prose (e.g. “cool nights below 22 C and humidity above 90% for 3+ nights”) into drivers.
  3. Ask the agronomy team for a per-stage yield-loss table if one is not already published. This is usually the only new content required.
  4. Add the rule_card: block to the page’s frontmatter. The page continues to render normally; the engine reads the block at build time.
  5. Set last_reviewed to today and open a PR. Cards are validated against a JSON Schema in CI.
A diagnosis page without a rule_card: block is still valuable to users but is invisible to the Alerts and Risk Monitoring engines. Prioritize rule cards for the top 5-10 conditions per crop first.

What the AI advisor returns

Every advisor response follows the same four-part schema, populated from the rule card and the retrieved literature page: This structure is what makes the advisor useful in the field: the operator never has to guess whether the AI is speculating or citing your literature.
  • Vegetation Indices - the signal layer’s core inputs.
  • Field Data Model - fields, zones, seasons, observations the engines run against.
  • Alerts - user-facing alert types and delivery.
  • Rice Blast - reference page with a fully populated rule_card: example.
  • Aggregation Model - how rule card counts and areas roll up to estate, regional, and national dashboards.
  • Fields Workspace - the operator view where rule-card findings are read and acted on.
  • Crop Cycle Models - Season Type vocabulary that season_type_filter gates on.