Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gentle-donkeys-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nmi-agro/fdm-agents": minor
"@nmi-agro/fdm-app": minor
---

Gerrit is now Renure-aware. Added an `includeRenure` fertilizer plan strategy (RVO mestcodes 130-134) with a new "RENURE" prompt section explaining the 80 kg N/ha norm (on top of the 170 kg dierlijke-mest norm). `searchFertilizers` now exposes `p_type_rvo` and, together with `simulateFarmPlan`'s new compliance check, only filters/flags Renure products when `includeRenure` is false **and** the plan's calendar year is 2026 or later — Renure has no legal meaning before 2026, so the toggle never affects earlier years.
5 changes: 5 additions & 0 deletions .changeset/gentle-herons-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-data": minor
---

Add four default Renure products (RVO mestcodes 130-133) to the BAAT fertilizer catalogue: Ammoniumsulfaatoplossing, Ammoniumnitraatoplossing, Mineralenconcentraat and Ammoniumstruviet. Also two non-Renure variants of Ammoniumstruviet and Kaliumstruviet are added. For mestcode 134 no default product has been added to the catalogue as it is a generic "other" category.
5 changes: 5 additions & 0 deletions .changeset/khaki-corners-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-calculator": minor
---

Add support for Renure at norms calculation. For 2026 add Renure as 4th norm and provide value and filling functions. For 2025 do not return Renure norm and count as manure for filling.
5 changes: 5 additions & 0 deletions .changeset/quiet-jokes-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-core": patch
---

`syncFertilizerCatalogueArray` (and therefore `syncCatalogues`, run on app startup) now automatically acquires a newly introduced fertilizer catalogue product for every existing farm that already has that catalogue source enabled, mirroring the bulk-acquisition that happens when a new farm is created. Previously, only farms created after a product was added to the catalogue would ever see it; existing farms had no automatic or manual way to pick up new catalogue products (e.g. the new Renure BAAT products). This only applies to genuinely new catalogue items (first-time insert); updates to existing catalogue items are unaffected.
5 changes: 5 additions & 0 deletions .changeset/six-games-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-core": minor
---

Add for `p_type_rvo` the Mestcodes for Renure: 130, 131, 132, 133, 134
5 changes: 5 additions & 0 deletions .changeset/yummy-wasps-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-app": minor
---

Show the Renure norm at Gebruiksruimte for 2026 and later
11 changes: 7 additions & 4 deletions fdm-agents/src/agents/gerrit/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Voer voor elke simulatie en voor het eindantwoord een handleiding-conformiteitsc
Na elke simulateFarmPlan-aanroep:
1. Controleer isValid. Als deze false is, lees complianceIssues — elk bericht noemt de overschreden norm en de overschrijding in kg. Los die overschrijdingen op voordat je verdergaat.
2. Lees agronomicWarnings voor hints over zachte grenzen (organische stof, stikstofbalans, mestbenutting). Gebruik ze om het plan te verfijnen.
3. Verifieer vóór het afronden: farmTotals.normsFilling.manure ≤ farmTotals.norms.manure, .nitrogen ≤ .norms.nitrogen, .phosphate ≤ .norms.phosphate.
3. Verifieer vóór het afronden: farmTotals.normsFilling.manure ≤ farmTotals.norms.manure, .nitrogen ≤ .norms.nitrogen, .phosphate ≤ .norms.phosphate, en vanaf 2026 tevens .renure ≤ .norms.renure.

**Roep simulateFarmPlan NOOIT twee keer aan met identieke percelen, meststoffen, hoeveelheden en timing.** Elke simulatie moet iets wezenlijks veranderen (hoeveelheden, producten of data). Maximaal 5 simulaties per planningsronde.

Expand All @@ -61,7 +61,8 @@ b. De exacte beperkende factor (product, N, P, of de gewashandleiding) uit te le
## RANDVOORWAARDEN

### 1. WETTELIJKE NORMEN — BEDRIJFSNIVEAU IN KG
De Nederlandse wet stelt drie wettelijke grenzen op **bedrijfsniveau**, uitgedrukt in **kg** (niet kg/ha). Individuele percelen mogen hun perceelsnorm overschrijden; alleen het bedrijfstotaal telt.
De Nederlandse wet stelt wettelijke grenzen op **bedrijfsniveau**, uitgedrukt in **kg** (niet kg/ha). Individuele percelen mogen hun perceelsnorm overschrijden; alleen het bedrijfstotaal telt.
Voor kalenderjaar 2025 geldt een stelsel van drie normen. Vanaf kalenderjaar 2026 geldt een stelsel van vier normen (inclusief Renure).

Formule: farmTotal_kg = Σ (fieldNorm_kg_per_ha × fieldArea_ha) over alle percelen.
Voorbeeld: 10 ha à 170 kg N/ha + 5 ha à 230 kg N/ha = 1700 + 1150 = 2850 kg N totaal.
Expand All @@ -70,6 +71,7 @@ De simulatietool berekent en retourneert farmTotals automatisch. Conform zijn be
- farmTotals.normsFilling.manure ≤ farmTotals.norms.manure (dierlijke mest stikstof N)
- farmTotals.normsFilling.nitrogen ≤ farmTotals.norms.nitrogen (werkzame stikstof N)
- farmTotals.normsFilling.phosphate ≤ farmTotals.norms.phosphate (fosfaat P₂O₅)
- Vanaf kalenderjaar 2026: farmTotals.normsFilling.renure ≤ farmTotals.norms.renure (Renure stikstof N, eigen plafond van 80 kg N/ha per jaar, bovenop de dierlijke-mestnorm)

### 2. STRATEGIE MESTRUIMTE VULLEN (alleen actief wanneer fillManureSpace = JA)

Expand Down Expand Up @@ -153,8 +155,8 @@ Je eindantwoord MOET één enkel JSON-object zijn met onderstaande structuur. Vo
"summary": "string — Nederlandse toelichting < 250 woorden",
"metrics": {
"farmTotals": {
"normsFilling": { "manure": number, "nitrogen": number, "phosphate": number },
"norms": { "manure": number, "nitrogen": number, "phosphate": number },
"normsFilling": { "manure": number, "nitrogen": number, "phosphate": number, "renure": number (optioneel, alleen vanaf 2026) },
"norms": { "manure": number, "nitrogen": number, "phosphate": number, "renure": number (optioneel, alleen vanaf 2026) },
"nBalance": {
"balance": number,
"target": number,
Expand Down Expand Up @@ -207,6 +209,7 @@ Alle nutriënthoeveelheden per perceel zijn in **kg/ha**.
- normsFilling.nitrogen: totale toegediende werkzame N (bedrijf = kg, perceel = kg/ha).
- normsFilling.phosphate: totale toegediende P₂O₅ (bedrijf = kg, perceel = kg/ha).
- norms.manure / nitrogen / phosphate: wettelijk maximum (bedrijf = kg, perceel = kg/ha). Perceelsresultaten bevatten een "normSource"-string.
- Vanaf kalenderjaar 2026: norms.renure / normsFilling.renure: Renure maximum en toedieningsvulling (bedrijf = kg, perceel = kg/ha).
- omBalance: netto organische stofbalans, kg EOS/ha. Positief = goed, streef naar ≥ 0.
- nBalance: balance en target in kg N/ha; emissietotalen ook in kg N/ha. Op bedrijfsniveau gewogen naar oppervlakte door de simulatietool.
- p_dose_nw: werkzame (effectieve) N kg/ha — vergelijk met d_n_req. p_dose_n is totale N (alleen referentie).
Expand Down
1 change: 1 addition & 0 deletions fdm-agents/src/agents/gerrit/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const NormValuesSchema = z.object({
manure: z.number(),
nitrogen: z.number(),
phosphate: z.number(),
renure: z.number().optional().describe("Only present/required for 2026 and later"),
})

/**
Expand Down
60 changes: 60 additions & 0 deletions fdm-agents/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("fdm-agents index", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: true,
isDerogation: false,
includeRenure: false,
}
expect(FertilizerPlanStrategiesSchema.parse(valid)).toEqual(valid)
})
Expand Down Expand Up @@ -90,6 +91,7 @@ describe("fdm-agents index", () => {
keepNitrogenBalanceBelowTarget: true,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}
const calendar = "2025"
const additionalContext = "Please use organic compost."
Expand All @@ -105,6 +107,57 @@ describe("fdm-agents index", () => {
expect(prompt).toContain("--- END ADDITIONAL USER CONTEXT ---")
})

it("should omit the Renure strategy line entirely for years before 2026", () => {
const farmData = { b_id_farm: "farm-123" }
const strategies = {
isOrganic: false,
fillManureSpace: false,
reduceAmmoniaEmissions: false,
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}

const prompt = buildFertilizerPlanPrompt(farmData, strategies, "2025")

expect(prompt).not.toContain("Renure")
})

it("should include the Renure strategy line for calendar year 2026 and later", () => {
const farmData = { b_id_farm: "farm-123" }
const strategies = {
isOrganic: false,
fillManureSpace: false,
reduceAmmoniaEmissions: false,
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: true,
}

const prompt = buildFertilizerPlanPrompt(farmData, strategies, "2026")

expect(prompt).toContain("Renure-producten overwegen: JA")
})

it("should state Renure is excluded when includeRenure is false for 2026", () => {
const farmData = { b_id_farm: "farm-123" }
const strategies = {
isOrganic: false,
fillManureSpace: false,
reduceAmmoniaEmissions: false,
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}

const prompt = buildFertilizerPlanPrompt(farmData, strategies, "2026")

expect(prompt).toContain("Renure-producten overwegen: NEE")
})

it("should include field summary if provided", () => {
const farmData = { b_id_farm: "farm-123" }
const strategies = {
Expand All @@ -114,6 +167,7 @@ describe("fdm-agents index", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}
const fieldsSummary = [
{
Expand Down Expand Up @@ -151,6 +205,7 @@ describe("fdm-agents index", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}
const fieldsSummary = [
{
Expand Down Expand Up @@ -285,6 +340,7 @@ describe("buildClarificationsBlock", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
},
"2025",
undefined,
Expand All @@ -310,6 +366,7 @@ describe("buildClarificationsBlock", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
},
"2025",
)
Expand All @@ -326,6 +383,7 @@ describe("buildClarificationsBlock", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
},
"2025",
undefined,
Expand All @@ -348,6 +406,7 @@ describe("buildClarificationsBlock", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
},
"2025",
)
Expand All @@ -363,6 +422,7 @@ describe("generateFarmFertilizerPlan", () => {
keepNitrogenBalanceBelowTarget: false,
workOnRotationLevel: false,
isDerogation: false,
includeRenure: false,
}

it("should call runOneShotAgent and return result string", async () => {
Expand Down
13 changes: 12 additions & 1 deletion fdm-agents/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface FertilizerPlanStrategies {
workOnRotationLevel: boolean
/** Whether the farm operates under derogation (prohibits mineral fertilizers containing phosphate) */
isDerogation: boolean
/** Whether to allow Renure products (RVO mestcodes 130-134) in the plan. Only relevant for calendar year 2026 and later. */
includeRenure: boolean
}

/** Schema for validating FertilizerPlanStrategies — all fields must be explicit booleans. */
Expand All @@ -48,6 +50,7 @@ export const FertilizerPlanStrategiesSchema = z.object({
keepNitrogenBalanceBelowTarget: z.boolean(),
workOnRotationLevel: z.boolean(),
isDerogation: z.boolean(),
includeRenure: z.boolean(),
})

/** Compact field summary injected into the initial prompt for faster agent orientation. */
Expand Down Expand Up @@ -151,6 +154,7 @@ export function buildFertilizerPlanPrompt(
): string {
const validatedStrategies = FertilizerPlanStrategiesSchema.parse(strategies)
const safeContext = additionalContext ? sanitizeAdditionalContext(additionalContext) : "None"
const isRenureApplicable = Number.parseInt(calendar, 10) >= 2026

// Filter out non-productive fields: buffer strips, nature/landscape elements, and small fragments
const productiveFields = fieldsSummary?.filter(
Expand All @@ -177,6 +181,13 @@ export function buildFertilizerPlanPrompt(
? `\nGESELECTEERDE MESTSTOFFEN (alleen deze gebruiken):\n${selectedFertilizerIds.map((id) => `- ${id}`).join("\n")}\nGebruik uitsluitend meststoffen uit bovenstaande lijst. Sla meststoffen die hier niet in staan over, ook als ze beschikbaar zijn in de inventaris.\n`
: ""

// Only mention Renure at all when the plan year is 2026 or later — before then, Renure has no
// legal meaning (RVO mestcodes 130-134 simply count as regular animal manure), so surfacing the
// toggle or the norm explanation for earlier years would be confusing/inapplicable noise.
const renureBlock = isRenureApplicable
? `- Renure-producten overwegen: ${validatedStrategies.includeRenure ? "JA (mag Renure-producten gebruiken (RVO mestcodes 130-134); deze tellen niet mee voor de 170 kg dierlijke-mestnorm, maar wel voor de werkzame-stikstofnorm en fosfaatnorm, met een eigen maximum van 80 kg N/ha bovenop de dierlijke-mestnorm)" : "NEE (gebruik GEEN producten met RVO mestcode 130-134 in dit plan)"}\n`
: ""

return `Stel een bemestingsplan op voor bedrijf "${farmData.b_id_farm}" voor het jaar "${calendar}".
${fieldsBlock}
TE HANDHAVEN STRATEGIEËN:
Expand All @@ -186,7 +197,7 @@ TE HANDHAVEN STRATEGIEËN:
- Stikstofbalans onder streefwaarde houden: ${validatedStrategies.keepNitrogenBalanceBelowTarget ? "JA (Zorg dat het stikstofbalansoverschot op bedrijfsniveau onder het bedrijfsomgevingsdoel blijft. Individuele percelen mogen hun doel overschrijden als dit door andere percelen wordt gecompenseerd)" : "NEE"}
- Werken op bouwplanniveau: ${validatedStrategies.workOnRotationLevel ? "JA (Alle percelen met hetzelfde b_lu_catalogue MOETEN identieke giften ontvangen — dezelfde producten, hoeveelheden, data en methoden)" : "NEE"}
- Derogatie: ${validatedStrategies.isDerogation ? "JA (Geen minerale meststoffen met fosfaat toegestaan)" : "NEE"}
${fertilizersBlock}${clarificationsBlock}
${renureBlock}${fertilizersBlock}${clarificationsBlock}
--- BEGIN ADDITIONAL USER CONTEXT ---
${safeContext}
--- END ADDITIONAL USER CONTEXT ---
Expand Down
Loading
Loading