From ddecb4eda0f30b000b1ade497e7364b928a71e78 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:39:13 +0100 Subject: [PATCH 01/20] refactor: make hoofteelt function available for other years as well --- .../norms/nl/2025/value/fosfaatgebruiksnorm.ts | 4 ++-- .../src/norms/nl/2025/value/hoofdteelt.test.ts | 16 ++++++++-------- .../src/norms/nl/2025/value/hoofdteelt.ts | 11 ++++++----- .../norms/nl/2025/value/stikstofgebruiksnorm.ts | 11 ++++++----- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index bb592b341..6a10edb3e 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -2,7 +2,7 @@ import { withCalculationCache } from "@svenvw/fdm-core" import Decimal from "decimal.js" import pkg from "../../../../package" import { fosfaatNormsData } from "./fosfaatgebruiksnorm-data" -import { determineNL2025Hoofdteelt } from "./hoofdteelt" +import { determineNLHoofdteelt } from "./hoofdteelt" import type { FosfaatGebruiksnormResult, FosfaatKlasse, @@ -150,7 +150,7 @@ export async function calculateNL2025FosfaatGebruiksNorm( ) } - const b_lu_catalogue = determineNL2025Hoofdteelt(cultivations) + const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2025) const is_grasland = isCultivationGrasland(b_lu_catalogue) // Determine the phosphate class based on soil analysis values and land type. diff --git a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.test.ts b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.test.ts index c7127f631..ba6759ce0 100644 --- a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest" -import { determineNL2025Hoofdteelt } from "./hoofdteelt" +import { determineNLHoofdteelt } from "./hoofdteelt" import type { NL2025NormsInputForCultivation } from "./types" -describe("determineNL2025Hoofdteelt", () => { +describe("determineNLHoofdteelt", () => { it("should return the cultivation with the longest duration in the period", async () => { const cultivations: NL2025NormsInputForCultivation[] = [ { @@ -20,7 +20,7 @@ describe("determineNL2025Hoofdteelt", () => { b_lu_variety: null, }, ] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations, 2025) expect(result).toBe("cat_B") }) @@ -41,7 +41,7 @@ describe("determineNL2025Hoofdteelt", () => { b_lu_variety: null, }, ] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations, 2025) expect(result).toBe("cat_C") }) @@ -55,7 +55,7 @@ describe("determineNL2025Hoofdteelt", () => { b_lu_variety: null, }, ] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations , 2025) expect(result).toBe("nl_6794") }) @@ -76,13 +76,13 @@ describe("determineNL2025Hoofdteelt", () => { b_lu_variety: null, }, ] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations, 2025) expect(result).toBe("cat_F") }) it("should handle an empty array of cultivations by returning nl_6794", async () => { const cultivations: NL2025NormsInputForCultivation[] = [] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations, 2025) expect(result).toBe("nl_6794") }) @@ -96,7 +96,7 @@ describe("determineNL2025Hoofdteelt", () => { b_lu_variety: null, }, ] - const result = await determineNL2025Hoofdteelt(cultivations) + const result = await determineNLHoofdteelt(cultivations, 2025) expect(result).toBe("cat_H") }) }) diff --git a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts index 5da2b43aa..a92d0d679 100644 --- a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts +++ b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts @@ -1,7 +1,7 @@ import type { NL2025NormsInputForCultivation } from "./types" /** - * Determines the main cultivation ('hoofdteelt') for the NL 2025 norms based on the legal definition. + * Determines the main cultivation ('hoofdteelt') for the NL 2025 and 2026 norms based on the legal definition. * The main cultivation is the one that is present for the longest duration within the period * from May 15th to July 15th. * @@ -13,14 +13,15 @@ import type { NL2025NormsInputForCultivation } from "./types" * { cultivation: { b_lu_start: '2025-05-01', b_lu_end: '2025-06-10', b_lu_catalogue: 'cat_A' } }, * { cultivation: { b_lu_start: '2025-06-01', b_lu_end: '2025-07-20', b_lu_catalogue: 'cat_B' } } * ]; - * const hoofdteelt = await determineNL2025Hoofdteelt(cultivations); + * const hoofdteelt = await determineNLHoofdteelt(cultivations); * // returns 'cat_B' */ -export function determineNL2025Hoofdteelt( +export function determineNLHoofdteelt( cultivations: NL2025NormsInputForCultivation[], + year: 2025 | 2026, ): string { - const HOOFDTEELT_START = new Date("2025-05-15") - const HOOFDTEELT_END = new Date("2025-07-15") + const HOOFDTEELT_START = new Date(`${year}-05-15`) + const HOOFDTEELT_END = new Date(`${year}-07-15`) let maxDuration = -1 let hoofdteeltCatalogue: string | null = null diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index 8fa38ff5f..c31939b7c 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -3,7 +3,7 @@ import Decimal from "decimal.js" import pkg from "../../../../package" import { getGeoTiffValue } from "../../../../shared/geotiff" import { getFdmPublicDataUrl } from "../../../../shared/public-data-url" -import { determineNL2025Hoofdteelt } from "./hoofdteelt" +import { determineNLHoofdteelt } from "./hoofdteelt" import { nitrogenStandardsData } from "./stikstofgebruiksnorm-data" import type { GebruiksnormResult, @@ -318,7 +318,7 @@ function determineSubTypeOmschrijving( ] if (bladgewasRvoTable2s.includes(standard.cultivation_rvo_table2)) { - const hoofdteeltCatalogue = determineNL2025Hoofdteelt(cultivations) + const hoofdteeltCatalogue = determineNLHoofdteelt(cultivations, 2025) if (cultivation.b_lu_catalogue === hoofdteeltCatalogue) { return "1e teelt" } @@ -363,8 +363,9 @@ function calculateKorting( } // Determine hoofdteelt for the current year (2025) - const hoofdteelt2025 = determineNL2025Hoofdteelt( + const hoofdteelt2025 = determineNLHoofdteelt( cultivations.filter((c) => c.b_lu_start.getFullYear() === currentYear), + 2025 ) const hoofdteelt2025Standard = nitrogenStandardsData.find((ns) => ns.b_lu_catalogue_match.includes(hoofdteelt2025), @@ -478,7 +479,7 @@ function calculateKorting( * correct nitrogen norm: * * 1. **Identify Main Crop (`hoofdteelt`)**: - * The `determineNL2025Hoofdteelt` function is called to identify the primary cultivation + * The `determineNLHoofdteelt` function is called to identify the primary cultivation * (`b_lu_catalogue`) for the field based on the provided `cultivations` array. This is * the first step to narrow down the applicable nitrogen standards. * @@ -540,7 +541,7 @@ export async function calculateNL2025StikstofGebruiksNorm( const cultivations = input.cultivations // Determine hoofdteelt - const b_lu_catalogue = determineNL2025Hoofdteelt(cultivations) + const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2025) let cultivation = cultivations.find( (c) => c.b_lu_catalogue === b_lu_catalogue, ) From 493ce3eb4f7f9f9b69c7a4ddf24699bebd31e009 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:46:23 +0100 Subject: [PATCH 02/20] refactor: use common types for output of norms for multiple years --- .../2025/value/dierlijke-mest-gebruiksnorm.ts | 2 +- .../nl/2025/value/fosfaatgebruiksnorm.ts | 2 +- .../nl/2025/value/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2025/value/types.d.ts | 56 ------------------- fdm-calculator/src/norms/nl/types.ts | 56 +++++++++++++++++++ 5 files changed, 59 insertions(+), 59 deletions(-) create mode 100644 fdm-calculator/src/norms/nl/types.ts diff --git a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.ts index 89ca4a443..5e3b580a2 100644 --- a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.ts @@ -4,9 +4,9 @@ import { getGeoTiffValue } from "../../../../shared/geotiff" import { getFdmPublicDataUrl } from "../../../../shared/public-data-url" import { isFieldInNVGebied } from "./stikstofgebruiksnorm" import type { - DierlijkeMestGebruiksnormResult, NL2025NormsInput, } from "./types.d" +import { DierlijkeMestGebruiksnormResult } from "norms/nl/types" /** * Determines if a field is located within a grondwaterbeschermingsgebied (GWBG) in the Netherlands. diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index 6a10edb3e..44f441718 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -4,10 +4,10 @@ import pkg from "../../../../package" import { fosfaatNormsData } from "./fosfaatgebruiksnorm-data" import { determineNLHoofdteelt } from "./hoofdteelt" import type { - FosfaatGebruiksnormResult, FosfaatKlasse, NL2025NormsInput, } from "./types.d" +import { FosfaatGebruiksnormResult } from "norms/nl/types" /** * Determines if a cultivation is a type of grassland based on its catalogue entry. diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index c31939b7c..f7b0f6d51 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -6,13 +6,13 @@ import { getFdmPublicDataUrl } from "../../../../shared/public-data-url" import { determineNLHoofdteelt } from "./hoofdteelt" import { nitrogenStandardsData } from "./stikstofgebruiksnorm-data" import type { - GebruiksnormResult, NitrogenStandard, NL2025NormsInput, NL2025NormsInputForCultivation, NormsByRegion, RegionKey, } from "./types" +import { GebruiksnormResult } from "norms/nl/types" /** * Determines if a field is located within a met nutriƫnten verontreinigde gebied (NV-gebied) in the Netherlands. diff --git a/fdm-calculator/src/norms/nl/2025/value/types.d.ts b/fdm-calculator/src/norms/nl/2025/value/types.d.ts index d7e2cce05..6e9c2f7e7 100644 --- a/fdm-calculator/src/norms/nl/2025/value/types.d.ts +++ b/fdm-calculator/src/norms/nl/2025/value/types.d.ts @@ -25,53 +25,12 @@ export type NL2025NormsInput = { soilAnalysis: Pick } -/** - * The result object returned by the `getNL2025DierlijkeMestGebruiksNorm` function, - * containing the determined animal manure nitrogen usage norm and its source. - */ -export interface DierlijkeMestGebruiksnormResult { - /** - * The determined usage standard for nitrogen from animal manure in kg N per hectare. - */ - normValue: number - /** - * A descriptive string indicating which rule or category was applied to determine the norm. - * Examples: "Standaard", "Derogatie - NV Gebied", "Derogatie - Buiten NV Gebied". - */ - normSource: string -} - -/** - * Represents the phosphate usage norm values for a specific phosphate class, - * differentiated by grassland and arable land. - */ -export interface FosfaatNorm { - grasland: number - bouwland: number -} - /** * Defines the possible phosphate classes based on RVO's "Tabel Fosfaatgebruiksnormen 2025". * These classes are determined by P-CaCl2 and P-Al soil analysis values. */ export type FosfaatKlasse = "Arm" | "Laag" | "Neutraal" | "Ruim" | "Hoog" -/** - * The result object returned by the `getNL2025FosfaatGebruiksNorm` function, - * containing the determined phosphate usage norm and the corresponding phosphate class. - */ -export interface FosfaatGebruiksnormResult { - /** - * The determined phosphate usage standard in kg P2O5 per hectare. - */ - normValue: number - /** - * The cultivation and phosphate class ('Arm', 'Laag', 'Neutraal', 'Ruim', 'Hoog') - * that was determined from the soil analysis values and used to derive the norm. - */ - normSource: string -} - /** * Defines the structure for a single nitrogen standard entry, * based on the RVO's "Tabel 2 Stikstof landbouwgrond 2025" and related documents. @@ -153,18 +112,3 @@ export type NormsByRegion = { [key in RegionKey]: { standard: number; nv_area: number } } -/** - * The result object returned by the `getNL2025StikstofGebruiksNorm` function, - * containing the calculated norm value and the name of the cultivation used for the calculation. - */ -export interface GebruiksnormResult { - /** - * The determined nitrogen usage standard in kg N per hectare. - */ - normValue: number - /** - * The cultivation name according to RVO's "Tabel 2 Stikstof landbouwgrond 2025" - * that was used to determine the legal limit. - */ - normSource: string -} diff --git a/fdm-calculator/src/norms/nl/types.ts b/fdm-calculator/src/norms/nl/types.ts new file mode 100644 index 000000000..5e5eb4cba --- /dev/null +++ b/fdm-calculator/src/norms/nl/types.ts @@ -0,0 +1,56 @@ +/** + * The result object returned by the `getNL{*}DierlijkeMestGebruiksNorm` function, + * containing the determined animal manure nitrogen usage norm and its source. + */ +export interface DierlijkeMestGebruiksnormResult { + /** + * The determined usage standard for nitrogen from animal manure in kg N per hectare. + */ + normValue: number + /** + * A descriptive string indicating which rule or category was applied to determine the norm. + * Examples: "Standaard", "Derogatie - NV Gebied", "Derogatie - Buiten NV Gebied". + */ + normSource: string +} + +/** + * Represents the phosphate usage norm values for a specific phosphate class, + * differentiated by grassland and arable land. + */ +export interface FosfaatNorm { + grasland: number + bouwland: number +} + +/** + * The result object returned by the `getNL{*}FosfaatGebruiksNorm` function, + * containing the determined phosphate usage norm and the corresponding phosphate class. + */ +export interface FosfaatGebruiksnormResult { + /** + * The determined phosphate usage standard in kg P2O5 per hectare. + */ + normValue: number + /** + * The cultivation and phosphate class ('Arm', 'Laag', 'Neutraal', 'Ruim', 'Hoog') + * that was determined from the soil analysis values and used to derive the norm. + */ + normSource: string +} + +/** + * The result object returned by the `getNL{*}StikstofGebruiksNorm` function, + * containing the calculated norm value and the name of the cultivation used for the calculation. + */ +export interface GebruiksnormResult { + /** + * The determined nitrogen usage standard in kg N per hectare. + */ + normValue: number + /** + * The cultivation name according to RVO's "Tabel 2 Stikstof landbouwgrond 2025" + * that was used to determine the legal limit. + */ + normSource: string +} From c4a236fca1c0e39b701b171014dfc9e11b3bfc4f Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:54:28 +0100 Subject: [PATCH 03/20] refactor: make grassland check available --- fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index 44f441718..0cd5b570a 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -14,7 +14,7 @@ import { FosfaatGebruiksnormResult } from "norms/nl/types" * @param b_lu_catalogue - The cultivation catalogue code. * @returns A promise that resolves to a boolean. */ -function isCultivationGrasland(b_lu_catalogue: string): boolean { +export function isCultivationGrasland(b_lu_catalogue: string): boolean { const graslandCodes = ["nl_265", "nl_266", "nl_331", "nl_332", "nl_335"] if (graslandCodes.includes(b_lu_catalogue)) { From b7144cb7361bec25f31ff6b8d942be4bdfe94164 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:28:23 +0100 Subject: [PATCH 04/20] refactor: make filling available for other years --- .../norms/nl/2025/filling/dierlijke-mest-gebruiksnorm.ts | 3 ++- .../src/norms/nl/2025/filling/fosfaatgebruiksnorm.ts | 3 ++- fdm-calculator/src/norms/nl/2025/filling/input.test.ts | 8 ++++---- fdm-calculator/src/norms/nl/2025/filling/input.ts | 2 +- .../src/norms/nl/2025/filling/stikstofgebruiksnorm.ts | 2 +- fdm-calculator/src/norms/nl/2025/filling/types.d.ts | 9 --------- fdm-calculator/src/norms/nl/types.ts | 9 +++++++++ 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2025/filling/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/filling/dierlijke-mest-gebruiksnorm.ts index 69766ac68..4ca927bf7 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/dierlijke-mest-gebruiksnorm.ts @@ -2,7 +2,8 @@ import { withCalculationCache } from "@svenvw/fdm-core" import Decimal from "decimal.js" import pkg from "../../../../package" import { table11Mestcodes } from "./table-11-mestcodes" -import type { NL2025NormsFillingInput, NormFilling } from "./types" +import type { NL2025NormsFillingInput } from "./types" +import type { NormFilling } from "norms/nl/types" /** * Calculates the nitrogen usage from animal manure for a list of fertilizer applications. diff --git a/fdm-calculator/src/norms/nl/2025/filling/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/filling/fosfaatgebruiksnorm.ts index 6f0881e3e..57dc3921d 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/fosfaatgebruiksnorm.ts @@ -6,7 +6,8 @@ import { import Decimal from "decimal.js" import pkg from "../../../../package" import { table11Mestcodes } from "./table-11-mestcodes" -import type { NL2025NormsFillingInput, NormFilling } from "./types" +import type { NL2025NormsFillingInput} from "./types" +import type { NormFilling } from "norms/nl/types" const rvoMestcodesOrganicRich25Percent = ["111", "112"] // Compost, Zeer schone compost const rvoMestcodesOrganicRich75Percent = ["110", "10", "61", "25", "56"] // Champost, Rundvee - Vaste mest, Geiten - Vaste mest, Paarden - Vaste mest, Schapen - Mest, alle systemen diff --git a/fdm-calculator/src/norms/nl/2025/filling/input.test.ts b/fdm-calculator/src/norms/nl/2025/filling/input.test.ts index 86a65d0de..e03c1cd30 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/input.test.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/input.test.ts @@ -6,7 +6,7 @@ import type { Field, } from "@svenvw/fdm-core" import { beforeEach, describe, expect, it, vi } from "vitest" -import { collectInputForFertilizerApplicationFilling } from "./input" +import { collectNL2025InputForFertilizerApplicationFilling } from "./input" import type { NL2025NormsFillingInput } from "./types" // Mock the entire @svenvw/fdm-core module @@ -29,7 +29,7 @@ import { isOrganicCertificationValid, } from "@svenvw/fdm-core" -describe("collectInputForFertilizerApplicationFilling", () => { +describe("collectNL2025InputForFertilizerApplicationFilling", () => { const mockFdm = {} as FdmType const mockPrincipalId = "principal123" const mockFieldId = "field456" @@ -81,7 +81,7 @@ describe("collectInputForFertilizerApplicationFilling", () => { { p_id_catalogue: "fert1", p_n_rt: 5, p_type_rvo: "115" }, ] - const result = await collectInputForFertilizerApplicationFilling( + const result = await collectNL2025InputForFertilizerApplicationFilling( mockFdm, mockPrincipalId, mockFieldId, @@ -146,7 +146,7 @@ describe("collectInputForFertilizerApplicationFilling", () => { vi.mocked(getField).mockResolvedValue(null) // Simulate field not found await expect( - collectInputForFertilizerApplicationFilling( + collectNL2025InputForFertilizerApplicationFilling( mockFdm, mockPrincipalId, mockFieldId, diff --git a/fdm-calculator/src/norms/nl/2025/filling/input.ts b/fdm-calculator/src/norms/nl/2025/filling/input.ts index 6d7113468..7dfce9d93 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/input.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/input.ts @@ -23,7 +23,7 @@ import type { NL2025NormsFillingInput } from "./types" * grazing intention status, the phosphate usage norm, and the field's centroid. * @throws {Error} Throws an error if the specified field cannot be found. */ -export async function collectInputForFertilizerApplicationFilling( +export async function collectNL2025InputForFertilizerApplicationFilling( fdm: FdmType, principal_id: PrincipalId, b_id: string, diff --git a/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts index 8ef3310cd..7c3c8f57d 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts @@ -7,9 +7,9 @@ import { table9 } from "./table-9" import { table11Mestcodes } from "./table-11-mestcodes" import type { NL2025NormsFillingInput, - NormFilling, WorkingCoefficientDetails, } from "./types" +import type { NormFilling } from "norms/nl/types" /** * Calculates the nitrogen utilization norm filling for a set of fertilizer applications. diff --git a/fdm-calculator/src/norms/nl/2025/filling/types.d.ts b/fdm-calculator/src/norms/nl/2025/filling/types.d.ts index d8e783b8e..cec4f14dc 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/types.d.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/types.d.ts @@ -7,15 +7,6 @@ import type { } from "@svenvw/fdm-core" import type { RegionKey } from "../value/types" -export type NormFilling = { - normFilling: number - applicationFilling: { - p_app_id: string - normFilling: number - normFillingDetails?: string - }[] -} - export type Table11Mestcodes = { p_type_rvo: schema.fertilizersCatalogueTypeSelect["p_type_rvo"] p_type_nitratesdirective: boolean diff --git a/fdm-calculator/src/norms/nl/types.ts b/fdm-calculator/src/norms/nl/types.ts index 5e5eb4cba..2a2db7387 100644 --- a/fdm-calculator/src/norms/nl/types.ts +++ b/fdm-calculator/src/norms/nl/types.ts @@ -54,3 +54,12 @@ export interface GebruiksnormResult { */ normSource: string } + +export type NormFilling = { + normFilling: number + applicationFilling: { + p_app_id: string + normFilling: number + normFillingDetails?: string + }[] +} From 99a8797fcfe7ca66513c1bcf3d684b2aa470f0f2 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:30:00 +0100 Subject: [PATCH 05/20] feat: Add calculation support of Dutch norms for fertilizer applications for 2026 --- .changeset/silly-signs-ring.md | 5 + fdm-calculator/src/norms/index.test.ts | 57 +- fdm-calculator/src/norms/index.ts | 46 +- .../dierlijke-mest-gebruiksnorm.test.ts | 206 ++ .../filling/dierlijke-mest-gebruiksnorm.ts | 116 + .../2026/filling/fosfaatgebruiksnorm.test.ts | 465 +++ .../nl/2026/filling/fosfaatgebruiksnorm.ts | 304 ++ .../src/norms/nl/2026/filling/input.test.ts | 167 + .../src/norms/nl/2026/filling/input.ts | 98 + .../2026/filling/stikstofgebruiksnorm.test.ts | 776 +++++ .../nl/2026/filling/stikstofgebruiksnorm.ts | 260 ++ .../nl/2026/filling/table-11-mestcodes.ts | 336 ++ .../src/norms/nl/2026/filling/table-9.ts | 178 ++ .../src/norms/nl/2026/filling/types.d.ts | 46 + .../value/dierlijke-mest-gebruiksnorm.test.ts | 10 + .../2026/value/dierlijke-mest-gebruiksnorm.ts | 44 + .../nl/2026/value/fosfaatgebruiksnorm-data.ts | 9 + .../nl/2026/value/fosfaatgebruiksnorm.test.ts | 43 + .../nl/2026/value/fosfaatgebruiksnorm.ts | 175 ++ .../src/norms/nl/2026/value/input.test.ts | 98 + .../src/norms/nl/2026/value/input.ts | 84 + .../2026/value/stikstofgebruiksnorm-data.ts | 2739 +++++++++++++++++ .../2026/value/stikstofgebruiksnorm.test.ts | 558 ++++ .../nl/2026/value/stikstofgebruiksnorm.ts | 569 ++++ .../src/norms/nl/2026/value/types.d.ts | 112 + 25 files changed, 7492 insertions(+), 9 deletions(-) create mode 100644 .changeset/silly-signs-ring.md create mode 100644 fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/input.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/input.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/table-11-mestcodes.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/table-9.ts create mode 100644 fdm-calculator/src/norms/nl/2026/filling/types.d.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/input.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/input.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm-data.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts create mode 100644 fdm-calculator/src/norms/nl/2026/value/types.d.ts diff --git a/.changeset/silly-signs-ring.md b/.changeset/silly-signs-ring.md new file mode 100644 index 000000000..390476588 --- /dev/null +++ b/.changeset/silly-signs-ring.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": minor +--- + +Add calculation support of Dutch norms for fertilizer applications for 2026 diff --git a/fdm-calculator/src/norms/index.test.ts b/fdm-calculator/src/norms/index.test.ts index a5374797e..236ddcafc 100644 --- a/fdm-calculator/src/norms/index.test.ts +++ b/fdm-calculator/src/norms/index.test.ts @@ -9,12 +9,20 @@ import { } from "./index" import { getNL2025FertilizerApplicationFillingForDierlijkeMestGebruiksNorm } from "./nl/2025/filling/dierlijke-mest-gebruiksnorm" import { getNL2025FertilizerApplicationFillingForFosfaatGebruiksNorm } from "./nl/2025/filling/fosfaatgebruiksnorm" -import { collectInputForFertilizerApplicationFilling } from "./nl/2025/filling/input" +import { collectNL2025InputForFertilizerApplicationFilling } from "./nl/2025/filling/input" import { getNL2025FertilizerApplicationFillingForStikstofGebruiksNorm } from "./nl/2025/filling/stikstofgebruiksnorm" import { getNL2025DierlijkeMestGebruiksNorm } from "./nl/2025/value/dierlijke-mest-gebruiksnorm" import { getNL2025FosfaatGebruiksNorm } from "./nl/2025/value/fosfaatgebruiksnorm" import { collectNL2025InputForNorms } from "./nl/2025/value/input" import { getNL2025StikstofGebruiksNorm } from "./nl/2025/value/stikstofgebruiksnorm" +import { collectNL2026InputForNorms } from "./nl/2026/value/input" +import { getNL2026StikstofGebruiksNorm } from "./nl/2026/value/stikstofgebruiksnorm" +import { getNL2026DierlijkeMestGebruiksNorm } from "./nl/2026/value/dierlijke-mest-gebruiksnorm" +import { getNL2026FosfaatGebruiksNorm } from "./nl/2026/value/fosfaatgebruiksnorm" +import { getNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm } from "./nl/2026/filling/fosfaatgebruiksnorm" +import { getNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm } from "./nl/2026/filling/dierlijke-mest-gebruiksnorm" +import { getNL2026FertilizerApplicationFillingForStikstofGebruiksNorm } from "./nl/2026/filling/stikstofgebruiksnorm" +import { collectNL2026InputForFertilizerApplicationFilling } from "./nl/2026/filling/input" describe("createFunctionsForNorms", () => { it("should return the correct functions for NL region and year 2025", () => { @@ -34,13 +42,32 @@ describe("createFunctionsForNorms", () => { ) }) + it("should return the correct functions for NL region and year 2026", () => { + const functions = createFunctionsForNorms("NL", "2026") + expect(functions.collectInputForNorms).toBe(collectNL2026InputForNorms) + expect(functions.calculateNormForNitrogen).toBe( + getNL2026StikstofGebruiksNorm, + ) + expect(functions.calculateNormForManure).toBe( + getNL2026DierlijkeMestGebruiksNorm, + ) + expect(functions.calculateNormForPhosphate).toBe( + getNL2026FosfaatGebruiksNorm, + ) + expect(functions.aggregateNormsToFarmLevel).toBe( + aggregateNormsToFarmLevel, + ) + }) + it("should throw an error for an unsupported year", () => { - expect(() => createFunctionsForNorms("NL", " 2024")).toThrow( + //@ts-expect-error + expect(() => createFunctionsForNorms("NL", "2024")).toThrow( "Year not supported", ) }) it("should throw an error for an unsupported region", () => { + //@ts-expect-error expect(() => createFunctionsForNorms("BE", "2025")).toThrow( "Region not supported", ) @@ -54,7 +81,7 @@ describe("createFunctionsForFertilizerApplicationFilling", () => { "2025", ) expect(functions.collectInputForFertilizerApplicationFilling).toBe( - collectInputForFertilizerApplicationFilling, + collectNL2025InputForFertilizerApplicationFilling, ) expect(functions.calculateFertilizerApplicationFillingForNitrogen).toBe( getNL2025FertilizerApplicationFillingForStikstofGebruiksNorm, @@ -70,14 +97,38 @@ describe("createFunctionsForFertilizerApplicationFilling", () => { ) }) + it("should return the correct functions for NL region and year 2025", () => { + const functions = createFunctionsForFertilizerApplicationFilling( + "NL", + "2026", + ) + expect(functions.collectInputForFertilizerApplicationFilling).toBe( + collectNL2026InputForFertilizerApplicationFilling, + ) + expect(functions.calculateFertilizerApplicationFillingForNitrogen).toBe( + getNL2026FertilizerApplicationFillingForStikstofGebruiksNorm, + ) + expect(functions.calculateFertilizerApplicationFillingForManure).toBe( + getNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm, + ) + expect( + functions.calculateFertilizerApplicationFillingForPhosphate, + ).toBe(getNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm) + expect(functions.aggregateNormFillingsToFarmLevel).toBe( + aggregateNormFillingsToFarmLevel, + ) + }) + it("should throw an error for an unsupported year", () => { expect(() => + //@ts-expect-error createFunctionsForFertilizerApplicationFilling("NL", "2024"), ).toThrow("Year not supported") }) it("should throw an error for an unsupported region", () => { expect(() => + //@ts-expect-error createFunctionsForFertilizerApplicationFilling("BE", "2025"), ).toThrow("Region not supported") }) diff --git a/fdm-calculator/src/norms/index.ts b/fdm-calculator/src/norms/index.ts index 76dff636a..22d15921f 100644 --- a/fdm-calculator/src/norms/index.ts +++ b/fdm-calculator/src/norms/index.ts @@ -4,15 +4,26 @@ import { } from "./farm" import { getNL2025FertilizerApplicationFillingForDierlijkeMestGebruiksNorm } from "./nl/2025/filling/dierlijke-mest-gebruiksnorm" import { getNL2025FertilizerApplicationFillingForFosfaatGebruiksNorm } from "./nl/2025/filling/fosfaatgebruiksnorm" -import { collectInputForFertilizerApplicationFilling } from "./nl/2025/filling/input" +import { collectNL2025InputForFertilizerApplicationFilling } from "./nl/2025/filling/input" import { getNL2025FertilizerApplicationFillingForStikstofGebruiksNorm } from "./nl/2025/filling/stikstofgebruiksnorm" -import type { NormFilling } from "./nl/2025/filling/types" import { getNL2025DierlijkeMestGebruiksNorm } from "./nl/2025/value/dierlijke-mest-gebruiksnorm" import { getNL2025FosfaatGebruiksNorm } from "./nl/2025/value/fosfaatgebruiksnorm" import { collectNL2025InputForNorms } from "./nl/2025/value/input" import { getNL2025StikstofGebruiksNorm } from "./nl/2025/value/stikstofgebruiksnorm" +import { getNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm } from "./nl/2026/filling/dierlijke-mest-gebruiksnorm" +import { getNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm } from "./nl/2026/filling/fosfaatgebruiksnorm" +import { collectNL2026InputForFertilizerApplicationFilling } from "./nl/2026/filling/input" +import { getNL2026FertilizerApplicationFillingForStikstofGebruiksNorm } from "./nl/2026/filling/stikstofgebruiksnorm" +import { getNL2026DierlijkeMestGebruiksNorm } from "./nl/2026/value/dierlijke-mest-gebruiksnorm" +import { getNL2026FosfaatGebruiksNorm } from "./nl/2026/value/fosfaatgebruiksnorm" +import { collectNL2026InputForNorms } from "./nl/2026/value/input" +import { getNL2026StikstofGebruiksNorm } from "./nl/2026/value/stikstofgebruiksnorm" +import type { NormFilling } from "./nl/types" -export function createFunctionsForNorms(b_region: "NL", year: "2025") { +type Years = "2025" | "2026" +type Regions = "NL" + +export function createFunctionsForNorms(b_region: Regions, year: Years) { if (b_region === "NL") { if (year === "2025") { return { @@ -23,20 +34,29 @@ export function createFunctionsForNorms(b_region: "NL", year: "2025") { aggregateNormsToFarmLevel: aggregateNormsToFarmLevel, } } + if (year === "2026") { + return { + collectInputForNorms: collectNL2026InputForNorms, + calculateNormForNitrogen: getNL2026StikstofGebruiksNorm, + calculateNormForManure: getNL2026DierlijkeMestGebruiksNorm, + calculateNormForPhosphate: getNL2026FosfaatGebruiksNorm, + aggregateNormsToFarmLevel: aggregateNormsToFarmLevel, + } + } throw new Error("Year not supported") } throw new Error("Region not supported") } export function createFunctionsForFertilizerApplicationFilling( - b_region: "NL", - year: "2025", + b_region: Regions, + year: Years, ) { if (b_region === "NL") { if (year === "2025") { return { collectInputForFertilizerApplicationFilling: - collectInputForFertilizerApplicationFilling, + collectNL2025InputForFertilizerApplicationFilling, calculateFertilizerApplicationFillingForNitrogen: getNL2025FertilizerApplicationFillingForStikstofGebruiksNorm, calculateFertilizerApplicationFillingForManure: @@ -47,6 +67,20 @@ export function createFunctionsForFertilizerApplicationFilling( aggregateNormFillingsToFarmLevel, } } + if (year === "2026") { + return { + collectInputForFertilizerApplicationFilling: + collectNL2026InputForFertilizerApplicationFilling, + calculateFertilizerApplicationFillingForNitrogen: + getNL2026FertilizerApplicationFillingForStikstofGebruiksNorm, + calculateFertilizerApplicationFillingForManure: + getNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm, + calculateFertilizerApplicationFillingForPhosphate: + getNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm, + aggregateNormFillingsToFarmLevel: + aggregateNormFillingsToFarmLevel, + } + } throw new Error("Year not supported") } throw new Error("Region not supported") diff --git a/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.test.ts new file mode 100644 index 000000000..d971ed54a --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.test.ts @@ -0,0 +1,206 @@ +import type { Fertilizer, FertilizerApplication } from "@svenvw/fdm-core" +import { describe, expect, it } from "vitest" +import { calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm } from "./dierlijke-mest-gebruiksnorm" +import type { NL2026NormsFillingInput } from "./types" + +describe("calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm", () => { + const mockFertilizers: Fertilizer[] = [ + { + p_id_catalogue: "1", + p_type_rvo: "11", + p_n_rt: 0.5, + }, + { + p_id_catalogue: "2", + p_type_rvo: "12", + }, + { + p_id_catalogue: "3", + p_type_rvo: "200", // Not in table11Mestcodes + }, + { + p_id_catalogue: "4", + // No p_type_rvo + }, + { + p_id_catalogue: "5", + p_type_rvo: "115", // Not relevant for nitrates directive + }, + ] + + const mockApplications: FertilizerApplication[] = [ + { + p_app_id: "app1", + p_id_catalogue: "1", + p_app_amount: 10000, + }, + { + p_app_id: "app2", + p_id_catalogue: "2", + p_app_amount: 20000, + }, + ] + + it("should calculate the norm filling for a single application", () => { + const result = + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [mockApplications[0]], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBe(5) + expect(result.applicationFilling).toEqual([ + { + p_app_id: "app1", + normFilling: 5, + }, + ]) + }) + + it("should calculate the norm filling for multiple applications", () => { + const result = + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: mockApplications, + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBe(85) // 5 + 80 + expect(result.applicationFilling).toEqual([ + { + p_app_id: "app1", + normFilling: 5, + }, + { + p_app_id: "app2", + normFilling: 80, + }, + ]) + }) + + it("should return zero filling for fertilizers not relevant to the nitrates directive", () => { + const result = + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [ + { + p_app_id: "app3", + p_id_catalogue: "5", + p_app_amount: 10, + }, + ], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBe(0) + expect(result.applicationFilling).toEqual([ + { + p_app_id: "app3", + normFilling: 0, + }, + ]) + }) + + it("should throw an error if a fertilizer is not found", () => { + expect(() => + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [ + { + p_app_id: "app4", + p_id_catalogue: "999", + p_app_amount: 10, + }, + ], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ), + ).toThrow("Fertilizer 999 not found for application app4") + }) + + it("should throw an error if a fertilizer has no p_type_rvo", () => { + expect(() => + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [ + { + p_app_id: "app5", + p_id_catalogue: "4", + p_app_amount: 10, + }, + ], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ), + ).toThrow("Fertilizer 4 has no p_type_rvo") + }) + + it("should throw an error if a fertilizer has an unknown p_type_rvo", () => { + expect(() => + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [ + { + p_app_id: "app6", + p_id_catalogue: "3", + p_app_amount: 10, + }, + ], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ), + ).toThrow("Fertilizer 3 has unknown p_type_rvo 200") + }) + + it("should return zero filling when no applications are provided", () => { + const result = + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + { + applications: [], + fertilizers: mockFertilizers, + cultivations: [], + has_organic_certification: false, + has_grazing_intention: false, + fosfaatgebruiksnorm: 0, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBe(0) + expect(result.applicationFilling).toEqual([]) + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts new file mode 100644 index 000000000..efc3cb8bc --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts @@ -0,0 +1,116 @@ +import { withCalculationCache } from "@svenvw/fdm-core" +import Decimal from "decimal.js" +import pkg from "../../../../package" +import { table11Mestcodes } from "./table-11-mestcodes" +import type { NL2026NormsFillingInput } from "./types" +import type { NormFilling } from "norms/nl/types" + +/** + * Calculates the nitrogen usage from animal manure for a list of fertilizer applications. + * + * This function determines the contribution of each fertilizer application to the nitrogen norm + * based on the type of manure used. It uses predefined values from `table11Mestcodes` to identify + * which fertilizers are considered animal manure and to find their nitrogen content. + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {NormFilling} An object containing the total nitrogen usage (`normFilling`) and a detailed breakdown per application (`applicationFilling`). + * @throws {Error} Throws an error if a fertilizer or its RVO type is not found, ensuring data integrity. + */ +export function calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm( + input: NL2026NormsFillingInput, +): NormFilling { + const { applications, fertilizers } = input + + // Create maps for efficient lookups of fertilizers and RVO types. + // This avoids iterating over the arrays repeatedly in a loop. + const fertilizersMap = new Map( + fertilizers.map((fertilizer) => [ + fertilizer.p_id_catalogue, + fertilizer, + ]), + ) + const rvoTypeMap = new Map( + table11Mestcodes.map((rvoType) => [rvoType.p_type_rvo, rvoType]), + ) + + // Use reduce to iterate over applications and calculate the total norm filling. + const { totalFilling, applicationFilling } = applications.reduce( + (acc, application) => { + // Retrieve the fertilizer for the current application. + const fertilizer = fertilizersMap.get(application.p_id_catalogue) + if (!fertilizer) { + throw new Error( + `Fertilizer ${application.p_id_catalogue} not found for application ${application.p_app_id}`, + ) + } + + // Get the RVO type of the fertilizer. + const p_type_rvo = fertilizer.p_type_rvo + if (!p_type_rvo) { + throw new Error( + `Fertilizer ${application.p_id_catalogue} has no p_type_rvo`, + ) + } + + // Find the properties associated with the RVO type. + const rvoTypeProperties = rvoTypeMap.get(p_type_rvo) + if (!rvoTypeProperties) { + throw new Error( + `Fertilizer ${application.p_id_catalogue} has unknown p_type_rvo ${p_type_rvo}`, + ) + } + + let normFilling = new Decimal(0) + // Check if the fertilizer is relevant for the nitrates directive. + if (rvoTypeProperties.p_type_nitratesdirective) { + const amount = new Decimal(application.p_app_amount) + // Determine the nitrogen content, using specific values if available, otherwise fallback to default. + const p_n_rt = new Decimal( + fertilizer.p_n_rt ?? rvoTypeProperties.p_n_rt ?? 0, + ) + // Calculate the norm filling for this application. + normFilling = amount.times(p_n_rt).dividedBy(1000) + } + + // Add the filling of the current application to the total. + acc.totalFilling = acc.totalFilling.plus(normFilling) + // Add the detailed filling for this application to the list. + acc.applicationFilling.push({ + p_app_id: application.p_app_id, + normFilling: normFilling.toNumber(), + }) + + return acc + }, + // Initial value for the accumulator. + { + totalFilling: new Decimal(0), + applicationFilling: [] as { + p_app_id: string + normFilling: number + }[], + }, + ) + + // Return the total norm filling and the breakdown per application. + return { + normFilling: totalFilling.toNumber(), + applicationFilling, + } +} + +/** + * Memoized version of {@link calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {NormFilling} An object containing the total nitrogen usage (`normFilling`) and a detailed breakdown per application (`applicationFilling`). + */ +export const getNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm = + withCalculationCache( + calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm, + "calculateNL2026FertilizerApplicationFillingForDierlijkeMestGebruiksNorm", + pkg.calculatorVersion, + ) diff --git a/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.test.ts new file mode 100644 index 000000000..426efeced --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.test.ts @@ -0,0 +1,465 @@ +import type { Fertilizer, FertilizerApplication } from "@svenvw/fdm-core" +import { describe, expect, it } from "vitest" +import { calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm } from "./fosfaatgebruiksnorm" +import type { NL2026NormsFillingInput } from "./types" + +// Mock data for fertilizers +const mockFertilizers: Fertilizer[] = [ + { + p_id_catalogue: "f1", + p_name_nl: "Drijfmest van rundvee", + p_type: "manure", + p_type_rvo: "14", + p_p_rt: 1.5, // Example 1: 30kg P / 20 = 1.5 + }, + { + p_id_catalogue: "f2", + p_name_nl: "Strorijke vaste mest van rundvee", + p_type: "manure", + p_type_rvo: "10", + p_p_rt: 0.75, // Example 1: 30kg P / 40 = 0.75 + }, + { + p_id_catalogue: "f3", + p_name_nl: "Groencompost", + p_type: "compost", + p_type_rvo: "111", + p_p_rt: 10, // Example 2: 1200 kg P / 120 = 10 + }, + { + p_id_catalogue: "f4", + p_name_nl: "Drijfmest", + p_type: "manure", + p_type_rvo: "14", + p_p_rt: 1.0, // Example 3: 60kg P / 60 = 1.0 + }, + { + p_id_catalogue: "f5", + p_name_nl: "GFT-compost", + p_type: "compost", + p_type_rvo: "112", + p_p_rt: 0.25, // Example 4: 10kg P / 40 = 0.25 + }, + { + p_id_catalogue: "f6", + p_name_nl: "Strorijke vaste mest van paarden", + p_type: "manure", + p_type_rvo: "25", + p_p_rt: 0.75, // Example 4: 10kg P / 13.3 = 0.75 + }, + { + p_id_catalogue: "f7", + p_name_nl: "Vaste mest varkens (biologisch)", + p_type: "manure", + p_type_rvo: "40", + p_p_rt: 0.75, + }, + { + p_id_catalogue: "f8", + p_name_nl: "Kunstmest", + p_type: "mineral", + p_type_rvo: "115", + p_p_rt: 50, + }, + { + p_id_catalogue: "f9", + p_name_nl: "Fertilizer with no p_p_rt", + p_type: "manure", + p_type_rvo: "108", // A type_rvo that has no p_p_rt in table11Mestcodes + p_p_rt: null, + }, + { + p_id_catalogue: "f10", + p_name_nl: "Fertilizer with p_p_rt in table11Mestcodes", + p_type: "manure", + p_type_rvo: "107", // A type_rvo that has p_p_rt in table11Mestcodes (3.1) + p_p_rt: null, + }, +] + +describe("calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm", () => { + // Helper to create a FertilizerApplication + const createApplication = ( + fertilizerId: string, + amount: number, + appId: string, + ): FertilizerApplication => ({ + p_app_id: appId, + p_id_catalogue: fertilizerId, + p_name_nl: `Application for ${fertilizerId}`, + p_app_amount: amount, + p_app_method: "spreading", + p_app_date: new Date(), + }) + + describe("5 examples from Staatscourant 2023-5152", () => { + // Example 1: Strorijke vaste mest van rundvee + it("should correctly calculate for Example 1 (Strorijke vaste mest)", () => { + const applications = [ + createApplication("f1", 20000, "app1"), // Drijfmest: 30kg P (20000 kg fertilizer * 1.5 P_RT / 1000) + createApplication("f2", 53333.33, "app2"), // Strorijke vaste mest: 40kg P (53333.33 kg fertilizer * 0.75 P_RT / 1000) + ] + const fosfaatgebruiksnorm = 60 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBeCloseTo(60) + expect(result.applicationFilling).toHaveLength(2) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(30) // Standard + expect(result.applicationFilling[1].normFilling).toBeCloseTo(30) // OS-rich (40 * 0.75) + }) + + // Example 2a: Groencompost + it("should correctly calculate for Example 2 (Groencompost)", () => { + const applications = [ + createApplication("f3", 12000, "app1"), // Groencompost: 120kg P (12000 kg fertilizer * 10 P_RT / 1000) + createApplication("f3", 9000, "app2"), // Groencompost: 90kg P (9000 kg fertilizer * 10 P_RT / 1000) + ] + const fosfaatgebruiksnorm = 120 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + // console.log(result) // Keep for debugging if needed + + expect(result.normFilling).toBeCloseTo(120) + expect(result.applicationFilling).toHaveLength(2) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(30) // OS-rich (120 * 0.25) + expect(result.applicationFilling[1].normFilling).toBeCloseTo(90) // OS-rich (90 * 1.00) + }) + + // Example 2b: Groencompost (This test case seems to be a duplicate or misinterpretation of Example 2 in the document. The document's Example 2 has only one OS-rich application of 120kg P, and then an additional 90kg P is mentioned as "extra space". The test here uses two applications of f3, which is fine, but the expected output for the first application is 120, which is the total norm. This needs to be adjusted to reflect the actual contribution of the first application.) + // Based on the document's Example 2, the total actual P from Groencompost is 120kg + 90kg = 210kg. + // 120kg is discounted at 25% -> 30kg. + // Remaining 90kg is counted at 100% -> 90kg. + // Total norm filling = 30 + 90 = 120kg. + // The test should reflect this breakdown. + it("should correctly calculate for Example 2 (Groencompost) - detailed breakdown", () => { + const applications = [ + createApplication("f3", 12000, "app1"), // Groencompost: 120kg P + createApplication("f3", 9000, "app2"), // Groencompost: 90kg P + ] + const fosfaatgebruiksnorm = 120 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + // console.log(result) // Keep for debugging if needed + + expect(result.normFilling).toBeCloseTo(120) + expect(result.applicationFilling).toHaveLength(2) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(30) // 120kg * 0.25 + expect(result.applicationFilling[1].normFilling).toBeCloseTo(90) // 90kg * 1.00 (beyond discounted limit) + }) + + // Example 3: Groencompost with Drijfmest + it("should correctly calculate for Example 3 (Groencompost with Drijfmest)", () => { + const applications = [ + createApplication("f4", 60000, "app1"), // Drijfmest: 60kg P + createApplication("f3", 10500, "app2"), // Groencompost: 105kg P + ] + const fosfaatgebruiksnorm = 105 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBeCloseTo(86.25) // 60 + (105 * 0.25) + expect(result.applicationFilling).toHaveLength(2) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(60) // Standard + expect(result.applicationFilling[1].normFilling).toBeCloseTo(26.25) // OS-rich (105 * 0.25) + }) + + // Example 4: GFT-compost and Strorijke mest van paarden with Drijfmest + it("should correctly calculate for Example 4 (Mixed OS-rich with Drijfmest)", () => { + const applications = [ + createApplication("f4", 40000, "app1"), // Drijfmest: 40kg P + createApplication("f5", 160000, "app2"), // GFT-compost: 40kg P + createApplication("f6", 17733.33, "app3"), // Strorijke mest paarden: 13.3kg P + ] + const fosfaatgebruiksnorm = 60 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBeCloseTo(59.975) // 40 + (40 * 0.25) + (13.3 * 0.75) = 40 + 10 + 9.975 = 59.975 + expect(result.applicationFilling).toHaveLength(3) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(40) // Standard + expect(result.applicationFilling[1].normFilling).toBeCloseTo(10) // OS-rich (40 * 0.25) + expect(result.applicationFilling[2].normFilling).toBeCloseTo(9.975) // OS-rich (13.3 * 0.75) + }) + + // Example 5: Groencompost with Drijfmest, not filling the norm completely + it("should correctly calculate for Example 5 (Groencompost, Drijfmest, partial filling)", () => { + const applications = [ + createApplication("f3", 12000, "app1"), // Groencompost: 120kg P + createApplication("f4", 40000, "app2"), // Drijfmest: 40kg P + ] + const fosfaatgebruiksnorm = 120 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + { + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput, + ) + + expect(result.normFilling).toBeCloseTo(70) // (120 * 0.25) + 40 = 30 + 40 + expect(result.applicationFilling).toHaveLength(2) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(30) // OS-rich (120 * 0.25) + expect(result.applicationFilling[1].normFilling).toBeCloseTo(40) // Standard + }) + }) + + // Additional Test Cases for coverage + + it("should return 0 for no applications", () => { + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications: [], + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm: 100, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBe(0) + expect(result.applicationFilling).toHaveLength(0) + }) + + it("should handle applications with only standard fertilizers", () => { + const applications = [ + createApplication("f1", 10000, "app1"), // Drijfmest: 10 * 1.5 = 15kg P + createApplication("f4", 20000, "app2"), // Drijfmest: 20 * 1.0 = 20kg P + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBeCloseTo(35) + expect(result.applicationFilling).toHaveLength(2) + }) + + it("should count OS-rich fertilizers at 100% if total applied is below 20kg threshold", () => { + const applications = [ + createApplication("f3", 1000, "app1"), // Groencompost: 10kg P (below 20kg threshold) + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBeCloseTo(10) // 100% counted + expect(result.applicationFilling[0].normFillingDetails).toContain( + "OS-rijke meststof, minimumdrempel niet gehaald, 100% geteld.", + ) + }) + + it("should correctly apply 75% discount for organic pig manure on an organic farm", () => { + const applications = [ + createApplication("f7", 40000, "app1"), // Vaste mest varkens (biologisch): 30kg P (75% discounted) + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: true, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBeCloseTo(22.5) // 30 * 0.75 = 22.5 + expect(result.applicationFilling[0].normFillingDetails).toContain( + "75% korting", + ) + }) + + it("should count organic pig manure at 100% on a non-organic farm", () => { + const applications = [ + createApplication("f7", 40000, "app1"), // Vaste mest varkens (biologisch): 30kg P (100% counted) + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBeCloseTo(30) // 100% counted + expect(result.applicationFilling[0].normFillingDetails).toBeUndefined() + }) + + it("should use p_p_rt from table11Mestcodes if not on fertilizer object", () => { + const applications = [ + createApplication("f10", 10000, "app1"), // Fertilizer with p_p_rt in table11Mestcodes (3.1) + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBeCloseTo(10 * 3.1) // 31 + }) + + it("should use 0 if p_p_rt is not on fertilizer and not in table11Mestcodes", () => { + const applications = [ + createApplication("f9", 10000, "app1"), // Fertilizer with no p_p_rt + ] + const fosfaatgebruiksnorm = 100 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + expect(result.normFilling).toBe(0) + }) + + it("should correctly handle OS-rich applications exceeding the norm", () => { + const applications = [ + createApplication("f4", 10000, "app1"), // Drijfmest: 10 * 1.0 = 10kg P + createApplication("f3", 20000, "app2"), // Groencompost: 200 * 0.25 = 50kg P (25% discounted) + ] + const fosfaatgebruiksnorm = 30 // Small norm + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + // Standard: 10kg. Remaining norm: 20kg. + // Groencompost: 200kg actual P. Discounted: 50kg. + // To fill 20kg norm with 25% discounted: need 20 / 0.25 = 80kg actual P. + // So 80kg of Groencompost is discounted to 20kg. + // Remaining Groencompost: 200 - 80 = 120kg. This counts 100%. + // Standard: 10kg. Remaining norm: 20kg. + // Groencompost: 200kg actual P. Discounted: 50kg. + // To fill 20kg norm with 25% discounted: need 20 / 0.25 = 80kg actual P. + // So 80kg of Groencompost is discounted to 20kg. + // Remaining Groencompost: 200 - 80 = 120kg. This counts 100%. + // Total: 10 (standard) + 177.5 (Groencompost) = 187.5. + expect(result.normFilling).toBeCloseTo(187.5) + expect(result.applicationFilling[1].normFilling).toBeCloseTo(177.5) // 7.5 (discounted) + 170 (100% counted) + expect(result.applicationFilling[1].normFillingDetails).toContain( + "OS-rijke meststof (25% korting) draagt 7.50kg bij aan de norm. Plus 170.00kg (100% geteld) boven de kortingslimiet.", + ) + }) + + it("should handle multiple OS-rich applications filling and exceeding the norm", () => { + const applications = [ + createApplication("f4", 10000, "app1"), // Drijfmest: 10kg P + createApplication("f3", 4000, "app2"), // Groencompost: 40kg actual P (25% discounted) + createApplication("f2", 20000, "app3"), // Strorijke vaste mest: 20kg actual P (75% discounted) + ] + const fosfaatgebruiksnorm = 30 + const result = + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm({ + applications, + fertilizers: mockFertilizers, + has_organic_certification: false, + fosfaatgebruiksnorm, + cultivations: [], + has_grazing_intention: false, + b_centroid: [0, 0], + } as NL2026NormsFillingInput) + // Standard: 10kg. + // App2 (Groencompost): 40kg actual P. + // Eligible for discount: min(40, 30 - 0) = 30kg. Discounted: 30 * 0.25 = 7.5kg. + // Remaining actual P: 40 - 30 = 10kg. This counts 100%. + // App2 contribution: 7.5 + 10 = 17.5kg. + // App3 (Strorijke mest): 20kg actual P. + // Eligible for discount: min(20, 30 - 30) = 0kg. Discounted: 0kg. + // Remaining actual P: 20 - 0 = 20kg. This counts 100%. + // App3 contribution: 0 + 20 = 20kg. + // Total: 10 (app1) + 17.5 (app2) + 15 (app3) = 42.5kg. + expect(result.normFilling).toBeCloseTo(42.5) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(10) + expect(result.applicationFilling[1].normFilling).toBeCloseTo(17.5) + expect(result.applicationFilling[2].normFilling).toBeCloseTo(15) + expect(result.applicationFilling[2].normFillingDetails).toContain( + "OS-rijke meststof, geen korting toegepast. Plus 15.00kg (100% geteld) boven de kortingslimiet.", + ) + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts new file mode 100644 index 000000000..774aeee6f --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts @@ -0,0 +1,304 @@ +import { + type Fertilizer, + type FertilizerApplication, + withCalculationCache, +} from "@svenvw/fdm-core" +import Decimal from "decimal.js" +import pkg from "../../../../package" +import { table11Mestcodes } from "./table-11-mestcodes" +import type { NL2026NormsFillingInput} from "./types" +import type { NormFilling } from "norms/nl/types" + +const rvoMestcodesOrganicRich25Percent = ["111", "112"] // Compost, Zeer schone compost +const rvoMestcodesOrganicRich75Percent = ["110", "10", "61", "25", "56"] // Champost, Rundvee - Vaste mest, Geiten - Vaste mest, Paarden - Vaste mest, Schapen - Mest, alle systemen +const rvoMestcodesOrganicRich75PercentOrganic = ["40"] // Varkens - Vaste mest (for organic certification) + +/** + * Calculates the norm filling for phosphate application, taking into account the + * "Stimuleren organische stofrijke meststoffen" (Stimulating organic-rich fertilizers) regulation. + * + * This regulation, detailed in Staatscourant 2023, nr. 5152, aims to encourage the use of + * organic-rich fertilizers by applying a differentiated percentage to their phosphate content + * when calculating against the phosphate usage norm. + * + * Key aspects of the regulation implemented: + * 1. **Minimum Threshold (Condition 1):** A discount is only applied if at least 20 kg/ha of + * phosphate from organic-rich fertilizers is applied. + * 2. **Iterative Discounting:** The differentiated percentage (25% or 75% mee) is applied + * iteratively. The discount is only valid for the portion of organic-rich phosphate + * that, when summed with other discounted organic-rich phosphate, does not exceed + * the `fosfaatgebruiksnorm`. Any organic-rich phosphate applied beyond this limit + * is counted at 100% towards the norm. + * 3. **Prioritization:** To maximize the benefit for the farmer, fertilizers with a 25% + * contribution factor (e.g., compost) are prioritized for the discount over those + * with a 75% contribution factor (e.g., strorijke vaste mest). + * This has been acknowledged by RVO to be possible in personal communication with Sven. + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {NormFilling} An object containing the total norm filling and a breakdown per application. + */ +export function calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm( + input: NL2026NormsFillingInput, +): NormFilling { + const { + applications, + fertilizers, + has_organic_certification, + fosfaatgebruiksnorm, + } = input + + // Create maps for efficient lookups of fertilizers and RVO types. + // This avoids iterating over the arrays repeatedly in a loop. + const fertilizersMap = new Map( + fertilizers.map((fertilizer) => [ + fertilizer.p_id_catalogue, + fertilizer, + ]), + ) + + // Determines if at least 20 kg P2O5 / ha is applied with organic-rich fertilizers + const condition1 = + determineCondition1StimuleringOrganischeStofrijkeMeststoffen( + applications, + fertilizersMap, + has_organic_certification, + ) + + let totalFilling = new Decimal(0) + const normLimit = new Decimal(fosfaatgebruiksnorm) + let remainingDiscountablePhosphate = normLimit // This tracks the remaining P that can be discounted + + // Separate applications into standard and organic-rich + const standardApplications: { + application: FertilizerApplication + p_p_rt: Decimal + p_app_amount: Decimal + }[] = [] + const organicRichApplications: { + application: FertilizerApplication + p_p_rt: Decimal + p_app_amount: Decimal + p_type_rvo: string + discountFactor: Decimal + originalIndex: number + }[] = [] + + applications.forEach((application, index) => { + const p_app_amount = new Decimal(application.p_app_amount ?? 0) + const p_p_rt = new Decimal( + fertilizersMap.get(application.p_id_catalogue)?.p_p_rt ?? + table11Mestcodes.find( + (t) => + t.p_type_rvo === + fertilizersMap.get(application.p_id_catalogue) + ?.p_type_rvo, + )?.p_p_rt ?? + 0, + ) + const p_type_rvo = + fertilizersMap.get(application.p_id_catalogue)?.p_type_rvo ?? "" + + if ( + rvoMestcodesOrganicRich25Percent.includes(p_type_rvo) || + rvoMestcodesOrganicRich75Percent.includes(p_type_rvo) || + (rvoMestcodesOrganicRich75PercentOrganic.includes(p_type_rvo) && + has_organic_certification) + ) { + let discountFactor: Decimal + if (rvoMestcodesOrganicRich25Percent.includes(p_type_rvo)) { + discountFactor = new Decimal(0.25) + } else { + discountFactor = new Decimal(0.75) + } + organicRichApplications.push({ + application, + p_p_rt, + p_app_amount, + p_type_rvo, + discountFactor, + originalIndex: index, + }) + } else { + standardApplications.push({ application, p_p_rt, p_app_amount }) + } + }) + + // Sort organic-rich applications to prioritize 25% discount over 75% discount + organicRichApplications.sort((a, b) => + a.discountFactor.cmp(b.discountFactor), + ) + + // Initialize applicationsFilling with placeholders to maintain original order + const orderedApplicationsFilling: { + p_app_id: string + normFilling: number + normFillingDetails?: string + }[] = new Array(applications.length) + + // Process standard applications first + for (const { application, p_p_rt, p_app_amount } of standardApplications) { + const normFilling = p_app_amount.times(p_p_rt).dividedBy(1000) + totalFilling = totalFilling.plus(normFilling) + orderedApplicationsFilling[ + applications.findIndex( + (app) => app.p_app_id === application.p_app_id, + ) + ] = { + p_app_id: application.p_app_id, + normFilling: normFilling.toNumber(), + } + } + + // Process organic-rich applications with iterative discounting + if (condition1) { + for (const { + application, + p_p_rt, + p_app_amount, + discountFactor, + originalIndex, + } of organicRichApplications) { + const actualPhosphateApplied = p_app_amount + .times(p_p_rt) + .dividedBy(1000) + let currentApplicationFilling = new Decimal(0) + let normFillingDetails: string + + // Calculate how much of this application can be discounted + const phosphateToDiscount = Decimal.min( + actualPhosphateApplied, + remainingDiscountablePhosphate, + ) + + if (phosphateToDiscount.gt(0)) { + currentApplicationFilling = currentApplicationFilling.plus( + phosphateToDiscount.times(discountFactor), + ) + remainingDiscountablePhosphate = + remainingDiscountablePhosphate.minus(phosphateToDiscount) + normFillingDetails = `OS-rijke meststof (${discountFactor.times( + 100, + )}% korting) draagt ${phosphateToDiscount + .times(discountFactor) + .toFixed(2)}kg bij aan de norm.` + } else { + normFillingDetails = + "OS-rijke meststof, geen korting toegepast." + } + + // Add any remaining actual phosphate (beyond the discountable limit) at 100% + const phosphateBeyondDiscount = + actualPhosphateApplied.minus(phosphateToDiscount) + if (phosphateBeyondDiscount.gt(0)) { + currentApplicationFilling = currentApplicationFilling.plus( + phosphateBeyondDiscount, + ) + normFillingDetails += ` Plus ${phosphateBeyondDiscount.toFixed( + 2, + )}kg (100% geteld) boven de kortingslimiet.` + } + + totalFilling = totalFilling.plus(currentApplicationFilling) + orderedApplicationsFilling[originalIndex] = { + p_app_id: application.p_app_id, + normFilling: currentApplicationFilling.toNumber(), + normFillingDetails: normFillingDetails, + } + } + } else { + // If condition1 is not met, organic-rich fertilizers are counted at 100% + for (const { + application, + p_p_rt, + p_app_amount, + originalIndex, + } of organicRichApplications) { + const normFilling = p_app_amount.times(p_p_rt).dividedBy(1000) + totalFilling = totalFilling.plus(normFilling) + orderedApplicationsFilling[originalIndex] = { + p_app_id: application.p_app_id, + normFilling: normFilling.toNumber(), + normFillingDetails: + "OS-rijke meststof, minimumdrempel niet gehaald, 100% geteld.", + } + } + } + + // Return the total norm filling and the breakdown per application. + return { + normFilling: totalFilling.toNumber(), + applicationFilling: orderedApplicationsFilling, + } +} + +/** + * Determines if at least 20 kg P2O5 / ha is applied with organic-rich fertilizers. + * This is Condition 1 for the "Stimuleren organische stofrijke meststoffen" regulation. + * + * @param {FertilizerApplication[]} applications - An array of fertilizer applications. + * @param {Map} fertilizersMap - A map of fertilizers for efficient lookup. + * @param {boolean} has_organic_certification - Indicates if the farm has organic certification. + * @returns {boolean} True if the 20 kg/ha threshold is met, false otherwise. + */ +function determineCondition1StimuleringOrganischeStofrijkeMeststoffen( + applications: FertilizerApplication[], + fertilizersMap: Map, + has_organic_certification: boolean, +): boolean { + // Set the RVO mestcodes for organic-rich fertilizers + const rvoMestcodesOrganicRich = [ + ...rvoMestcodesOrganicRich25Percent, + ...rvoMestcodesOrganicRich75Percent, + ] + if (has_organic_certification) { + rvoMestcodesOrganicRich.push(...rvoMestcodesOrganicRich75PercentOrganic) + } + + // Sum the phosphate dose of organic-rich fertilizers + const totalPhosphateDoseOrganicDose = applications.reduce( + (acc, application) => { + const fertilizer = fertilizersMap.get(application.p_id_catalogue) + if (!fertilizer) { + return acc + } + + const p_p_rt = new Decimal( + fertilizer.p_p_rt ?? + table11Mestcodes.find( + (t) => t.p_type_rvo === fertilizer.p_type_rvo, + )?.p_p_rt ?? + 0, + ) + + if (p_p_rt.isZero()) { + return acc + } + + const p_app_amount = new Decimal(application.p_app_amount ?? 0) + const actualPhosphate = p_app_amount.times(p_p_rt).dividedBy(1000) + + if (rvoMestcodesOrganicRich.includes(fertilizer.p_type_rvo ?? "")) { + return acc.plus(actualPhosphate) + } + return acc + }, + new Decimal(0), + ) + return totalPhosphateDoseOrganicDose.gte(20) +} + +/** + * Memoized version of {@link calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {NormFilling} An object containing the total norm filling and a breakdown per application. + */ +export const getNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm = + withCalculationCache( + calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm, + "calculateNL2026FertilizerApplicationFillingForFosfaatGebruiksNorm", + pkg.calculatorVersion, + ) diff --git a/fdm-calculator/src/norms/nl/2026/filling/input.test.ts b/fdm-calculator/src/norms/nl/2026/filling/input.test.ts new file mode 100644 index 000000000..e9fa1f331 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/input.test.ts @@ -0,0 +1,167 @@ +import type { + Cultivation, + FdmType, + Fertilizer, + FertilizerApplication, + Field, +} from "@svenvw/fdm-core" +import { beforeEach, describe, expect, it, vi } from "vitest" +import { collectNL2026InputForFertilizerApplicationFilling } from "./input" +import type { NL2026NormsFillingInput } from "./types" + +// Mock the entire @svenvw/fdm-core module +vi.mock("@svenvw/fdm-core", () => ({ + getField: vi.fn(), + getGrazingIntention: vi.fn(), + isOrganicCertificationValid: vi.fn(), + getCultivations: vi.fn(), + getFertilizerApplications: vi.fn(), + getFertilizers: vi.fn(), +})) + +// Import the mocked functions +import { + getCultivations, + getFertilizerApplications, + getFertilizers, + getField, + getGrazingIntention, + isOrganicCertificationValid, +} from "@svenvw/fdm-core" + +describe("collectNL2026InputForFertilizerApplicationFilling", () => { + const mockFdm = {} as FdmType + const mockPrincipalId = "principal123" + const mockFieldId = "field456" + const mockFosfaatgebruiksnorm = 100 + + beforeEach(() => { + // Reset all mocks before each test + vi.clearAllMocks() + + // Set up default mock implementations for a successful scenario + vi.mocked(getField).mockResolvedValue({ + b_id: mockFieldId, + b_id_farm: "farm789", + b_centroid: [10, 20], + // Add other necessary Field properties + } as Field) + vi.mocked(getGrazingIntention).mockResolvedValue(true) + vi.mocked(isOrganicCertificationValid).mockResolvedValue(false) + vi.mocked(getCultivations).mockResolvedValue([ + { + b_lu: "cult1", + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + b_lu_catalogue: "nl_2014", + }, + ] as unknown as Cultivation[]) + vi.mocked(getFertilizerApplications).mockResolvedValue([ + { p_app_id: "app1", p_id_catalogue: "fert1", p_app_amount: 1000 }, + ] as FertilizerApplication[]) + vi.mocked(getFertilizers).mockResolvedValue([ + { p_id_catalogue: "fert1", p_n_rt: 5, p_type_rvo: "115" }, + ] as Fertilizer[]) + }) + + it("should successfully collect all input data for a valid scenario", async () => { + const expectedB_centroid: [number, number] = [10, 20] + const expectedCultivations = [ + { + b_lu: "cult1", + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + b_lu_catalogue: "nl_2014", + }, + ] + const expectedApplications: FertilizerApplication[] = [ + { p_app_id: "app1", p_id_catalogue: "fert1", p_app_amount: 1000 }, + ] + const expectedFertilizers: Fertilizer[] = [ + { p_id_catalogue: "fert1", p_n_rt: 5, p_type_rvo: "115" }, + ] + + const result = await collectNL2026InputForFertilizerApplicationFilling( + mockFdm, + mockPrincipalId, + mockFieldId, + mockFosfaatgebruiksnorm, + ) + + // Assert that all fdm-core functions were called with the correct arguments + expect(getField).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + mockFieldId, + ) + expect(getGrazingIntention).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "farm789", + 2026, + ) + expect(isOrganicCertificationValid).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "farm789", + new Date(2026, 4, 15), + ) + expect(getCultivations).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + mockFieldId, + { + start: new Date(2026, 0, 1), + end: new Date(2026, 11, 31, 23, 59, 59, 999), + }, + ) + expect(getFertilizerApplications).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "field456", + { + start: new Date(2026, 0, 1), + end: new Date(2026, 11, 31, 23, 59, 59, 999), + }, + ) + expect(getFertilizers).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "farm789", + ) + + // Assert the structure and content of the returned NL2026NormsFillingInput object + expect(result).toEqual({ + cultivations: expectedCultivations, + applications: expectedApplications, + fertilizers: expectedFertilizers, + has_organic_certification: false, + has_grazing_intention: true, + fosfaatgebruiksnorm: mockFosfaatgebruiksnorm, + b_centroid: expectedB_centroid, + } as NL2026NormsFillingInput) + }) + + it("should throw an error if the field is not found", async () => { + vi.mocked(getField).mockResolvedValue(null) // Simulate field not found + + await expect( + collectNL2026InputForFertilizerApplicationFilling( + mockFdm, + mockPrincipalId, + mockFieldId, + mockFosfaatgebruiksnorm, + ), + ).rejects.toThrow( + `Field with id ${mockFieldId} not found for principal ${mockPrincipalId}`, + ) + }) + + // Add more tests for edge cases and different scenarios as needed + // For example: + // - No cultivations + // - No applications + // - No fertilizers + // - Different grazing intention / organic certification status + // - Empty b_centroid (if getField returns a field without centroid, though current mock ensures it has one) +}) diff --git a/fdm-calculator/src/norms/nl/2026/filling/input.ts b/fdm-calculator/src/norms/nl/2026/filling/input.ts new file mode 100644 index 000000000..b54c6a597 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/input.ts @@ -0,0 +1,98 @@ +import type { FdmType, PrincipalId, Timeframe } from "@svenvw/fdm-core" +import { + getCultivations, + getFertilizerApplications, + getFertilizers, + getField, + getGrazingIntention, + isOrganicCertificationValid, +} from "@svenvw/fdm-core" +import type { NL2026NormsFillingInput } from "./types" + +/** + * Collects all necessary input data from fdm-core functions for the NL 2026 norms filling calculations. + * This function standardizes the data collection process, ensuring all calculation functions + * receive a unified input object (NL2026NormsFillingInput). + * + * @param {FdmType} fdm - The FdmType instance for interacting with the Farm Data Model. + * @param {string} principal_id - The ID of the principal (user or organization) performing the calculation. + * @param {string} b_id - The ID of the field for which the norms are being calculated. + * @param {number} fosfaatgebruiksnorm - The phosphate usage norm in kg/ha for the current calculation. + * @returns {Promise} A promise that resolves to a standardized input object + * containing cultivations, fertilizer applications, fertilizers, organic certification status, + * grazing intention status, the phosphate usage norm, and the field's centroid. + * @throws {Error} Throws an error if the specified field cannot be found. + */ +export async function collectNL2026InputForFertilizerApplicationFilling( + fdm: FdmType, + principal_id: PrincipalId, + b_id: string, + fosfaatgebruiksnorm: number, +): Promise { + // Define the calendar year for the norms calculation. + const year = 2026 + // Define the timeframe for data collection for the current year. + const startOfYear = new Date(year, 0, 1) // January 1st of the specified year + const endOfYear = new Date(year, 11, 31, 23, 59, 59, 999) // December 31st of the specified year, including December 31st + const timeframe2026: Timeframe = { start: startOfYear, end: endOfYear } + + // 1. Retrieve field details using the field ID. + // This is crucial for obtaining the farm ID and the field's geographical centroid. + const field = await getField(fdm, principal_id, b_id) + if (!field) { + throw new Error( + `Field with id ${b_id} not found for principal ${principal_id}`, + ) + } + const b_id_farm = field.b_id_farm + const b_centroid = field.b_centroid + + // 2. Retrieve the grazing intention status for the farm for the specified year. + // This indicates whether grazing is intended on the farm, affecting certain norm calculations. + const has_grazing_intention = await getGrazingIntention( + fdm, + principal_id, + b_id_farm, + year, + ) + + // 3. Check the organic certification status for the farm. + // This is relevant for specific organic-rich fertilizer regulations. + // The date is set to mid-year to ensure it falls within the certification period if applicable. + const has_organic_certification = await isOrganicCertificationValid( + fdm, + principal_id, + b_id_farm, + new Date(year, 4, 15), // May 15th of the specified year + ) + + // 4. Retrieve all cultivations associated with the field within the defined cultivation timeframe. + // This data is used to determine land use (e.g., bouwland/grasland). + const cultivations = await getCultivations( + fdm, + principal_id, + b_id, + timeframe2026, + ) + + // 5. Retrieve all fertilizer applications for the farm within the current year's timeframe. + const applications = await getFertilizerApplications( + fdm, + principal_id, + b_id, + timeframe2026, + ) + // 6. Retrieve details of all fertilizers used on the farm. + const fertilizers = await getFertilizers(fdm, principal_id, b_id_farm) + + // Assemble all collected data into the standardized NL2026NormsFillingInput object. + return { + cultivations: cultivations, + applications: applications, + fertilizers: fertilizers, + has_organic_certification: has_organic_certification, + has_grazing_intention: has_grazing_intention, + fosfaatgebruiksnorm: fosfaatgebruiksnorm, + b_centroid: b_centroid, + } +} diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts new file mode 100644 index 000000000..5553e1d1f --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts @@ -0,0 +1,776 @@ +import type { + Cultivation, + Fertilizer, + FertilizerApplication, +} from "@svenvw/fdm-core" +import { afterEach, describe, expect, it, vi } from "vitest" +import { getRegion } from "../../2025/value/stikstofgebruiksnorm" +import type { RegionKey } from "../value/types" +import { + calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm, + getWorkingCoefficient, + isBouwland, +} from "./stikstofgebruiksnorm" +import type { NL2026NormsFillingInput } from "./types" + +// Mock getRegion +vi.mock("../value/stikstofgebruiksnorm", () => ({ + getRegion: vi.fn(), +})) + +describe("isBouwland", () => { + it("should return true if cultivation is not in non-bouwland codes", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-01-01", + b_lu_catalogue: "nl_2014", // A generic bouwland code + }, + ] + const applicationDate = new Date("2026-06-15") + expect(isBouwland(cultivations, applicationDate)).toBe(true) + }) + + it("should return false if cultivation is in non-bouwland codes", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-01-01", + b_lu_catalogue: "nl_265", // Grasland + }, + ] + const applicationDate = new Date("2026-06-15") + expect(isBouwland(cultivations, applicationDate)).toBe(false) + }) + + it("should return false if no active cultivation exists", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2024-01-01", + b_end: "2024-12-31", + b_lu_catalogue: "nl_2014", + }, + ] + const applicationDate = new Date("2026-06-15") + expect(isBouwland(cultivations, applicationDate)).toBe(false) + }) + + it("should return true for a cultivation spanning the application date", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-01-01", + b_end: "2026-12-31", + b_lu_catalogue: "nl_2014", + }, + ] + const applicationDate = new Date("2026-07-01") + expect(isBouwland(cultivations, applicationDate)).toBe(true) + }) + + it("should return false for a cultivation ending before the application date", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-01-01", + b_end: "2026-06-30", + b_lu_catalogue: "nl_2014", + }, + ] + const applicationDate = new Date("2026-07-01") + expect(isBouwland(cultivations, applicationDate)).toBe(false) + }) + + it("should return false for a cultivation starting after the application date", () => { + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-08-01", + b_end: "2026-12-31", + b_lu_catalogue: "nl_2014", + }, + ] + const applicationDate = new Date("2026-07-01") + expect(isBouwland(cultivations, applicationDate)).toBe(false) + }) +}) + +describe("getWorkingCoefficient", () => { + it("should return default details if p_type_rvo is null or undefined", () => { + const result = getWorkingCoefficient( + null, + "zand_nwc", + true, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(1.0) + expect(result.description).toBe("Kunstmest") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return default details if p_type_rvo is not found in table9", () => { + const result = getWorkingCoefficient( + "999", + "zand_nwc", + true, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(1.0) + expect(result.description).toBe("Kunstmest") + expect(result.subTypeDescription).toBeUndefined() + }) + + // Drijfmest van graasdieren op het eigen bedrijf geproduceerd + describe("Drijfmest van graasdieren op het eigen bedrijf geproduceerd (onFarmProduced: true)", () => { + const p_type_rvo = "14" // Drijfmest rundvee + const soilType: RegionKey = "zand_nwc" + const isBouwland = false // Grasland + const fertilizerOnFarmProduced = true // Explicitly true for on-farm produced + + it("should return 0.45 for on-farm produced drijfmest with grazing intention", () => { + const b_grazing_intention = true + const applicationDate = new Date("2026-06-15") + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.45) + expect(result.description).toBe( + "Drijfmest van graasdieren op het eigen bedrijf geproduceerd", + ) + expect(result.subTypeDescription).toBe("Op bedrijf met beweiding") + }) + + it("should return 0.60 for on-farm produced drijfmest without grazing intention", () => { + const b_grazing_intention = false + const applicationDate = new Date("2026-06-15") + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.6) + expect(result.description).toBe( + "Drijfmest van graasdieren op het eigen bedrijf geproduceerd", + ) + expect(result.subTypeDescription).toBe( + "Op bedrijf zonder beweiding", + ) + }) + }) + + // Drijfmest van graasdieren aangevoerd + it("should return 0.60 for aangevoerd drijfmest (onFarmProduced: false)", () => { + const p_type_rvo = "14" // Drijfmest rundvee + const soilType: RegionKey = "zand_nwc" + const b_grazing_intention = true + const isBouwland = false + const applicationDate = new Date("2026-06-15") + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.6) + expect(result.description).toBe("Drijfmest van graasdieren aangevoerd") + expect(result.subTypeDescription).toBeUndefined() + }) + + // Drijfmest van varkens + describe("Drijfmest van varkens", () => { + const p_type_rvo = "46" // Drijfmest fokzeugen + const b_grazing_intention = false + const isBouwland = true + const applicationDate = new Date("2026-06-15") + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + + it("should return 0.60 for klei en veen soil", () => { + const soilType: RegionKey = "klei" + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.6) + expect(result.description).toBe("Drijfmest van varkens") + expect(result.subTypeDescription).toBe("Op klei en veen") + }) + + it("should return 0.80 for zand en lƶss soil", () => { + const soilType: RegionKey = "zand_nwc" + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.8) + expect(result.description).toBe("Drijfmest van varkens") + expect(result.subTypeDescription).toBe("Op zand en lƶss") + }) + }) + + // Dunnen fractie na mestbewerking en gier + it("should return 0.80 for dunne fractie", () => { + const p_type_rvo = "12" // Filtraat na mestscheiding + const soilType: RegionKey = "zand_nwc" + const b_grazing_intention = false + const isBouwland = true + const applicationDate = new Date("2026-06-15") + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.8) + expect(result.description).toBe( + "Dunne fractie na mestbewerking en gier", + ) + expect(result.subTypeDescription).toBeUndefined() + }) + + // Vaste mest van graasdieren op het eigen bedrijf geproduceerd + describe("Vaste mest van graasdieren op het eigen bedrijf geproduceerd (onFarmProduced: true)", () => { + const p_type_rvo = "10" // Vaste mest rundvee + const fertilizerOnFarmProduced = true + const b_grazing_intention = false + const isBouwland = true + const soilType: RegionKey = "klei" + + it("should return 0.30 for bouwland on klei/veen from Sep 1 to Jan 31", () => { + const applicationDate = new Date("2026-10-15") // October + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.3) + expect(result.description).toBe( + "Vaste mest van graasdieren op het eigen bedrijf geproduceerd", + ) + expect(result.subTypeDescription).toBe( + "Op bouwland op klei en veen, van 1 september t/m 31 januari", + ) + }) + + it("should return 0.45 for overige toepassingen on bedrijf met beweiding (outside Sep-Jan period)", () => { + const applicationDate = new Date("2026-03-15") // March + const b_grazing_intention_true = true + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention_true, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.45) + expect(result.description).toBe( + "Vaste mest van graasdieren op het eigen bedrijf geproduceerd", + ) + expect(result.subTypeDescription).toBe( + "Overige toepassingen op bedrijf met beweiding", + ) + }) + + it("should return 0.60 for overige toepassingen on bedrijf zonder beweiding (outside Sep-Jan period)", () => { + const applicationDate = new Date("2026-03-15") // March + const b_grazing_intention_false = false + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention_false, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.6) + expect(result.description).toBe( + "Vaste mest van graasdieren op het eigen bedrijf geproduceerd", + ) + expect(result.subTypeDescription).toBe( + "Overige toepassingen op bedrijf zonder beweiding", + ) + }) + }) + + // Vaste mest van graasdieren aangevoerd + it("should return 0.40 for aangevoerd vaste mest (onFarmProduced: false) overige toepassingen", () => { + const p_type_rvo = "10" // Vaste mest rundvee + const soilType: RegionKey = "zand_nwc" + const b_grazing_intention = false + const isBouwland = true + const applicationDate = new Date("2026-06-15") + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.4) + expect(result.description).toBe("Vaste mest van graasdieren aangevoerd") + expect(result.subTypeDescription).toBe("Overige toepassingen") + }) + + // Vaste mest van varkens, pluimvee en nertsen + it("should return 0.55 for vaste mest van varkens, pluimvee en nertsen", () => { + const p_type_rvo = "40" // Varkens, vaste mest + const soilType: RegionKey = "zand_nwc" + const b_grazing_intention = false + const isBouwland = true + const applicationDate = new Date("2026-06-15") + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.55) + expect(result.description).toBe( + "Vaste mest van varkens, pluimvee en nertsen", + ) + expect(result.subTypeDescription).toBeUndefined() + }) + + // Vaste mest van overige diersoorten + describe("Vaste mest van overige diersoorten", () => { + const p_type_rvo = "104" // Cavia, vaste mest + const soilType: RegionKey = "klei" + const b_grazing_intention = false + const isBouwland = true + const fertilizerOnFarmProduced = false // Explicitly false for aangevoerd + + it("should return 0.30 for bouwland on klei/veen from Sep 1 to Jan 31", () => { + const applicationDate = new Date("2026-11-01") // November + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.3) + expect(result.description).toBe( + "Vaste mest van overige diersoorten", + ) + expect(result.subTypeDescription).toBe( + "Op bouwland op klei en veen, van 1 september t/m 31 januari", + ) + }) + + it("should return 0.40 for overige toepassingen (outside Sep-Jan period)", () => { + const applicationDate = new Date("2026-04-01") // April + const result = getWorkingCoefficient( + p_type_rvo, + soilType, + b_grazing_intention, + isBouwland, + applicationDate, + fertilizerOnFarmProduced, + ) + expect(result.p_n_wcl).toBe(0.4) + expect(result.description).toBe( + "Vaste mest van overige diersoorten", + ) + expect(result.subTypeDescription).toBe("Overige toepassingen") + }) + }) + + // Overig (top-level entries) + it("should return 0.10 for Compost", () => { + const result = getWorkingCoefficient( + "111", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(0.1) + expect(result.description).toBe("Compost") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return 0.25 for Champost", () => { + const result = getWorkingCoefficient( + "110", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(0.25) + expect(result.description).toBe("Champost") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return 0.40 for Zuiveringsslib", () => { + const result = getWorkingCoefficient( + "114", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(0.4) + expect(result.description).toBe("Zuiveringsslib") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return 0.50 for Overige organische meststoffen", () => { + const result = getWorkingCoefficient( + "116", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(0.5) + expect(result.description).toBe("Overige organische meststoffen") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return 1.0 for Kunstmest", () => { + const result = getWorkingCoefficient( + "115", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(1.0) + expect(result.description).toBe("Kunstmest") + expect(result.subTypeDescription).toBeUndefined() + }) + + it("should return 1.0 for Mineralenconcentraat", () => { + const result = getWorkingCoefficient( + "120", + "zand_nwc", + false, + true, + new Date(), + false, + ) + expect(result.p_n_wcl).toBe(1.0) + expect(result.description).toBe("Mineralenconcentraat") + expect(result.subTypeDescription).toBeUndefined() + }) +}) + +describe("calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm", () => { + afterEach(() => { + vi.clearAllMocks() + }) + + it("should calculate norm filling correctly for a single application with known nitrogen content", async () => { + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-05-01", + p_app_amount: 1000, + p_id_catalogue: "fert1", + }, + ] + const fertilizers: Fertilizer[] = [ + { + p_id_catalogue: "fert1", + p_n_rt: 5, // 5 kg N per ton + p_type_rvo: "115", // Kunstmest (working coefficient 1.0) + }, + ] + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = false + const cultivations: Cultivation[] = [] + + const result = + await calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, // Default value for tests + fosfaatgebruiksnorm: 0, // Default value for tests + } as NL2026NormsFillingInput, + ) + + // Expected: 1000 kg * 5 kg/ton * 1.0 (100%) / 1000 = 5 + expect(result.normFilling).toBeCloseTo(5) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(5) + expect(result.applicationFilling[0].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 100% - Kunstmest", + ) + }) + + it("should calculate norm filling correctly for multiple applications", async () => { + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-05-01", + p_app_amount: 1000, + p_id_catalogue: "fert1", + }, + { + p_app_id: "app2", + b_id: "field1", + p_app_date: "2026-03-15", + p_app_amount: 500, + p_id_catalogue: "fert2", + }, + ] + const fertilizers: Fertilizer[] = [ + { + p_id_catalogue: "fert1", + p_n_rt: 5, // 5 kg N per ton + p_type_rvo: "115", // Kunstmest (working coefficient 1.0) + }, + { + p_id_catalogue: "fert2", + p_n_rt: 10, // 10 kg N per ton + p_type_rvo: "111", // Compost (working coefficient 0.1) + }, + ] + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = false + const cultivations: Cultivation[] = [] + + const result = + await calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, // Default value for tests + fosfaatgebruiksnorm: 0, // Default value for tests + } as NL2026NormsFillingInput, + ) + + // App1: 1000 * 5 * 1.0 / 1000 = 5 + // App2: 500 * 10 * 0.1 / 1000 = 0.5 + // Total: 5.5 + expect(result.normFilling).toBeCloseTo(5.5) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(5) + expect(result.applicationFilling[0].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 100% - Kunstmest", + ) + expect(result.applicationFilling[1].normFilling).toBeCloseTo(0.5) + expect(result.applicationFilling[1].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 10% - Compost", + ) + }) + + it("should use table11Mestcodes for nitrogen content if p_n_rt is 0", async () => { + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-05-01", + p_app_amount: 1000, + p_id_catalogue: "fert1", + }, + ] + const fertilizers: Fertilizer[] = [ + { + p_id_catalogue: "fert1", + p_n_rt: 0, // Nitrogen content not directly known + p_type_rvo: "14", // Drijfmest rundvee (Table 11: 4.0 kg N/ton) + }, + ] + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = true // Drijfmest graasdieren, met beweiding -> 0.45 + const cultivations: Cultivation[] = [] + + const result = + await calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, // Default value for tests + fosfaatgebruiksnorm: 0, // Default value for tests + } as NL2026NormsFillingInput, + ) + + // Expected: 1000 * 4.0 (from Table 11) * 0.45 (from Table 9) / 1000 = 1.8 + expect(result.normFilling).toBeCloseTo(1.8) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(1.8) + expect(result.applicationFilling[0].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 45% - Drijfmest van graasdieren op het eigen bedrijf geproduceerd - Op bedrijf met beweiding", + ) + }) + + it("should throw an error if fertilizer cannot be found", async () => { + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-05-01", + p_app_amount: 1000, + p_id_catalogue: "nonExistentFert", + }, + ] + const fertilizers: Fertilizer[] = [] // Empty fertilizers array + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = false + const cultivations: Cultivation[] = [] + + await expect( + calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm({ + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, // Default value for tests + fosfaatgebruiksnorm: 0, // Default value for tests + } as NL2026NormsFillingInput), + ).rejects.toThrow( + "Fertilizer nonExistentFert not found for application app1", + ) + }) + + it("should treat onFarmProduced as false when has_grazing_intention is false for drijfmest", async () => { + vi.mocked(getRegion).mockResolvedValue("zand_nwc") + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-05-01", + p_app_amount: 1000, + p_id_catalogue: "fert1", + }, + ] + const fertilizers: Fertilizer[] = [ + { + p_id_catalogue: "fert1", + p_n_rt: 0, // Nitrogen content not directly known + p_type_rvo: "14", // Drijfmest rundvee (Table 11: 4.0 kg N/ton) + }, + ] + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = false // No grazing intention, so onFarmProduced should be false + const cultivations: Cultivation[] = [] + + const result = + await calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, + fosfaatgebruiksnorm: 0, + } as NL2026NormsFillingInput, + ) + + // For p_type_rvo "14" (Drijfmest rundvee), if onFarmProduced is false, + // it falls into "Drijfmest van graasdieren aangevoerd" which has p_n_wcl of 0.60. + // Expected: 1000 * 4.0 (from Table 11) * 0.60 (from Table 9) / 1000 = 2.4 + expect(result.normFilling).toBeCloseTo(2.4) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(2.4) + expect(result.applicationFilling[0].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 60% - Drijfmest van graasdieren aangevoerd", + ) + }) + + it("should correctly apply bouwland logic for working coefficient", async () => { + vi.mocked(getRegion).mockResolvedValue("klei") // Soil type for bouwland rule + const applications: FertilizerApplication[] = [ + { + p_app_id: "app1", + b_id: "field1", + p_app_date: "2026-10-15", // Sep 1 to Jan 31 period + p_app_amount: 1000, + p_id_catalogue: "fert1", + }, + ] + const fertilizers: Fertilizer[] = [ + { + p_id_catalogue: "fert1", + p_n_rt: 10, // 10 kg N per ton + p_type_rvo: "10", + }, + ] + const b_centroid: [number, number] = [0, 0] + const has_grazing_intention = false + const cultivations: Cultivation[] = [ + { + b_lu: "cult1", + b_start: "2026-01-01", + b_end: "2026-12-31", + b_lu_catalogue: "nl_2014", // Bouwland + }, + ] + + const result = + await calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + has_organic_certification: false, // Default value for tests + fosfaatgebruiksnorm: 0, // Default value for tests + } as NL2026NormsFillingInput, + ) + + // For p_type_rvo "10" (Vaste mest rundvee), onFarmProduced: true in table9. + // Since has_grazing_intention is false, onFarmProduced will be false in the main function. + // For "bouwland op klei en veen, van 1 september t/m 31 januari", p_n_wcl is 0.3. + // Expected: 1000 * 10 * 0.3 / 1000 = 3 + expect(result.normFilling).toBeCloseTo(3) + expect(result.applicationFilling[0].normFilling).toBeCloseTo(3) + expect(result.applicationFilling[0].normFillingDetails).toBe( + "Werkingscoƫfficiƫnt: 30% - Vaste mest van graasdieren aangevoerd - Op bouwland op klei en veen, van 1 september t/m 31 januari", + ) + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts new file mode 100644 index 000000000..02070038e --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts @@ -0,0 +1,260 @@ +import { type Cultivation, withCalculationCache } from "@svenvw/fdm-core" +import Decimal from "decimal.js" +import pkg from "../../../../package" +import { getRegion } from "../../2025/value/stikstofgebruiksnorm" +import type { RegionKey } from "../value/types" +import { table9 } from "./table-9" +import { table11Mestcodes } from "./table-11-mestcodes" +import type { + NL2026NormsFillingInput, + WorkingCoefficientDetails, +} from "./types" +import type { NormFilling } from "norms/nl/types" + +/** + * Calculates the nitrogen utilization norm filling for a set of fertilizer applications. + * This function determines the amount of effective nitrogen applied, taking into account + * fertilizer type, nitrogen content, working coefficients, soil type, grazing intention, + * and land use (bouwland/arable land). + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {Promise} An object containing the total norm filling and details for each application. + */ +export async function calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm( + input: NL2026NormsFillingInput, +): Promise { + const { + applications, + fertilizers, + b_centroid, + has_grazing_intention, + cultivations, + } = input + + const applicationFillings: NormFilling["applicationFilling"] = [] + let totalNormFilling = new Decimal(0) + + const soilType = await getRegion(b_centroid) + + for (const application of applications) { + const fertilizer = fertilizers.find( + (f) => f.p_id_catalogue === application.p_id_catalogue, + ) + if (!fertilizer) { + throw new Error( + `Fertilizer ${application.p_id_catalogue} not found for application ${application.p_app_id}`, + ) + } + + // If nitrogen content is not known (explicitly 0 or undefined/null), use the value from Table 11 based on p_type_rvo + let nitrogenContentValue = fertilizer.p_n_rt + if ( + nitrogenContentValue === 0 || + nitrogenContentValue === undefined || + nitrogenContentValue === null + ) { + const table11Entry = table11Mestcodes.find( + (entry) => entry.p_type_rvo === fertilizer.p_type_rvo, + ) + nitrogenContentValue = table11Entry?.p_n_rt ?? 0 + } + const p_n_rt = new Decimal(nitrogenContentValue) + + const p_app_date = new Date(application.p_app_date) + const isCurrentBouwland = isBouwland(cultivations, p_app_date) + + // Determine the onFarmProduced status of the *actual fertilizer* based on temporary logic. + // TODO: Implement proper determination of onFarmProduced based on actual farm data. + const onFarmProduced = has_grazing_intention // Assume that if farm performs grazing, drijfmest and vaste mest are from the farm itself, otherwise supplied. + + const workingCoefficientDetails = getWorkingCoefficient( + fertilizer.p_type_rvo, + soilType, + has_grazing_intention, + isCurrentBouwland, + p_app_date, + onFarmProduced, + ) + + // Calculate norm filling: amount * nitrogen content * (working coefficient / 100) / 1000 + const p_app_amount = new Decimal(application.p_app_amount) + const normFilling = p_app_amount + .times(p_n_rt) + .times(workingCoefficientDetails.p_n_wcl) + .dividedBy(1000) + totalNormFilling = totalNormFilling.plus(normFilling) + + const descriptionParts = [workingCoefficientDetails.description] + if (workingCoefficientDetails.subTypeDescription) { + descriptionParts.push(workingCoefficientDetails.subTypeDescription) + } + const normFillingDetailString = `Werkingscoƫfficiƫnt: ${workingCoefficientDetails.p_n_wcl * 100}% - ${descriptionParts.join(" - ")}` + + applicationFillings.push({ + p_app_id: application.p_app_id, + normFilling: normFilling.toNumber(), + normFillingDetails: normFillingDetailString, + }) + } + + return { + normFilling: totalNormFilling.toNumber(), + applicationFilling: applicationFillings, + } +} + +/** + * Determines if a field is considered "Bouwland" (arable land) at a given application date. + * A field is not considered Bouwland if its active cultivation's `b_lu_catalogue` code + * is one of the specified non-bouwland codes. + * + * @param {Cultivation[]} cultivations - An array of cultivations for the farm. + * @param {Date} p_app_date - The date of the fertilizer application. + * @returns {boolean} True if the field is considered Bouwland, false otherwise. + */ +export function isBouwland( + cultivations: Cultivation[], + p_app_date: Date, +): boolean { + const nonBouwlandCodes = ["nl_265", "nl_266", "nl_331", "nl_332"] + + const activeCultivation = cultivations.find((c) => { + const startDate = new Date(c.b_start) + const endDate = c.b_end ? new Date(c.b_end) : undefined + return ( + p_app_date >= startDate && + (endDate === undefined || p_app_date <= endDate) + ) + }) + + if ( + !activeCultivation || + nonBouwlandCodes.includes(activeCultivation.b_lu_catalogue) + ) { + return false + } + + return true +} + +/** + * Determines the working coefficient for a given fertilizer application based on various conditions. + * The working coefficient is retrieved from `table9` and depends on the fertilizer type, + * whether it's produced on-farm, soil type, grazing intention, land use, and application date. + * + * @param {string | null | undefined} p_type_rvo - The RVO fertilizer type code. + * @param {RegionKey | undefined} soilType - The soil type of the field. + * @param {boolean} b_grazing_intention - Indicates if there is a grazing intention for the farm. + * @param {boolean} isBouwland - True if the land is arable land (bouwland), false otherwise. + * @param {Date} p_app_date - The date of the fertilizer application. + * @param {boolean} fertilizerOnFarmProduced - True if the fertilizer is produced on the farm, false otherwise. + * @returns {WorkingCoefficientDetails} An object containing the working coefficient, its main description, and an optional subtype description. + */ +export function getWorkingCoefficient( + p_type_rvo: string | null | undefined, + soilType: RegionKey | undefined, + b_grazing_intention: boolean, + isBouwland: boolean, + p_app_date: Date, + fertilizerOnFarmProduced: boolean, // New parameter +): WorkingCoefficientDetails { + const defaultDetails: WorkingCoefficientDetails = { + p_n_wcl: 1.0, + description: "Kunstmest", + } + + if (!p_type_rvo) { + return defaultDetails + } + + for (const entry of table9) { + if (entry.p_type_rvo.includes(p_type_rvo)) { + // If the table entry explicitly specifies an onFarmProduced requirement, + // the fertilizer's onFarmProduced status must match it. + if ( + entry.onFarmProduced !== undefined && + entry.onFarmProduced !== fertilizerOnFarmProduced + ) { + continue // Mismatch, try next entry + } + + if (entry.subTypes) { + const matchingSubType = entry.subTypes.find((subType) => { + if ( + subType.b_grazing_intention !== undefined && + subType.b_grazing_intention !== b_grazing_intention + ) { + return false + } + + if ( + subType.grondsoortCode && + !subType.grondsoortCode.includes(soilType as RegionKey) + ) { + return false + } + + if ( + subType.isBouwland !== undefined && + subType.isBouwland !== isBouwland + ) { + return false + } + + if (subType.applicationPeriod) { + const appMonth = p_app_date.getMonth() // 0-11 (Jan is 0, Dec is 11) + const appDay = p_app_date.getDate() + + if ( + subType.applicationPeriod === + "1 september t/m 31 januari" + ) { + // September (month 8) to January (month 0) + if ( + !( + (appMonth >= 8 && appMonth <= 11) || + (appMonth === 0 && appDay <= 31) + ) + ) { + return false + } + } + } + return true // All conditions for this subType match + }) + + if (matchingSubType) { + return { + p_n_wcl: matchingSubType.p_n_wcl, + description: entry.description, + subTypeDescription: matchingSubType.description, + } + } + } else if (entry.p_n_wcl !== undefined) { + // If no subTypes, use the main entry's p_n_wcl + return { + p_n_wcl: entry.p_n_wcl, + description: entry.description, + } + } + } + } + + return defaultDetails // If no specific rule is found, return the default 100% (1.0) +} + +/** + * Memoized version of {@link calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsFillingInput} input - The standardized input object containing all necessary data. + * @returns {Promise} An object containing the total norm filling and details for each application. + */ +export const getNL2026FertilizerApplicationFillingForStikstofGebruiksNorm = + withCalculationCache( + calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm, + "calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm", + pkg.calculatorVersion, + ) diff --git a/fdm-calculator/src/norms/nl/2026/filling/table-11-mestcodes.ts b/fdm-calculator/src/norms/nl/2026/filling/table-11-mestcodes.ts new file mode 100644 index 000000000..34d5d38a2 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/table-11-mestcodes.ts @@ -0,0 +1,336 @@ +import type { Table11Mestcodes } from "./types" + +export const table11Mestcodes: Table11Mestcodes = [ + { + p_type_rvo: "10", + p_type_nitratesdirective: true, + p_n_rt: 6.4, + p_p_rt: 3.2, + }, + { + p_type_rvo: "11", + p_type_nitratesdirective: true, + p_n_rt: 4.0, + p_p_rt: 1.3, + }, + { + p_type_rvo: "12", + p_type_nitratesdirective: true, + p_n_rt: 4.0, + p_p_rt: 0.2, + }, + { + p_type_rvo: "13", + p_type_nitratesdirective: true, + p_n_rt: 9.2, + p_p_rt: 9.2, + }, + { + p_type_rvo: "14", + p_type_nitratesdirective: true, + p_n_rt: 4.0, + p_p_rt: 1.5, + }, + { + p_type_rvo: "17", + p_type_nitratesdirective: true, + p_n_rt: 4.2, + p_p_rt: 5.0, + }, + { + p_type_rvo: "18", + p_type_nitratesdirective: true, + p_n_rt: 3.2, + p_p_rt: 1.2, + }, + { + p_type_rvo: "19", + p_type_nitratesdirective: true, + p_n_rt: 5.5, + p_p_rt: 2.2, + }, + { + p_type_rvo: "23", + p_type_nitratesdirective: true, + p_n_rt: 30.1, + p_p_rt: 22.9, + }, + { + p_type_rvo: "30", + p_type_nitratesdirective: true, + p_n_rt: 9.9, + p_p_rt: 6.2, + }, + { + p_type_rvo: "31", + p_type_nitratesdirective: true, + p_n_rt: 24.3, + p_p_rt: 22.1, + }, + { + p_type_rvo: "32", + p_type_nitratesdirective: true, + p_n_rt: 26.0, + p_p_rt: 20.9, + }, + { + p_type_rvo: "33", + p_type_nitratesdirective: true, + p_n_rt: 32.6, + p_p_rt: 26.3, + }, + { + p_type_rvo: "35", + p_type_nitratesdirective: true, + p_n_rt: 26.8, + p_p_rt: 24.9, + }, + { + p_type_rvo: "39", + p_type_nitratesdirective: true, + p_n_rt: 31.1, + p_p_rt: 15.4, + }, + { + p_type_rvo: "40", + p_type_nitratesdirective: true, + p_n_rt: 8.1, + p_p_rt: 8.0, + }, + { + p_type_rvo: "41", + p_type_nitratesdirective: true, + p_n_rt: 6.8, + p_p_rt: 1.6, + }, + { + p_type_rvo: "42", + p_type_nitratesdirective: true, + p_n_rt: 2.0, + p_p_rt: 0.9, + }, + { + p_type_rvo: "43", + p_type_nitratesdirective: true, + p_n_rt: 11.0, + p_p_rt: 17.8, + }, + { + p_type_rvo: "46", + p_type_nitratesdirective: true, + p_n_rt: 3.8, + p_p_rt: 2.4, + }, + { + p_type_rvo: "50", + p_type_nitratesdirective: true, + p_n_rt: 6.4, + p_p_rt: 3.8, + }, + { + p_type_rvo: "56", + p_type_nitratesdirective: true, + p_n_rt: 8.5, + p_p_rt: 4.7, + }, + { + p_type_rvo: "60", + p_type_nitratesdirective: true, + p_n_rt: 4.8, + p_p_rt: 2.5, + }, + { + p_type_rvo: "61", + p_type_nitratesdirective: true, + p_n_rt: 9.1, + p_p_rt: 4.8, + }, + { + p_type_rvo: "75", + p_type_nitratesdirective: true, + p_n_rt: 27.7, + p_p_rt: 45.7, + }, + { + p_type_rvo: "76", + p_type_nitratesdirective: true, + p_n_rt: 7.9, + p_p_rt: 3.1, + }, + { + p_type_rvo: "80", + p_type_nitratesdirective: true, + p_n_rt: 9.7, + p_p_rt: 9.4, + }, + { + p_type_rvo: "81", + p_type_nitratesdirective: true, + p_n_rt: 5.8, + p_p_rt: 3.8, + }, + { + p_type_rvo: "90", + p_type_nitratesdirective: true, + p_n_rt: 11.3, + p_p_rt: 11.7, + }, + { + p_type_rvo: "91", + p_type_nitratesdirective: true, + p_n_rt: 0.81, + p_p_rt: 0.14, + }, + { + p_type_rvo: "92", + p_type_nitratesdirective: true, + p_n_rt: 4.4, + p_p_rt: 3.0, + }, + { + p_type_rvo: "25", + p_type_nitratesdirective: true, + p_n_rt: 4.8, + p_p_rt: 2.5, + }, + { + p_type_rvo: "26", + p_type_nitratesdirective: true, + p_n_rt: 5.0, + p_p_rt: 3.0, + }, + { + p_type_rvo: "27", + p_type_nitratesdirective: true, + p_n_rt: 4.7, + p_p_rt: 3.2, + }, + { + p_type_rvo: "95", + p_type_nitratesdirective: true, + p_n_rt: 7.1, + p_p_rt: 5.3, + }, + { + p_type_rvo: "96", + p_type_nitratesdirective: true, + p_n_rt: 4.1, + p_p_rt: 2.1, + }, + { + p_type_rvo: "97", + p_type_nitratesdirective: true, + p_n_rt: 8.9, + p_p_rt: 8.1, + }, + { + p_type_rvo: "98", + p_type_nitratesdirective: true, + p_n_rt: 8.9, + p_p_rt: 8.1, + }, + { + p_type_rvo: "99", + p_type_nitratesdirective: true, + p_n_rt: 32.6, + p_p_rt: 17.7, + }, + { + p_type_rvo: "100", + p_type_nitratesdirective: true, + p_n_rt: 23.1, + p_p_rt: 18.7, + }, + { + p_type_rvo: "101", + p_type_nitratesdirective: true, + p_n_rt: 23.1, + p_p_rt: 18.7, + }, + { + p_type_rvo: "102", + p_type_nitratesdirective: true, + p_n_rt: 11.9, + p_p_rt: 11.7, + }, + { + p_type_rvo: "103", + p_type_nitratesdirective: true, + p_n_rt: 11.9, + p_p_rt: 11.7, + }, + { + p_type_rvo: "104", + p_type_nitratesdirective: true, + p_n_rt: 11.9, + p_p_rt: 11.7, + }, + { + p_type_rvo: "105", + p_type_nitratesdirective: true, + p_n_rt: 11.9, + p_p_rt: 11.7, + }, + { + p_type_rvo: "106", + p_type_nitratesdirective: true, + p_n_rt: 11.9, + p_p_rt: 11.7, + }, + { + p_type_rvo: "107", + p_type_nitratesdirective: true, + p_n_rt: 5.5, + p_p_rt: 3.1, + }, + { + p_type_rvo: "108", + p_type_nitratesdirective: true, + }, + { + p_type_rvo: "109", + p_type_nitratesdirective: true, + p_n_rt: 8.0, + p_p_rt: 4.4, + }, + { + p_type_rvo: "110", + p_type_nitratesdirective: true, + p_n_rt: 7.0, + p_p_rt: 3.9, + }, + { + p_type_rvo: "111", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "112", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "113", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "114", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "115", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "116", + p_type_nitratesdirective: false, + }, + { + p_type_rvo: "117", + p_type_nitratesdirective: true, + p_n_rt: 7.1, + p_p_rt: 4.1, + }, + { + p_type_rvo: "120", + p_type_nitratesdirective: true, + }, +] diff --git a/fdm-calculator/src/norms/nl/2026/filling/table-9.ts b/fdm-calculator/src/norms/nl/2026/filling/table-9.ts new file mode 100644 index 000000000..37d311509 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/table-9.ts @@ -0,0 +1,178 @@ +import type { Table9 } from "./types" + +export const table9: Table9 = [ + { + description: + "Drijfmest van graasdieren op het eigen bedrijf geproduceerd", + p_type_rvo: ["14", "60", "18", "19"], + onFarmProduced: true, + subTypes: [ + { + description: "Op bedrijf met beweiding", + b_grazing_intention: true, + p_n_wcl: 0.45, + }, + { + description: "Op bedrijf zonder beweiding", + b_grazing_intention: false, + p_n_wcl: 0.6, + }, + ], + }, + { + description: "Drijfmest van graasdieren aangevoerd", + p_type_rvo: ["14", "60", "18", "19"], + onFarmProduced: false, + p_n_wcl: 0.6, + }, + { + description: "Drijfmest van varkens", + p_type_rvo: ["46", "50"], + subTypes: [ + { + description: "Op klei en veen", + grondsoortCode: ["klei", "veen"], + p_n_wcl: 0.6, + }, + { + description: "Op zand en lƶss", + grondsoortCode: ["zand_nwc", "zand_zuid", "loess"], + p_n_wcl: 0.8, + }, + ], + }, + { + description: "Drijfmest van overige diersoorten", + p_type_rvo: ["30", "76", "81", "91", "92"], + p_n_wcl: 0.6, + }, + { + description: "Dunne fractie na mestbewerking en gier", + p_type_rvo: ["12", "17", "41", "42"], + p_n_wcl: 0.8, + }, + { + description: + "Vaste mest van graasdieren op het eigen bedrijf geproduceerd", + p_type_rvo: ["10", "56", "61", "25", "26", "27", "95", "96"], + onFarmProduced: true, + subTypes: [ + { + description: + "Op bouwland op klei en veen, van 1 september t/m 31 januari", + grondsoortCode: ["klei", "veen"], + isBouwland: true, + applicationPeriod: "1 september t/m 31 januari", + p_n_wcl: 0.3, + }, + { + description: "Overige toepassingen op bedrijf met beweiding", + b_grazing_intention: true, + p_n_wcl: 0.45, + }, + { + description: "Overige toepassingen op bedrijf zonder beweiding", + b_grazing_intention: false, + p_n_wcl: 0.6, + }, + ], + }, + { + description: "Vaste mest van graasdieren aangevoerd", + p_type_rvo: ["10", "56", "61", "25", "26", "27", "95", "96"], + onFarmProduced: false, + subTypes: [ + { + description: + "Op bouwland op klei en veen, van 1 september t/m 31 januari", + grondsoortCode: ["klei", "veen"], + isBouwland: true, + applicationPeriod: "1 september t/m 31 januari", + p_n_wcl: 0.3, + }, + { + description: "Overige toepassingen", + p_n_wcl: 0.4, + }, + ], + }, + { + description: "Vaste mest van varkens, pluimvee en nertsen", + p_type_rvo: [ + "23", + "31", + "32", + "33", + "35", + "39", + "40", + "43", + "75", + "80", + "97", + "98", + "99", + "100", + "101", + ], + p_n_wcl: 0.55, + }, + { + description: "Vaste mest van overige diersoorten", + p_type_rvo: [ + "11", + "13", + "24", + "30", + "76", + "81", + "90", + "91", + "92", + "102", + "103", + "104", + "105", + "106", + ], + subTypes: [ + { + description: + "Op bouwland op klei en veen, van 1 september t/m 31 januari", + grondsoortCode: ["klei", "veen"], + isBouwland: true, + applicationPeriod: "1 september t/m 31 januari", + p_n_wcl: 0.3, + }, + { + description: "Overige toepassingen", + p_n_wcl: 0.4, + }, + ], + }, + { + description: "Compost", + p_type_rvo: ["111", "112"], + p_n_wcl: 0.1, + }, + { + description: "Champost", + p_type_rvo: ["110", "117"], + p_n_wcl: 0.25, + }, + { + description: "Zuiveringsslib", + p_type_rvo: ["113", "114"], + p_n_wcl: 0.4, + }, + { + description: "Overige organische meststoffen", + p_type_rvo: ["116"], + p_n_wcl: 0.5, + }, + { + description: "Mineralenconcentraat", + p_type_rvo: ["120"], + p_n_wcl: 1, + }, +] diff --git a/fdm-calculator/src/norms/nl/2026/filling/types.d.ts b/fdm-calculator/src/norms/nl/2026/filling/types.d.ts new file mode 100644 index 000000000..808748021 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/filling/types.d.ts @@ -0,0 +1,46 @@ +import type * as schema from "@svenvw/fdm-core" +import type { + Cultivation, + Fertilizer, + FertilizerApplication, + Field, +} from "@svenvw/fdm-core" +import type { RegionKey } from "../value/types" + +export type Table11Mestcodes = { + p_type_rvo: schema.fertilizersCatalogueTypeSelect["p_type_rvo"] + p_type_nitratesdirective: boolean + p_n_rt?: number + p_p_rt?: number +}[] + +export type Table9 = { + description: string + p_type_rvo: schema.fertilizersCatalogueTypeSelect["p_type_rvo"][] + onFarmProduced?: boolean + subTypes?: { + description: string + b_grazing_intention?: boolean + grondsoortCode?: RegionKey[] + applicationPeriod?: "1 september t/m 31 januari" | "hele jaar" + isBouwland?: boolean + p_n_wcl: number + }[] + p_n_wcl?: number +}[] + +export type WorkingCoefficientDetails = { + p_n_wcl: number + description: string + subTypeDescription?: string +} + +export type NL2026NormsFillingInput = { + cultivations: Cultivation[] + applications: FertilizerApplication[] + fertilizers: Fertilizer[] + has_organic_certification: boolean + has_grazing_intention: boolean + fosfaatgebruiksnorm: number + b_centroid: Field["b_centroid"] +} diff --git a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.test.ts new file mode 100644 index 000000000..d84d497cf --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest" +import { calculateNL2026DierlijkeMestGebruiksNorm } from "./dierlijke-mest-gebruiksnorm" + +describe("calculateNL2026DierlijkeMestGebruiksNorm", () => { + it("should return the default norm value", async () => { + const result = await calculateNL2026DierlijkeMestGebruiksNorm() + expect(result.normValue).toBe(170) + expect(result.normSource).toBe("Standaard - geen derogatie") + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts new file mode 100644 index 000000000..489d3e769 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts @@ -0,0 +1,44 @@ +import { withCalculationCache } from "@svenvw/fdm-core" +import pkg from "../../../../package" +import type { DierlijkeMestGebruiksnormResult } from "norms/nl/types" + +/** + * Determines the 'gebruiksnorm' (usage standard) for nitrogen from animal manure + * for a given farm and parcel in the Netherlands for the year 2026. + * + * This function implements the rules and norms specified by the RVO for 2026. + * + * @param input - An object containing all necessary parameters for the calculation. + * See {@link DierlijkeMestGebruiksnormInput} for details. + * @returns An object of type `DierlijkeMestGebruiksnormResult` containing the determined + * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied. + * + * @remarks + * The rules for 2026 are as follows: + * - **Standard Norm**: The norm is 170 kg N/ha from animal manure. + */ +export async function calculateNL2026DierlijkeMestGebruiksNorm(): Promise { + let normValue: number + let normSource: string + + normValue = 170 + normSource = "Standaard - geen derogatie" + + return { normValue, normSource } +} + +/** + * Memoized version of {@link calculateNL2026DierlijkeMestGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsInput} input - An object containing all necessary parameters for the calculation. + * @returns {Promise} An object of type `DierlijkeMestGebruiksnormResult` containing the determined + * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied. + */ +export const getNL2026DierlijkeMestGebruiksNorm = withCalculationCache( + calculateNL2026DierlijkeMestGebruiksNorm, + "calculateNL2026DierlijkeMestGebruiksNorm", + pkg.calculatorVersion, +) diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts new file mode 100644 index 000000000..c358e5562 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts @@ -0,0 +1,9 @@ +export const fosfaatNormsData = [ + { + Arm: { grasland: 120, bouwland: 120 }, + Laag: { grasland: 105, bouwland: 80 }, + Neutraal: { grasland: 95, bouwland: 70 }, + Ruim: { grasland: 90, bouwland: 60 }, + Hoog: { grasland: 75, bouwland: 40 }, + }, +] diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts new file mode 100644 index 000000000..5707affc4 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest" +import { calculateNL2026FosfaatGebruiksNorm } from "./fosfaatgebruiksnorm" +import type { NL2026NormsInput, NL2026NormsInputForCultivation } from "./types" + +describe("calculateNL2026FosfaatGebruiksNorm", () => { + it("should return the correct norm for grasland", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: { type: "Point", coordinates: [5.0, 52.0] }, + }, + cultivations: [ + { + b_lu_catalogue: "nl_265", + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + const result = await calculateNL2026FosfaatGebruiksNorm(mockInput) + expect(result.normValue).toBe(120) + expect(result.normSource).toContain("Grasland") + }) + + it("should return the correct norm for bouwland", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: { type: "Point", coordinates: [5.0, 52.0] }, + }, + cultivations: [ + { + b_lu_catalogue: "nl_101", + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + const result = await calculateNL2026FosfaatGebruiksNorm(mockInput) + expect(result.normValue).toBe(120) + expect(result.normSource).toContain("Bouwland") + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts new file mode 100644 index 000000000..63b81a9f5 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -0,0 +1,175 @@ +import { withCalculationCache } from "@svenvw/fdm-core" +import Decimal from "decimal.js" +import pkg from "../../../../package" +import { fosfaatNormsData } from "./fosfaatgebruiksnorm-data" +import { determineNLHoofdteelt } from "../../2025/value/hoofdteelt" +import type { FosfaatKlasse, NL2026NormsInput } from "./types.d" +import { FosfaatGebruiksnormResult } from "norms/nl/types" +import { isCultivationGrasland } from "norms/nl/2025/value/fosfaatgebruiksnorm" + +/** + * Helper function to determine the phosphate class ('Arm', 'Laag', 'Neutraal', 'Ruim', 'Hoog') + * based on P-CaCl2 and P-Al soil analysis values and land type (grasland/bouwland). + * + * This logic is derived directly from "Tabel 1: Grasland (P-CaCl2/P-Al getal)" and + * "Tabel 2: Bouwland (P-CaCl2/P-Al getal)" in the RVO documentation for 2026. + * + * @param a_p_cc - The P-CaCl2 (P-PAE) value from soil analysis. + * @param a_p_al - The P-Al value from soil analysis. + * @param is_grasland - True if the land is grassland, false if arable land. + * @returns The determined `FosfaatKlasse`. + * @see {@link https://www.rvo.nl/onderwerpen/mest/gebruiken-en-uitrijden/fosfaat-landbouwgrond/differentiatie | RVO Fosfaatdifferentiatie (official page)} + */ +function getFosfaatKlasse( + a_p_cc: number, + a_p_al: number, + is_grasland: boolean, +): FosfaatKlasse { + // Round P-AL to whole number and convert to Decimal for precise comparisons + const pAl = new Decimal(a_p_al).toDecimalPlaces(0) + + // Round P-CaCl2 to 1 digit and convert to Decimal for precise comparisons + const pCc = new Decimal(a_p_cc).toDecimalPlaces(1) + + if (is_grasland) { + // Logic for Grasland (Table 1) + if (pCc.lessThan(0.8)) { + if (pAl.lessThan(21)) return "Arm" + if (pAl.lessThanOrEqualTo(45)) return "Laag" + if (pAl.lessThanOrEqualTo(55)) return "Neutraal" + return "Ruim" // pAl.greaterThan(new Decimal(55)) + } + if (pCc.lessThanOrEqualTo(1.4)) { + if (pAl.lessThan(21)) return "Arm" + if (pAl.lessThanOrEqualTo(30)) return "Laag" + if (pAl.lessThanOrEqualTo(45)) return "Neutraal" + return "Ruim" // pAl.greaterThan(new Decimal(45)) + } + if (pCc.lessThanOrEqualTo(2.4)) { + if (pAl.lessThan(21)) return "Laag" + if (pAl.lessThanOrEqualTo(30)) return "Neutraal" + if (pAl.lessThanOrEqualTo(55)) return "Ruim" + return "Hoog" // pAl.greaterThan(new Decimal(55)) + } + if (pCc.lessThanOrEqualTo(3.4)) { + if (pAl.lessThan(21)) return "Neutraal" + if (pAl.lessThanOrEqualTo(45)) return "Ruim" + return "Hoog" // pAl.greaterThan(new Decimal(45)) + } + // pCc.greaterThan(new Decimal(3.4)) + if (pAl.lessThan(31)) return "Ruim" + return "Hoog" // pAl.greaterThanOrEqualTo(new Decimal(31)) + } + + // Logic for Bouwland (Table 2) + if (pCc.lessThan(0.8)) { + if (pAl.lessThan(46)) return "Arm" + return "Laag" // pAl.greaterThanOrEqualTo(new Decimal(46)) + } + if (pCc.lessThanOrEqualTo(1.4)) { + if (pAl.lessThan(46)) return "Arm" + if (pAl.lessThanOrEqualTo(55)) return "Laag" + return "Neutraal" // pAl.greaterThan(new Decimal(55)) + } + if (pCc.lessThanOrEqualTo(2.4)) { + if (pAl.lessThan(31)) return "Arm" + if (pAl.lessThanOrEqualTo(45)) return "Laag" + if (pAl.lessThanOrEqualTo(55)) return "Neutraal" + return "Ruim" // pAl.greaterThan(new Decimal(55)) + } + if (pCc.lessThanOrEqualTo(3.4)) { + if (pAl.lessThan(21)) return "Arm" + if (pAl.lessThanOrEqualTo(30)) return "Laag" + if (pAl.lessThanOrEqualTo(45)) return "Neutraal" + if (pAl.lessThanOrEqualTo(55)) return "Ruim" + return "Hoog" // pAl.greaterThan(new Decimal(55)) + } + // pCc.greaterThan(new Decimal(3.4)) + if (pAl.lessThan(31)) return "Laag" + if (pAl.lessThanOrEqualTo(45)) return "Neutraal" + if (pAl.lessThanOrEqualTo(55)) return "Ruim" + return "Hoog" // pAl.greaterThan(new Decimal(55)) +} + +/** + * Determines the 'gebruiksnorm' (usage standard) for phosphate for a given field + * based on its land type (grasland/bouwland) and soil phosphate condition, + * derived from P-CaCl2 and P-Al soil analysis values. + * + * This function implements the "Tabel Fosfaatgebruiksnormen 2026" and the + * "Differentiatie fosfaatgebruiksnorm 2026" rules from RVO. + * + * @param input - An object containing all necessary parameters for the calculation. + * See {@link FosfaatGebruiksnormInput} for details. + * @returns An object of type `FosfaatGebruiksnormResult` containing the determined + * phosphate usage standard (`normValue`) and the `fosfaatKlasse` (the phosphate + * class determined from the soil analysis). Returns `null` if a norm cannot be determined. + * + * @remarks + * The function operates as follows: + * 1. **Determine Phosphate Class**: The `getFosfaatKlasse` helper function is used + * to classify the soil's phosphate condition ('Arm', 'Laag', 'Neutraal', 'Ruim', 'Hoog') + * based on the provided `a_p_cc` and `a_p_al` values and whether it's grassland or arable land. + * This classification directly uses the lookup tables provided by RVO for 2026. + * 2. **Retrieve Base Norm**: The determined `fosfaatKlasse` is then used to look up the + * corresponding base phosphate norm from the `fosfaatNormsData.json` file. + * 3. **Apply Land Type**: The specific norm for either `grasland` or `bouwland` is selected + * from the base norm based on the `is_grasland` input parameter. + * 4. **Return Result**: The function returns the final `normValue` and the `fosfaatKlasse`. + * + * @see {@link https://www.rvo.nl/onderwerpen/mest/gebruiken-en-uitrijden/fosfaat-landbouwgrond | RVO Fosfaat landbouwgrond (official page)} + * @see {@link https://www.rvo.nl/onderwerpen/mest/gebruiken-en-uitrijden/fosfaat-landbouwgrond/differentiatie | RVO Fosfaatdifferentiatie (official page, including tables for 2026)} + */ +export async function calculateNL2026FosfaatGebruiksNorm( + input: NL2026NormsInput, +): Promise { + const cultivations = input.cultivations + const a_p_cc = input.soilAnalysis.a_p_cc + const a_p_al = input.soilAnalysis.a_p_al + + if (!a_p_al || !a_p_cc) { + throw new Error( + "Missing soil analysis data for NL 2026 Fosfaatgebruiksnorm", + ) + } + + const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2026) + const is_grasland = isCultivationGrasland(b_lu_catalogue) + + // Determine the phosphate class based on soil analysis values and land type. + const fosfaatKlasse = getFosfaatKlasse(a_p_cc, a_p_al, is_grasland) + + // Retrieve the base norms for the determined phosphate class. + const normsForKlasse = fosfaatNormsData[0][fosfaatKlasse] + + if (!normsForKlasse) { + throw new Error(`No phosphate norms found for class ${fosfaatKlasse}.`) + } + + // Select the specific norm based on whether it's grassland or arable land. + const normValue = is_grasland + ? normsForKlasse.grasland + : normsForKlasse.bouwland + const normSource = is_grasland + ? `Grasland: ${fosfaatKlasse}` + : `Bouwland: ${fosfaatKlasse}` + + return { normValue, normSource } +} + +/** + * Memoized version of {@link calculateNL2026FosfaatGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsInput} input - An object containing all necessary parameters for the calculation. + * @returns {Promise} An object of type `FosfaatGebruiksnormResult` containing the determined + * phosphate usage standard (`normValue`) and the `fosfaatKlasse` (the phosphate + * class determined from the soil analysis). Returns `null` if a norm cannot be determined. + */ +export const getNL2026FosfaatGebruiksNorm = withCalculationCache( + calculateNL2026FosfaatGebruiksNorm, + "calculateNL2026FosfaatGebruiksNorm", + pkg.calculatorVersion, +) diff --git a/fdm-calculator/src/norms/nl/2026/value/input.test.ts b/fdm-calculator/src/norms/nl/2026/value/input.test.ts new file mode 100644 index 000000000..59564d21f --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/input.test.ts @@ -0,0 +1,98 @@ +import type { + Cultivation, + FdmType, + Field, + SoilAnalysis, +} from "@svenvw/fdm-core" +import * as fdmCore from "@svenvw/fdm-core" +import { describe, expect, it, vi } from "vitest" +import { collectNL2026InputForNorms } from "./input" + +vi.mock("@svenvw/fdm-core", async () => { + const actual = await vi.importActual("@svenvw/fdm-core") + return { + ...actual, + getField: vi.fn(), + getCultivations: vi.fn(), + getCurrentSoilData: vi.fn(), + isDerogationGrantedForYear: vi.fn(), + getGrazingIntention: vi.fn(), + } +}) + +describe("collectNL2026InputForNorms", () => { + it("should collect input correctly", async () => { + const mockFdm = {} as FdmType + const mockPrincipalId = "principal-1" + const mockFieldId = "field-1" + + const mockField = { + b_id: mockFieldId, + b_id_farm: "farm-1", + b_centroid: { type: "Point", coordinates: [5.0, 52.0] }, + } as Field + const mockCultivations = [{ b_lu: "test" }] as Cultivation[] + const mockSoilAnalysis = [ + { parameter: "a_p_cc", value: 1.0 }, + { parameter: "a_p_al", value: 20 }, + ] + + const timeframe = { + start: new Date(2026, 0, 1), + end: new Date(2026, 11, 31), + } + + const timeframeCultivation = { + start: new Date(2024, 0, 1), + end: new Date(2026, 11, 31), + } + + vi.mocked(fdmCore.getField).mockResolvedValue(mockField) + vi.mocked(fdmCore.getCultivations).mockResolvedValue(mockCultivations) + vi.mocked(fdmCore.getCurrentSoilData).mockResolvedValue( + mockSoilAnalysis as unknown as SoilAnalysis[], + ) + vi.mocked(fdmCore.isDerogationGrantedForYear).mockResolvedValue(false) + vi.mocked(fdmCore.getGrazingIntention).mockResolvedValue(false) + + const result = await collectNL2026InputForNorms( + mockFdm, + mockPrincipalId, + mockFieldId, + ) + + expect(result.farm.has_grazing_intention).toBe(false) + expect(result.field).toBe(mockField) + expect(result.cultivations).toBe(mockCultivations) + expect(result.soilAnalysis).toEqual({ a_p_cc: 1.0, a_p_al: 20 }) + expect(fdmCore.getField).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + mockFieldId, + ) + expect(fdmCore.isDerogationGrantedForYear).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "farm-1", + 2026, + ) + expect(fdmCore.getGrazingIntention).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + "farm-1", + 2026, + ) + expect(fdmCore.getCultivations).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + mockFieldId, + timeframeCultivation, + ) + expect(fdmCore.getCurrentSoilData).toHaveBeenCalledWith( + mockFdm, + mockPrincipalId, + mockFieldId, + timeframe, + ) + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/value/input.ts b/fdm-calculator/src/norms/nl/2026/value/input.ts new file mode 100644 index 000000000..61b126c90 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/input.ts @@ -0,0 +1,84 @@ +import { + type FdmType, + getCultivations, + getCurrentSoilData, + getField, + getGrazingIntention, + type PrincipalId, + type Timeframe, +} from "@svenvw/fdm-core" +import type { NL2026NormsInput } from "./types.d" + +/** + * Collects all necessary input data from the FDM to calculate the Dutch (NL) norms for the year 2026. + * + * This function orchestrates fetching data for a given farm, its fields, cultivations, and soil analyses, + * and structures it into a format suitable for the various NL 2026 norm calculation functions. + * + * @param fdm - An initialized FdmType instance for data access. + * @param principal_id - The ID of the principal initiating the data collection. + * @param b_id - The unique identifier of the field for which to collect data. + * @returns A promise that resolves to an `NL2026NormsInput` object, containing all the + * structured data required for the norm calculations. + */ +export async function collectNL2026InputForNorms( + fdm: FdmType, + principal_id: PrincipalId, + b_id: string, +): Promise { + // Create timeframe for 2026 + const year = 2026 + const startOfYear = new Date(year, 0, 1) + const endOfYear = new Date(year, 11, 31) + const timeframe2026: Timeframe = { start: startOfYear, end: endOfYear } + const timeframe2026Cultivation: Timeframe = { + start: new Date(year - 1, 0, 1), + end: endOfYear, + } + + // 1. Get the details for the field. + const field = await getField(fdm, principal_id, b_id) + + // 2. Get the grazing intention for the farm + const has_grazing_intention = await getGrazingIntention( + fdm, + principal_id, + field.b_id_farm, + 2026, + ) + + // 3. Get the details of the cultivations + const cultivations = await getCultivations( + fdm, + principal_id, + b_id, + timeframe2026Cultivation, + ) + + // 5. Get the details of the soil analyses + const soilAnalysis = await getCurrentSoilData( + fdm, + principal_id, + field.b_id, + timeframe2026, + ) + const soilAnalysisPicked = { + a_p_cc: + soilAnalysis.find( + (x: { parameter: string }) => x.parameter === "a_p_cc", + )?.value ?? null, + a_p_al: + soilAnalysis.find( + (x: { parameter: string }) => x.parameter === "a_p_al", + )?.value ?? null, + } + + return { + farm: { + has_grazing_intention, + }, + field: field, + cultivations: cultivations, + soilAnalysis: soilAnalysisPicked, + } +} diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm-data.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm-data.ts new file mode 100644 index 000000000..e2ce284b0 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm-data.ts @@ -0,0 +1,2739 @@ +export const nitrogenStandardsData = [ + { + cultivation_rvo_table2: "Grasland", + b_lu_catalogue_match: ["nl_265", "nl_331"], + type: "grasland", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "beweiden", + norms: { + klei: { standard: 345, nv_area: 276 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 250, nv_area: 200 }, + loess: { standard: 250, nv_area: 200 }, + veen: { standard: 265, nv_area: 212 }, + }, + }, + { + omschrijving: "volledig maaien", + norms: { + klei: { standard: 385, nv_area: 308 }, + zand_nwc: { standard: 320, nv_area: 256 }, + zand_zuid: { standard: 320, nv_area: 256 }, + loess: { standard: 320, nv_area: 256 }, + veen: { standard: 300, nv_area: 240 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Tijdelijk grasland", + b_lu_catalogue_match: ["nl_266"], + type: "grasland_tijdelijk", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + period_description: "van 1 januari tot minstens 15 april", + period_start_month: 1, + period_start_day: 1, + period_end_month: 4, + period_end_day: 15, + norms: { + klei: { standard: 60, nv_area: 48 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 50, nv_area: 40 }, + loess: { standard: 50, nv_area: 40 }, + veen: { standard: 50, nv_area: 40 }, + }, + }, + { + period_description: "van 1 januari tot minstens 15 mei", + period_start_month: 1, + period_start_day: 1, + period_end_month: 5, + period_end_day: 15, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 90, nv_area: 72 }, + zand_zuid: { standard: 90, nv_area: 72 }, + loess: { standard: 90, nv_area: 72 }, + veen: { standard: 90, nv_area: 72 }, + }, + }, + { + period_description: "van 1 januari tot minstens 15 augustus", + period_start_month: 1, + period_start_day: 1, + period_end_month: 8, + period_end_day: 15, + norms: { + klei: { standard: 250, nv_area: 200 }, + zand_nwc: { standard: 210, nv_area: 168 }, + zand_zuid: { standard: 210, nv_area: 168 }, + loess: { standard: 210, nv_area: 168 }, + veen: { standard: 210, nv_area: 168 }, + }, + }, + { + period_description: "van 1 januari tot minstens 15 september", + period_start_month: 1, + period_start_day: 1, + period_end_month: 9, + period_end_day: 15, + norms: { + klei: { standard: 280, nv_area: 224 }, + zand_nwc: { standard: 235, nv_area: 188 }, + zand_zuid: { standard: 235, nv_area: 188 }, + loess: { standard: 235, nv_area: 188 }, + veen: { standard: 235, nv_area: 188 }, + }, + }, + { + period_description: "van 1 januari tot minstens 15 oktober", + period_start_month: 1, + period_start_day: 1, + period_end_month: 10, + period_end_day: 15, + norms: { + klei: { standard: 310, nv_area: 248 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 250, nv_area: 200 }, + loess: { standard: 250, nv_area: 200 }, + veen: { standard: 265, nv_area: 212 }, + }, + }, + { + period_description: "vanaf 15 april tot minstens 15 oktober", + period_start_month: 4, + period_start_day: 15, + period_end_month: 10, + period_end_day: 15, + norms: { + klei: { standard: 310, nv_area: 248 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 250, nv_area: 200 }, + loess: { standard: 250, nv_area: 200 }, + veen: { standard: 265, nv_area: 212 }, + }, + }, + { + period_description: "vanaf 15 mei tot minstens 15 oktober", + period_start_month: 5, + period_start_day: 15, + period_end_month: 10, + period_end_day: 15, + norms: { + klei: { standard: 280, nv_area: 224 }, + zand_nwc: { standard: 235, nv_area: 188 }, + zand_zuid: { standard: 235, nv_area: 188 }, + loess: { standard: 235, nv_area: 188 }, + veen: { standard: 235, nv_area: 188 }, + }, + }, + { + period_description: "vanaf 15 augustus tot minstens 15 oktober", + period_start_month: 8, + period_start_day: 15, + period_end_month: 10, + period_end_day: 15, + norms: { + klei: { standard: 95, nv_area: 76 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + period_description: + "vanaf 15 september tot minstens 15 oktober", + period_start_month: 9, + period_start_day: 15, + period_end_month: 10, + period_end_day: 15, + norms: { + klei: { standard: 30, nv_area: 24 }, + zand_nwc: { standard: 25, nv_area: 20 }, + zand_zuid: { standard: 25, nv_area: 20 }, + loess: { standard: 25, nv_area: 20 }, + veen: { standard: 25, nv_area: 20 }, + }, + }, + { + period_description: "vanaf 15 oktober", + period_start_month: 10, + period_start_day: 15, + period_end_month: 12, + period_end_day: 31, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewas, consumptie aardappelen", + b_lu_catalogue_match: ["nl_2014"], + type: "aardappel", + is_winterteelt: false, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "hoge norm", + varieties: [ + "Adora", + "Annabelle", + "Bintje", + "Carlita", + "Courage", + "Draga", + "Felsina", + "Fontane", + "Innovator", + "Inova", + "Jaerla", + "Lady Blanca", + "Lady Olympia", + "Lady Rosetta", + "Liseta", + "Maritiema", + "Marlen", + "Miranda", + "Ramos", + "Redstar", + "Sante", + "Satellite", + "Victoria", + "VR 808", + "Zorba", + ], + norms: { + klei: { standard: 275, nv_area: 220 }, + zand_nwc: { standard: 260, nv_area: 208 }, + zand_zuid: { standard: 208, nv_area: 166 }, + loess: { standard: 204, nv_area: 163 }, + veen: { standard: 270, nv_area: 216 }, + }, + }, + { + omschrijving: "lage norm", + varieties: [ + "Agria", + "Allure", + "Alpha", + "Aprilia", + "Asterix", + "Aziza", + "Ballys", + "Baraka", + "Bartina", + "Ceasar", + "Dore", + "Eigenheimer", + "El Paso", + "Futura", + "Gloria", + "Irene", + "Maradonna", + "Markies", + "Milva", + "Minerva", + "Mondial", + "Morene", + "Mozart", + "Producent", + "Remarka", + "Rodeo", + "Safari", + "Saphire", + "Simply Red", + "Spirit", + "Terra Gold", + "Ukama", + "Vision", + ], + norms: { + klei: { standard: 225, nv_area: 180 }, + zand_nwc: { standard: 210, nv_area: 168 }, + zand_zuid: { standard: 168, nv_area: 134 }, + loess: { standard: 164, nv_area: 131 }, + veen: { standard: 220, nv_area: 176 }, + }, + }, + { + omschrijving: "overig", + norms: { + klei: { standard: 250, nv_area: 200 }, + zand_nwc: { standard: 235, nv_area: 188 }, + zand_zuid: { standard: 188, nv_area: 150 }, + loess: { standard: 184, nv_area: 147 }, + veen: { standard: 245, nv_area: 196 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: + "Consumptieaardappel, vroeg (loofvernietiging voor 15 juli)", + b_lu_catalogue_match: ["nl_1911", "nl_1912"], + type: "aardappel", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 96, nv_area: 77 }, + loess: { standard: 96, nv_area: 77 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewas, pootaardappelen", + b_lu_catalogue_match: ["nl_2015", "nl_2016"], + type: "aardappel", + is_winterteelt: false, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "hoge norm", + varieties: [ + "Adora", + "Agata", + "Annabelle", + "Arinda", + "Berber", + "Binella", + "Climax", + "Donald", + "Elisabeth", + "Fontane", + "Gloria", + "Inova", + "Jaerla", + "Junior", + "Lady Rosetta", + "Lady Olympia", + "Leyla", + "Linzer Delikatess", + "Miriam", + "Orinana", + "Premiere", + "Primura", + "Prior", + "Rikea", + "Romano", + "Satellite", + "Sirco", + "Sirtema", + "Tresor", + "Ukama", + ], + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + omschrijving: "lage norm", + varieties: [ + "Arcade", + "Astarte", + "Asterix", + "Baraka", + "Bartina", + "Diamant", + "Dolce Vita", + "Elles", + "Elvira", + "Everest", + "Florijn", + "Kardal", + "Karnico", + "Maradonna", + "Mondial", + "Morene", + "Mozart", + "Picasso", + "Remarka", + "Resonant", + "Rodeo", + "Saphire", + "Sifra", + "Simply Red", + "Spirit", + "Van Gogh", + "Vebesta", + "Vento", + "Voyager", + ], + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 100, nv_area: 80 }, + zand_zuid: { standard: 100, nv_area: 80 }, + loess: { standard: 100, nv_area: 80 }, + veen: { standard: 100, nv_area: 80 }, + }, + }, + { + omschrijving: "overig", + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: + "Pootaardappelen, uitgroeiteelt (loofvernietiging na 15 augustus)", + b_lu_catalogue_match: [ + "nl_1928", + "nl_1929", + "nl_2015", + "nl_2016", + "nl_3730", + "nl_3731", + ], + type: "aardappel", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewas, zetmeelaardappelen", + b_lu_catalogue_match: ["nl_2017"], + type: "aardappel", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 240, nv_area: 192 }, + zand_nwc: { standard: 230, nv_area: 184 }, + zand_zuid: { standard: 184, nv_area: 147 }, + loess: { standard: 184, nv_area: 147 }, + veen: { standard: 230, nv_area: 184 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, suikerbieten", + b_lu_catalogue_match: ["nl_256"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 145, nv_area: 116 }, + zand_zuid: { standard: 116, nv_area: 93 }, + loess: { standard: 116, nv_area: 93 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Cichorei", + b_lu_catalogue_match: ["nl_511", "nl_1023", "nl_1024", "nl_1036"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 70, nv_area: 56 }, + zand_nwc: { standard: 70, nv_area: 56 }, + zand_zuid: { standard: 70, nv_area: 56 }, + loess: { standard: 70, nv_area: 56 }, + veen: { standard: 70, nv_area: 56 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, voederbieten", + b_lu_catalogue_match: ["nl_257"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 165, nv_area: 132 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 132, nv_area: 106 }, + loess: { standard: 132, nv_area: 106 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, wintertarwe", + b_lu_catalogue_match: ["nl_233", "nl_382"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 245, nv_area: 196 }, + zand_nwc: { standard: 160, nv_area: 128 }, + zand_zuid: { standard: 160, nv_area: 128 }, + loess: { standard: 190, nv_area: 152 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, zomertarwe", + b_lu_catalogue_match: ["nl_234"], + type: "akkerbouw", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, wintergerst", + b_lu_catalogue_match: ["nl_235"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, zomergerst", + b_lu_catalogue_match: ["nl_236", "nl_381"], + type: "akkerbouw", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 80, nv_area: 64 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen Triticale", + b_lu_catalogue_match: ["nl_314"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 160, nv_area: 128 }, + zand_nwc: { standard: 150, nv_area: 120 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, winterrogge", + b_lu_catalogue_match: ["nl_237", "nl_6806"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Haver", + b_lu_catalogue_match: ["nl_238", "nl_670", "nl_6636"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 100, nv_area: 80 }, + zand_zuid: { standard: 100, nv_area: 80 }, + loess: { standard: 100, nv_area: 80 }, + veen: { standard: 100, nv_area: 80 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, mais", + b_lu_catalogue_match: [ + "nl_259", + "nl_316", + "nl_317", + "nl_1935", + "nl_2032", + ], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "derogatie", + norms: { + klei: { standard: 160, nv_area: 128 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + { + omschrijving: "non-derogatie", + norms: { + klei: { standard: 185, nv_area: 148 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Luzerne", + b_lu_catalogue_match: ["nl_258", "nl_663", "nl_1926", "nl_1949"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + sub_types: [ + { + omschrijving: "eerste jaar", + norms: { + klei: { standard: 40, nv_area: 32 }, + zand_nwc: { standard: 40, nv_area: 32 }, + zand_zuid: { standard: 40, nv_area: 32 }, + loess: { standard: 40, nv_area: 32 }, + veen: { standard: 40, nv_area: 32 }, + }, + }, + { + omschrijving: "volgende jaren", + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: + "Akkerbouwgewassen, Gras voor industriƫle verwerking", + b_lu_catalogue_match: ["nl_3805"], + type: "gras", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "inzaai in september en eerste jaar", + norms: { + klei: { standard: 30, nv_area: 24 }, + zand_nwc: { standard: 25, nv_area: 20 }, + zand_zuid: { standard: 25, nv_area: 20 }, + loess: { standard: 25, nv_area: 20 }, + veen: { standard: 25, nv_area: 20 }, + }, + }, + { + omschrijving: "inzaai voor 15 mei en volgende jaren", + norms: { + klei: { standard: 310, nv_area: 248 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 250, nv_area: 200 }, + loess: { standard: 250, nv_area: 200 }, + veen: { standard: 265, nv_area: 212 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Graszaad, Engels raaigras", + b_lu_catalogue_match: [ + "nl_1919", + "nl_2030", + "nl_2031", + "nl_3506", + "nl_6750", + "nl_6751", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + sub_types: [ + { + omschrijving: "1e jaars", + norms: { + klei: { standard: 165, nv_area: 132 }, + zand_nwc: { standard: 150, nv_area: 120 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 155, nv_area: 124 }, + }, + }, + { + omschrijving: "overjarig", + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Rietzwenkgras", + b_lu_catalogue_match: [ + "nl_1915", + "nl_3516", + "nl_3807", + "nl_6782", + "nl_6783", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 130, nv_area: 104 }, + zand_zuid: { standard: 104, nv_area: 83 }, + loess: { standard: 104, nv_area: 83 }, + veen: { standard: 135, nv_area: 108 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Rietzwenkgras, volgteelt", + b_lu_catalogue_match: [ + "nl_1915", + "nl_3516", + "nl_3807", + "nl_6782", + "nl_6783", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 60, nv_area: 48 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 40, nv_area: 32 }, + loess: { standard: 40, nv_area: 32 }, + veen: { standard: 55, nv_area: 44 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen,Veldbeemdgras", + b_lu_catalogue_match: [ + "nl_1916", + "nl_3523", + "nl_6746", + "nl_6788", + "nl_6789", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 130, nv_area: 104 }, + zand_nwc: { standard: 110, nv_area: 80 }, + zand_zuid: { standard: 100, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen,Veldbeemdgras, volgteelt", + b_lu_catalogue_match: [ + "nl_1916", + "nl_3523", + "nl_6746", + "nl_6788", + "nl_6789", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 60, nv_area: 48 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 40, nv_area: 32 }, + loess: { standard: 40, nv_area: 32 }, + veen: { standard: 55, nv_area: 44 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Roodzwenkgras", + b_lu_catalogue_match: [ + "nl_1917", + "nl_1918", + "nl_3808", + "nl_6784", + "nl_6785", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + sub_types: [ + { + omschrijving: "1e jaars", + norms: { + klei: { standard: 85, nv_area: 68 }, + zand_nwc: { standard: 75, nv_area: 60 }, + zand_zuid: { standard: 60, nv_area: 48 }, + loess: { standard: 60, nv_area: 48 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + omschrijving: "overjarig", + norms: { + klei: { standard: 115, nv_area: 92 }, + zand_nwc: { standard: 105, nv_area: 84 }, + zand_zuid: { standard: 84, nv_area: 67 }, + loess: { standard: 84, nv_area: 67 }, + veen: { standard: 110, nv_area: 88 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Roodzwenkgras, volgteelt", + b_lu_catalogue_match: [ + "nl_1917", + "nl_1918", + "nl_3808", + "nl_6784", + "nl_6785", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + sub_types: [ + { + omschrijving: "1e jaars", + norms: { + klei: { standard: 35, nv_area: 28 }, + zand_nwc: { standard: 35, nv_area: 28 }, + zand_zuid: { standard: 28, nv_area: 22 }, + loess: { standard: 28, nv_area: 22 }, + veen: { standard: 35, nv_area: 28 }, + }, + }, + { + omschrijving: "overjarig", + norms: { + klei: { standard: 45, nv_area: 36 }, + zand_nwc: { standard: 45, nv_area: 36 }, + zand_zuid: { standard: 36, nv_area: 29 }, + loess: { standard: 36, nv_area: 29 }, + veen: { standard: 45, nv_area: 36 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Graszaad,Westerwolds", + b_lu_catalogue_match: ["nl_1919", "nl_3513", "nl_6790", "nl_6791"], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 100, nv_area: 80 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Graszaad, Italiaans", + b_lu_catalogue_match: ["nl_1920", "nl_3512", "nl_6754", "nl_6755"], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 130, nv_area: 104 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 96, nv_area: 77 }, + loess: { standard: 96, nv_area: 77 }, + veen: { standard: 125, nv_area: 100 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, graszaad overig", + b_lu_catalogue_match: [ + "nl_383", + "nl_1914", + "nl_6768", + "nl_1034", + "nl_1035", + "nl_6752", + ], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 64, nv_area: 51 }, + loess: { standard: 64, nv_area: 51 }, + veen: { standard: 85, nv_area: 68 }, + }, + }, + { + cultivation_rvo_table2: + "Akkerbouwgewassen, graszaad, overig, vanggewas", + b_lu_catalogue_match: ["nl_6753"], + type: "graszaad", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 45, nv_area: 36 }, + zand_nwc: { standard: 45, nv_area: 36 }, + zand_zuid: { standard: 36, nv_area: 29 }, + loess: { standard: 36, nv_area: 29 }, + veen: { standard: 45, nv_area: 36 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, graszoden", + b_lu_catalogue_match: ["nl_1921"], + type: "gras", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 340, nv_area: 272 }, + zand_nwc: { standard: 340, nv_area: 272 }, + zand_zuid: { standard: 272, nv_area: 218 }, + loess: { standard: 272, nv_area: 218 }, + veen: { standard: 340, nv_area: 272 }, + }, + }, + { + cultivation_rvo_table2: + "Akkerbouwgewassen, Ui overig, zaaiui of winterui.", + b_lu_catalogue_match: ["nl_6660", "nl_6664", "nl_1932", "nl_1933"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Ui overig", + b_lu_catalogue_match: ["nl_263", "nl_1934", "nl_1021"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Blauwmaanzaad", + b_lu_catalogue_match: ["nl_247"], + type: "akkerbouw", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 100, nv_area: 80 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Karwij", + b_lu_catalogue_match: ["nl_246"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, koolzaad", + b_lu_catalogue_match: ["nl_1922", "nl_1923"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "winter", + norms: { + klei: { standard: 205, nv_area: 164 }, + zand_nwc: { standard: 190, nv_area: 152 }, + zand_zuid: { standard: 152, nv_area: 122 }, + loess: { standard: 152, nv_area: 122 }, + veen: { standard: 195, nv_area: 156 }, + }, + }, + { + omschrijving: "zomer", + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 96, nv_area: 77 }, + loess: { standard: 96, nv_area: 77 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Vlas", + b_lu_catalogue_match: ["nl_249", "nl_666", "nl_3736"], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 70, nv_area: 56 }, + zand_nwc: { standard: 70, nv_area: 56 }, + zand_zuid: { standard: 56, nv_area: 45 }, + loess: { standard: 56, nv_area: 45 }, + veen: { standard: 70, nv_area: 56 }, + }, + }, + { + cultivation_rvo_table2: "Akkerbouwgewassen, Akkerbouwgewassen, overig", + b_lu_catalogue_match: [ + "nl_1927", + "nl_2652", + "nl_375", + "nl_516", + "nl_944", + "nl_3510", + "nl_6802", + ], + type: "akkerbouw", + is_winterteelt: true, + is_vanggewas: true, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Bladgewassen, Spinazie", + b_lu_catalogue_match: ["nl_2773", "nl_2774"], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "1e teelt", + norms: { + klei: { standard: 260, nv_area: 208 }, + zand_nwc: { standard: 190, nv_area: 152 }, + zand_zuid: { standard: 152, nv_area: 122 }, + loess: { standard: 152, nv_area: 122 }, + veen: { standard: 200, nv_area: 160 }, + }, + }, + { + omschrijving: "volgteelt", + norms: { + klei: { standard: 185, nv_area: 148 }, + zand_nwc: { standard: 145, nv_area: 116 }, + zand_zuid: { standard: 116, nv_area: 93 }, + loess: { standard: 116, nv_area: 93 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Baldgewassen, Spinazie volgteelt", + b_lu_catalogue_match: ["nl_1022"], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 185, nv_area: 148 }, + zand_nwc: { standard: 145, nv_area: 116 }, + zand_zuid: { standard: 116, nv_area: 93 }, + loess: { standard: 116, nv_area: 93 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + { + cultivation_rvo_table2: "Bladgewassen, Slasoorten", + b_lu_catalogue_match: [ + "nl_2767", + "nl_2768", + "nl_2769", + "nl_2770", + "nl_2771", + "nl_2772", + "nl_2766", + ], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "1e teelt", + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 132, nv_area: 106 }, + loess: { standard: 132, nv_area: 106 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + omschrijving: "volgteelt", + norms: { + klei: { standard: 105, nv_area: 84 }, + zand_nwc: { standard: 105, nv_area: 84 }, + zand_zuid: { standard: 84, nv_area: 67 }, + loess: { standard: 84, nv_area: 67 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Bladgewassen, Andijvie eerste teelt", + b_lu_catalogue_match: ["nl_2709"], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 170, nv_area: 136 }, + zand_zuid: { standard: 136, nv_area: 109 }, + loess: { standard: 136, nv_area: 109 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: "Bladgewassen, Andijvie eerste teelt volgteelt", + b_lu_catalogue_match: ["nl_2708"], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "1e teelt", + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 170, nv_area: 136 }, + zand_zuid: { standard: 136, nv_area: 109 }, + loess: { standard: 136, nv_area: 109 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + omschrijving: "volgteelt", + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 90, nv_area: 72 }, + zand_zuid: { standard: 72, nv_area: 58 }, + loess: { standard: 72, nv_area: 58 }, + veen: { standard: 90, nv_area: 72 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Bladgewassen, Selderij, bleek/groen", + b_lu_catalogue_match: ["nl_2765"], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Bladgewassen, Prei", + b_lu_catalogue_match: [ + "nl_2749", + "nl_2750", + "nl_2799", + "nl_2800", + "nl_2801", + "nl_2802", + ], + type: "bladgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 245, nv_area: 196 }, + zand_nwc: { standard: 225, nv_area: 180 }, + zand_zuid: { standard: 180, nv_area: 144 }, + loess: { standard: 180, nv_area: 144 }, + veen: { standard: 235, nv_area: 188 }, + }, + }, + { + cultivation_rvo_table2: "Bladgewassen, Bladgewassen overig", + b_lu_catalogue_match: ["nl_2791", "nl_2792"], + type: "bladgewas", + is_winterteelt: false, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "eenmalige oogst", + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + omschrijving: "meermalige oogst", + norms: { + klei: { standard: 275, nv_area: 220 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 200, nv_area: 160 }, + loess: { standard: 200, nv_area: 160 }, + veen: { standard: 260, nv_area: 208 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Koolgewassen, Spruitkool", + b_lu_catalogue_match: ["nl_2777", "nl_2778"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 290, nv_area: 232 }, + zand_nwc: { standard: 265, nv_area: 212 }, + zand_zuid: { standard: 212, nv_area: 170 }, + loess: { standard: 212, nv_area: 170 }, + veen: { standard: 275, nv_area: 220 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Witte Kool", + b_lu_catalogue_match: ["nl_2789", "nl_2790"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 320, nv_area: 256 }, + zand_nwc: { standard: 290, nv_area: 232 }, + zand_zuid: { standard: 232, nv_area: 186 }, + loess: { standard: 232, nv_area: 186 }, + veen: { standard: 305, nv_area: 244 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Rode kool", + b_lu_catalogue_match: ["nl_2759", "nl_2760"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 285, nv_area: 228 }, + zand_nwc: { standard: 260, nv_area: 208 }, + zand_zuid: { standard: 208, nv_area: 166 }, + loess: { standard: 208, nv_area: 166 }, + veen: { standard: 270, nv_area: 216 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Savooiekool", + b_lu_catalogue_match: ["nl_2761", "nl_2762"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 285, nv_area: 228 }, + zand_nwc: { standard: 260, nv_area: 208 }, + zand_zuid: { standard: 208, nv_area: 166 }, + loess: { standard: 208, nv_area: 166 }, + veen: { standard: 270, nv_area: 216 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Spitskool", + b_lu_catalogue_match: ["nl_2775", "nl_2776"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 285, nv_area: 228 }, + zand_nwc: { standard: 260, nv_area: 208 }, + zand_zuid: { standard: 208, nv_area: 166 }, + loess: { standard: 208, nv_area: 166 }, + veen: { standard: 270, nv_area: 216 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Bloemkool", + b_lu_catalogue_match: [ + "nl_2713", + "nl_2714", + "nl_2795", + "nl_2796", + "nl_2797", + "nl_2798", + ], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 230, nv_area: 184 }, + zand_nwc: { standard: 210, nv_area: 168 }, + zand_zuid: { standard: 168, nv_area: 134 }, + loess: { standard: 168, nv_area: 134 }, + veen: { standard: 220, nv_area: 176 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Broccoli", + b_lu_catalogue_match: ["nl_2719", "nl_2720"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 270, nv_area: 216 }, + zand_nwc: { standard: 235, nv_area: 188 }, + zand_zuid: { standard: 188, nv_area: 150 }, + loess: { standard: 188, nv_area: 150 }, + veen: { standard: 245, nv_area: 196 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Chinese kool", + b_lu_catalogue_match: ["nl_2721", "nl_2722"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 155, nv_area: 124 }, + zand_zuid: { standard: 124, nv_area: 99 }, + loess: { standard: 124, nv_area: 99 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, boerenkool", + b_lu_catalogue_match: ["nl_2715", "nl_2716"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 155, nv_area: 124 }, + zand_zuid: { standard: 124, nv_area: 99 }, + loess: { standard: 124, nv_area: 99 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Paksoi", + b_lu_catalogue_match: ["nl_2745", "nl_2746"], + type: "koolgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 132, nv_area: 106 }, + loess: { standard: 132, nv_area: 106 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: "Koolgewassen, Raapstelen", + b_lu_catalogue_match: ["nl_2753", "nl_2754", "nl_664"], + type: "koolgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 130, nv_area: 104 }, + zand_zuid: { standard: 104, nv_area: 83 }, + loess: { standard: 104, nv_area: 83 }, + veen: { standard: 135, nv_area: 108 }, + }, + }, + { + cultivation_rvo_table2: "Kruiden", + b_lu_catalogue_match: ["nl_2743", "nl_2744"], + type: "kruiden", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "bladgewas, eenmalige oogst", + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + omschrijving: "bladgewas, meermalig oogsten", + norms: { + klei: { standard: 275, nv_area: 220 }, + zand_nwc: { standard: 250, nv_area: 200 }, + zand_zuid: { standard: 200, nv_area: 160 }, + loess: { standard: 200, nv_area: 160 }, + veen: { standard: 260, nv_area: 208 }, + }, + }, + { + omschrijving: "wortelgewassen", + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + omschrijving: "zaadgewassen", + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 90, nv_area: 72 }, + zand_zuid: { standard: 72, nv_area: 58 }, + loess: { standard: 72, nv_area: 58 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Kruiden, bladgewas, eemalige oogst", + b_lu_catalogue_match: [ + "nl_1019", + "nl_1020", + "nl_1028", + "nl_1029", + "nl_1030", + "nl_1031", + "nl_1032", + "nl_1037", + "nl_1038", + "nl_654", + "nl_6749", + ], + type: "kruiden", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 112, nv_area: 90 }, + loess: { standard: 112, nv_area: 90 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + cultivation_rvo_table2: "Kruiden, wortelgewassen", + b_lu_catalogue_match: ["nl_652"], + type: "kruiden", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Kruiden, zaadgewassen", + b_lu_catalogue_match: ["nl_655"], + type: "kruiden", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 90, nv_area: 72 }, + zand_zuid: { standard: 72, nv_area: 58 }, + loess: { standard: 72, nv_area: 58 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: + "Vruchtgewassen, Aardbei (wachtbed, vermeerdering)", + b_lu_catalogue_match: ["nl_2700", "nl_2701"], + type: "vruchtgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 110, nv_area: 88 }, + zand_zuid: { standard: 88, nv_area: 70 }, + loess: { standard: 88, nv_area: 70 }, + veen: { standard: 115, nv_area: 92 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, Aardbei (productie)", + b_lu_catalogue_match: ["nl_2702"], + type: "vruchtgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 155, nv_area: 124 }, + zand_zuid: { standard: 124, nv_area: 99 }, + loess: { standard: 124, nv_area: 99 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + cultivation_rvo_table2: + "Vruchtgewassen, Komkommerachtigen (augurk, courgette, meloen, pompoen)", + b_lu_catalogue_match: [ + "nl_2723", + "nl_2724", + "nl_2729", + "nl_2730", + "nl_2731", + "nl_2732", + "nl_2733", + "nl_2734", + "nl_2735", + "nl_2736", + ], + type: "vruchtgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 190, nv_area: 152 }, + zand_nwc: { standard: 175, nv_area: 140 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 180, nv_area: 144 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, suikermais", + b_lu_catalogue_match: ["nl_814"], + type: "vruchtgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, stam/stokboon", + b_lu_catalogue_match: ["nl_2779", "nl_2780", "nl_2781", "nl_2782"], + type: "vruchtgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 110, nv_area: 88 }, + zand_zuid: { standard: 88, nv_area: 70 }, + loess: { standard: 88, nv_area: 70 }, + veen: { standard: 115, nv_area: 92 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, Landbouwstambonen, rijp zaad", + b_lu_catalogue_match: ["nl_2751", "nl_2752"], + type: "vruchtgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 135, nv_area: 108 }, + zand_nwc: { standard: 135, nv_area: 108 }, + zand_zuid: { standard: 108, nv_area: 86 }, + loess: { standard: 108, nv_area: 86 }, + veen: { standard: 135, nv_area: 108 }, + }, + }, + { + cultivation_rvo_table2: + "Vruchtgewassen, Veld- en tuinbonen, vers + rijp zaad", + b_lu_catalogue_match: [ + "nl_311", + "nl_665", + "nl_853", + "nl_854", + "nl_6767", + ], + type: "vruchtgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 50, nv_area: 40 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 40, nv_area: 32 }, + loess: { standard: 40, nv_area: 32 }, + veen: { standard: 50, nv_area: 40 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, Tuinbonen, vers/peulen", + b_lu_catalogue_match: ["nl_242"], + type: "vruchtgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 75, nv_area: 60 }, + zand_nwc: { standard: 75, nv_area: 60 }, + zand_zuid: { standard: 75, nv_area: 60 }, + loess: { standard: 75, nv_area: 60 }, + veen: { standard: 75, nv_area: 60 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, Erwt, vers + rijp zaad", + b_lu_catalogue_match: ["nl_241", "nl_244", "nl_308"], + type: "vruchtgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 30, nv_area: 24 }, + zand_nwc: { standard: 30, nv_area: 24 }, + zand_zuid: { standard: 30, nv_area: 24 }, + loess: { standard: 30, nv_area: 24 }, + veen: { standard: 30, nv_area: 24 }, + }, + }, + { + cultivation_rvo_table2: "Vruchtgewassen, Peul", + b_lu_catalogue_match: ["nl_2747", "nl_2748"], + type: "vruchtgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 85, nv_area: 68 }, + zand_zuid: { standard: 68, nv_area: 54 }, + loess: { standard: 68, nv_area: 54 }, + veen: { standard: 85, nv_area: 68 }, + }, + }, + { + cultivation_rvo_table2: + "Stengel/knol/wortelgewassen, Asperges (excl. Opkweek)", + b_lu_catalogue_match: ["nl_2710"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 85, nv_area: 68 }, + zand_nwc: { standard: 75, nv_area: 60 }, + zand_zuid: { standard: 60, nv_area: 48 }, + loess: { standard: 60, nv_area: 48 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Knolselderij", + b_lu_catalogue_match: ["nl_2725", "nl_2726"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: + "Stengel/knol/wortelgewassen, Knolvenkel/venkel", + b_lu_catalogue_match: ["nl_2727", "nl_2728"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 132, nv_area: 106 }, + loess: { standard: 132, nv_area: 106 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Koolraap", + b_lu_catalogue_match: ["nl_2737", "nl_2738"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 155, nv_area: 124 }, + zand_zuid: { standard: 124, nv_area: 99 }, + loess: { standard: 124, nv_area: 99 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Koolrabi", + b_lu_catalogue_match: ["nl_2739", "nl_2740"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 180, nv_area: 144 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 132, nv_area: 106 }, + loess: { standard: 132, nv_area: 106 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: + "Stengel/knol/wortelgewassen, Kroten/rode bieten", + b_lu_catalogue_match: ["nl_2741", "nl_2742"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 185, nv_area: 148 }, + zand_nwc: { standard: 170, nv_area: 136 }, + zand_zuid: { standard: 136, nv_area: 109 }, + loess: { standard: 136, nv_area: 109 }, + veen: { standard: 175, nv_area: 140 }, + }, + }, + { + cultivation_rvo_table2: + "Stengel/knol/wortelgewassen, winterpeen/waspeen", + b_lu_catalogue_match: ["nl_2783", "nl_2784", "nl_2785", "nl_2786"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 110, nv_area: 88 }, + zand_zuid: { standard: 110, nv_area: 88 }, + loess: { standard: 110, nv_area: 88 }, + veen: { standard: 110, nv_area: 88 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, bospeen", + b_lu_catalogue_match: ["nl_2717", "nl_2718"], + type: "stengel_knol_wortelgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 50, nv_area: 40 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 50, nv_area: 40 }, + loess: { standard: 50, nv_area: 40 }, + veen: { standard: 50, nv_area: 40 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Rabarber", + b_lu_catalogue_match: ["nl_2755", "nl_2756"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 250, nv_area: 200 }, + zand_nwc: { standard: 230, nv_area: 184 }, + zand_zuid: { standard: 184, nv_area: 147 }, + loess: { standard: 184, nv_area: 147 }, + veen: { standard: 240, nv_area: 192 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Radijs", + b_lu_catalogue_match: ["nl_2757", "nl_2758"], + type: "stengel_knol_wortelgewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 80, nv_area: 64 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 64, nv_area: 51 }, + loess: { standard: 64, nv_area: 51 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Schorseneren", + b_lu_catalogue_match: ["nl_2763", "nl_2764"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 170, nv_area: 136 }, + zand_zuid: { standard: 170, nv_area: 136 }, + loess: { standard: 170, nv_area: 136 }, + veen: { standard: 170, nv_area: 136 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, Witlof", + b_lu_catalogue_match: ["nl_2787", "nl_2788"], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 100, nv_area: 80 }, + zand_zuid: { standard: 100, nv_area: 80 }, + loess: { standard: 100, nv_area: 80 }, + veen: { standard: 100, nv_area: 80 }, + }, + }, + { + cultivation_rvo_table2: "Stengel/knol/wortelgewassen, overig", + b_lu_catalogue_match: [ + "nl_2793", + "nl_2794", + "nl_1949", + "nl_2711", + "nl_2712", + "nl_3521", + "nl_6632", + ], + type: "stengel_knol_wortelgewas", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 185, nv_area: 148 }, + zand_zuid: { standard: 148, nv_area: 118 }, + loess: { standard: 148, nv_area: 118 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Groenbemesters, niet-vlinderbloemige", + b_lu_catalogue_match: [ + "nl_427", + "nl_428", + "nl_3502", + "nl_3503", + "nl_3504", + "nl_3505", + "nl_3507", + "nl_3508", + "nl_3514", + "nl_3517", + "nl_3519", + "nl_3520", + ], + type: "groenbemester", + is_vanggewas: true, + is_winterteelt: false, + norms: { + klei: { standard: 60, nv_area: 48 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 50, nv_area: 40 }, + loess: { standard: 50, nv_area: 40 }, + veen: { standard: 60, nv_area: 48 }, + }, + }, + { + cultivation_rvo_table2: "Groenbemesters, vlinderbloemige", + b_lu_catalogue_match: [ + "nl_426", + "nl_6763", + "nl_6762", + "nl_800", + "nl_801", + "nl_802", + "nl_803", + "nl_6757", + "nl_6756", + "nl_6759", + "nl_6758", + "nl_6761", + "nl_6760", + "nl_3518", + "nl_6765", + "nl_6764", + "nl_6769", + ], + type: "groenbemester", + is_vanggewas: true, + is_winterteelt: false, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + { + cultivation_rvo_table2: + "Graszaadstoppel ter vernietiging in najaar of vroege voorjaar", + b_lu_catalogue_match: [], + type: "groenbemester", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 60, nv_area: 48 }, + zand_nwc: { standard: 50, nv_area: 40 }, + zand_zuid: { standard: 50, nv_area: 40 }, + loess: { standard: 50, nv_area: 40 }, + veen: { standard: 60, nv_area: 48 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Acidanthera", + b_lu_catalogue_match: [], + type: "bloembol", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 255, nv_area: 204 }, + zand_nwc: { standard: 240, nv_area: 192 }, + zand_zuid: { standard: 240, nv_area: 192 }, + loess: { standard: 240, nv_area: 192 }, + veen: { standard: 240, nv_area: 192 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Anemone coronaria", + b_lu_catalogue_match: [], + type: "bloembol", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 130, nv_area: 104 }, + zand_nwc: { standard: 125, nv_area: 100 }, + zand_zuid: { standard: 125, nv_area: 100 }, + loess: { standard: 125, nv_area: 100 }, + veen: { standard: 125, nv_area: 100 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Fritillaria imperialis", + b_lu_catalogue_match: [], + type: "bloembol", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 135, nv_area: 108 }, + zand_nwc: { standard: 130, nv_area: 104 }, + zand_zuid: { standard: 130, nv_area: 104 }, + loess: { standard: 130, nv_area: 104 }, + veen: { standard: 130, nv_area: 104 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Hyacint", + b_lu_catalogue_match: [ + "nl_970", + "nl_971", + "nl_999", + "nl_1016", + "nl_1017", + "nl_1018", + ], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 220, nv_area: 176 }, + zand_nwc: { standard: 210, nv_area: 168 }, + zand_zuid: { standard: 210, nv_area: 168 }, + loess: { standard: 210, nv_area: 168 }, + veen: { standard: 210, nv_area: 168 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Iris", + b_lu_catalogue_match: [ + "nl_973", + "nl_974", + "nl_1000", + "nl_1051", + "nl_1052", + ], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "grofbollig", + norms: { + klei: { standard: 170, nv_area: 136 }, + zand_nwc: { standard: 160, nv_area: 128 }, + zand_zuid: { standard: 160, nv_area: 128 }, + loess: { standard: 160, nv_area: 128 }, + veen: { standard: 160, nv_area: 128 }, + }, + }, + { + omschrijving: "fijnbollig", + norms: { + klei: { standard: 140, nv_area: 112 }, + zand_nwc: { standard: 135, nv_area: 108 }, + zand_zuid: { standard: 135, nv_area: 108 }, + loess: { standard: 135, nv_area: 108 }, + veen: { standard: 135, nv_area: 108 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Krokus", + b_lu_catalogue_match: ["nl_976", "nl_977", "nl_1001"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "grote gele", + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + omschrijving: "overig", + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 85, nv_area: 68 }, + zand_zuid: { standard: 85, nv_area: 68 }, + loess: { standard: 85, nv_area: 68 }, + veen: { standard: 85, nv_area: 68 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Narcis", + b_lu_catalogue_match: ["nl_982", "nl_983", "nl_1003"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 145, nv_area: 116 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Tulp", + b_lu_catalogue_match: ["nl_985", "nl_986", "nl_1004"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 190, nv_area: 152 }, + zand_zuid: { standard: 190, nv_area: 152 }, + loess: { standard: 190, nv_area: 152 }, + veen: { standard: 190, nv_area: 152 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Dahlia", + b_lu_catalogue_match: ["nl_964", "nl_965", "nl_997"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 105, nv_area: 84 }, + zand_zuid: { standard: 105, nv_area: 84 }, + loess: { standard: 105, nv_area: 84 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Gladiool", + b_lu_catalogue_match: ["nl_967", "nl_968", "nl_998"], + type: "bloembol", + is_winterteelt: false, + is_vanggewas: false, + sub_types: [ + { + omschrijving: "pitten", + norms: { + klei: { standard: 260, nv_area: 208 }, + zand_nwc: { standard: 245, nv_area: 196 }, + zand_zuid: { standard: 245, nv_area: 196 }, + loess: { standard: 245, nv_area: 196 }, + veen: { standard: 245, nv_area: 196 }, + }, + }, + { + omschrijving: "kralen", + norms: { + klei: { standard: 190, nv_area: 152 }, + zand_nwc: { standard: 180, nv_area: 144 }, + zand_zuid: { standard: 180, nv_area: 144 }, + loess: { standard: 180, nv_area: 144 }, + veen: { standard: 180, nv_area: 144 }, + }, + }, + ], + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Knolbegonia", + b_lu_catalogue_match: [], + type: "bloembol", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 145, nv_area: 116 }, + zand_zuid: { standard: 145, nv_area: 116 }, + loess: { standard: 145, nv_area: 116 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen,Lelie", + b_lu_catalogue_match: ["nl_979", "nl_980", "nl_1002"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 155, nv_area: 124 }, + zand_nwc: { standard: 145, nv_area: 116 }, + zand_zuid: { standard: 145, nv_area: 116 }, + loess: { standard: 145, nv_area: 116 }, + veen: { standard: 145, nv_area: 116 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, Zantedeschia", + b_lu_catalogue_match: ["nl_988", "nl_989", "nl_1005"], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 120, nv_area: 96 }, + zand_nwc: { standard: 120, nv_area: 96 }, + zand_zuid: { standard: 120, nv_area: 96 }, + loess: { standard: 120, nv_area: 96 }, + veen: { standard: 120, nv_area: 96 }, + }, + }, + { + cultivation_rvo_table2: "Bloembollengewassen, overig", + b_lu_catalogue_match: [ + "nl_176", + "nl_994", + "nl_995", + "nl_1006", + "nl_1007", + "nl_1010", + "nl_1011", + "nl_1012", + "nl_1013", + "nl_1014", + "nl_1015", + "nl_1025", + "nl_1026", + "nl_1027", + "nl_6795", + "nl_6796", + "nl_6797", + "nl_6803", + "nl_6804", + "nl_6805", + ], + type: "bloembol", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 165, nv_area: 132 }, + zand_nwc: { standard: 155, nv_area: 124 }, + zand_zuid: { standard: 155, nv_area: 124 }, + loess: { standard: 155, nv_area: 124 }, + veen: { standard: 155, nv_area: 124 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, Appel", + b_lu_catalogue_match: ["nl_1095", "nl_1096"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, blauwe bes", + b_lu_catalogue_match: ["nl_1869", "nl_1874", "nl_1047"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 95, nv_area: 76 }, + zand_zuid: { standard: 95, nv_area: 76 }, + loess: { standard: 95, nv_area: 76 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, braam", + b_lu_catalogue_match: ["nl_2327"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, framboos", + b_lu_catalogue_match: ["nl_2326"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, kers", + b_lu_catalogue_match: ["nl_1872", "nl_2328", "nl_1100", "nl_2645"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, Peer", + b_lu_catalogue_match: ["nl_1097", "nl_1098"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, Pruimen", + b_lu_catalogue_match: ["nl_1870"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, rode bes", + b_lu_catalogue_match: ["nl_2325"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 140, nv_area: 112 }, + zand_zuid: { standard: 140, nv_area: 112 }, + loess: { standard: 140, nv_area: 112 }, + veen: { standard: 140, nv_area: 112 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, wijnbouw", + b_lu_catalogue_match: ["nl_1099"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 100, nv_area: 80 }, + zand_nwc: { standard: 95, nv_area: 76 }, + zand_zuid: { standard: 95, nv_area: 76 }, + loess: { standard: 95, nv_area: 76 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: "Fruitteeltgewassen, zwarte bes", + b_lu_catalogue_match: ["nl_1873"], + type: "fruitteelt", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 165, nv_area: 132 }, + zand_zuid: { standard: 165, nv_area: 132 }, + loess: { standard: 165, nv_area: 132 }, + veen: { standard: 165, nv_area: 132 }, + }, + }, + { + cultivation_rvo_table2: "Buitenbloemen, hoog", + b_lu_catalogue_match: ["nl_1025", "nl_1026", "nl_1054"], + type: "buitenbloem", + is_winterteelt: true, + is_vanggewas: false, + varieties: [ + "Alchemilla mollis", + "Carthamus", + "Gypsophila paniculata", + "Lymonium", + "Lysimachia", + "Paeonia", + "Solidago", + "Veronica", + ], + norms: { + klei: { standard: 200, nv_area: 160 }, + zand_nwc: { standard: 200, nv_area: 160 }, + zand_zuid: { standard: 200, nv_area: 160 }, + loess: { standard: 200, nv_area: 160 }, + veen: { standard: 200, nv_area: 160 }, + }, + }, + { + cultivation_rvo_table2: "Buitenbloemen, overig", + b_lu_catalogue_match: [ + "nl_174", + "nl_515", + "nl_636", + "nl_637", + "nl_653", + "nl_656", + "nl_657", + "nl_991", + "nl_992", + "nl_1039", + "nl_1040", + "nl_1042", + "nl_1043", + "nl_1044", + "nl_1045", + "nl_1046", + "nl_1048", + "nl_1049", + "nl_1050", + "nl_1055", + ], + type: "buitenbloem", + is_winterteelt: true, + is_vanggewas: true, + varieties: [], + norms: { + klei: { standard: 150, nv_area: 120 }, + zand_nwc: { standard: 150, nv_area: 120 }, + zand_zuid: { standard: 150, nv_area: 120 }, + loess: { standard: 150, nv_area: 120 }, + veen: { standard: 150, nv_area: 120 }, + }, + }, + { + cultivation_rvo_table2: "Buitenbloemen, Tagetes", + b_lu_catalogue_match: ["nl_346", "nl_347", "nl_669", "nl_671"], + type: "buitenbloem", + is_vanggewas: true, + is_winterteelt: false, + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 90, nv_area: 72 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Laanbomen onderstammen", + b_lu_catalogue_match: ["nl_1070"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 40, nv_area: 32 }, + zand_nwc: { standard: 40, nv_area: 32 }, + zand_zuid: { standard: 40, nv_area: 32 }, + loess: { standard: 40, nv_area: 32 }, + veen: { standard: 40, nv_area: 32 }, + }, + }, + { + cultivation_rvo_table2: + "Boomkwekerijgewassen, Laanbomen/parkbomen, opzetters", + b_lu_catalogue_match: ["nl_1071", "nl_1072"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 115, nv_area: 92 }, + zand_nwc: { standard: 115, nv_area: 92 }, + zand_zuid: { standard: 115, nv_area: 92 }, + loess: { standard: 115, nv_area: 92 }, + veen: { standard: 115, nv_area: 92 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen,Sierheesters", + b_lu_catalogue_match: ["nl_1075"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 75, nv_area: 60 }, + zand_nwc: { standard: 75, nv_area: 60 }, + zand_zuid: { standard: 75, nv_area: 60 }, + loess: { standard: 75, nv_area: 60 }, + veen: { standard: 75, nv_area: 60 }, + }, + }, + { + cultivation_rvo_table2: + "Boomkwekerijgewassen, coniferen incl kerstsparren en dennen", + b_lu_catalogue_match: ["nl_796", "nl_1074"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 80, nv_area: 64 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Rozen", + b_lu_catalogue_match: ["nl_1073"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 70, nv_area: 56 }, + zand_nwc: { standard: 70, nv_area: 56 }, + zand_zuid: { standard: 70, nv_area: 56 }, + loess: { standard: 70, nv_area: 56 }, + veen: { standard: 70, nv_area: 56 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Bos- en haagplantsoen", + b_lu_catalogue_match: ["nl_1067", "nl_863"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 95, nv_area: 76 }, + zand_nwc: { standard: 95, nv_area: 76 }, + zand_zuid: { standard: 95, nv_area: 76 }, + loess: { standard: 95, nv_area: 76 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Vaste planten", + b_lu_catalogue_match: ["nl_1080"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 175, nv_area: 140 }, + zand_nwc: { standard: 175, nv_area: 140 }, + zand_zuid: { standard: 175, nv_area: 140 }, + loess: { standard: 175, nv_area: 140 }, + veen: { standard: 175, nv_area: 140 }, + }, + }, + { + cultivation_rvo_table2: + "Boomkwekerijgewassen, Vruchtbomen, onderstammen", + b_lu_catalogue_match: ["nl_1078"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 30, nv_area: 24 }, + zand_nwc: { standard: 30, nv_area: 24 }, + zand_zuid: { standard: 30, nv_area: 24 }, + loess: { standard: 30, nv_area: 24 }, + veen: { standard: 30, nv_area: 24 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, moerbomen", + b_lu_catalogue_match: ["nl_1077"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 110, nv_area: 88 }, + zand_nwc: { standard: 110, nv_area: 88 }, + zand_zuid: { standard: 110, nv_area: 88 }, + loess: { standard: 110, nv_area: 88 }, + veen: { standard: 110, nv_area: 88 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Vruchtbomen, overig,", + b_lu_catalogue_match: ["nl_1079"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 135, nv_area: 108 }, + zand_nwc: { standard: 105, nv_area: 84 }, + zand_zuid: { standard: 105, nv_area: 84 }, + loess: { standard: 105, nv_area: 84 }, + veen: { standard: 105, nv_area: 84 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Trek- en besheesters", + b_lu_catalogue_match: ["nl_1076"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 80, nv_area: 64 }, + zand_nwc: { standard: 80, nv_area: 64 }, + zand_zuid: { standard: 80, nv_area: 64 }, + loess: { standard: 80, nv_area: 64 }, + veen: { standard: 80, nv_area: 64 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Snijgroen", + b_lu_catalogue_match: ["nl_1876"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 95, nv_area: 76 }, + zand_nwc: { standard: 95, nv_area: 76 }, + zand_zuid: { standard: 95, nv_area: 76 }, + loess: { standard: 95, nv_area: 76 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Ericaceae", + b_lu_catalogue_match: ["nl_1069"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 70, nv_area: 56 }, + zand_nwc: { standard: 70, nv_area: 56 }, + zand_zuid: { standard: 70, nv_area: 56 }, + loess: { standard: 70, nv_area: 56 }, + veen: { standard: 70, nv_area: 56 }, + }, + }, + { + cultivation_rvo_table2: "Boomkwekerijgewassen, Buxus", + b_lu_catalogue_match: ["nl_1068"], + type: "boomkwekerij", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 95, nv_area: 76 }, + zand_nwc: { standard: 95, nv_area: 76 }, + zand_zuid: { standard: 95, nv_area: 76 }, + loess: { standard: 95, nv_area: 76 }, + veen: { standard: 95, nv_area: 76 }, + }, + }, + { + cultivation_rvo_table2: + "Boomkwekerijgewassen, Snelgroeiende houtsoorten voor biomassaproductie", + b_lu_catalogue_match: ["nl_864"], + type: "bosbouw", + is_winterteelt: true, + is_vanggewas: false, + norms: { + klei: { standard: 90, nv_area: 72 }, + zand_nwc: { standard: 90, nv_area: 72 }, + zand_zuid: { standard: 90, nv_area: 72 }, + loess: { standard: 90, nv_area: 72 }, + veen: { standard: 90, nv_area: 72 }, + }, + }, + { + cultivation_rvo_table2: + "Akkerbouwgewas, Aardappel, bestrijdingsmaatregel", + b_lu_catalogue_match: ["nl_2026"], + type: "aardappel", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + { + cultivation_rvo_table2: "Geen plaatsingsruimte", + b_lu_catalogue_match: [ + "nl_338", + "nl_1940", + "nl_2300", + "nl_3801", + "nl_3802", + "nl_6798", + "nl_6794", + "nl_6809", + "nl_6800", + "nl_6807", + ], + type: "Geen gewas", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + { + cultivation_rvo_table2: "Geen plaatsingsruimte", + b_lu_catalogue_match: [ + "nl_343", + "nl_794", + "nl_795", + "nl_2617", + "nl_2618", + "nl_2619", + "nl_2620", + "nl_2621", + "nl_2622", + "nl_2624", + "nl_2625", + "nl_2626", + "nl_2628", + "nl_2629", + "nl_2630", + "nl_2631", + "nl_2633", + "nl_2634", + "nl_2636", + "nl_2637", + "nl_2638", + "nl_2639", + "nl_2642", + "nl_6808", + "nl_6801", + "nl_6793", + ], + type: "Landschapselement", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, + { + cultivation_rvo_table2: "Geen plaatsingsruimte", + b_lu_catalogue_match: [ + "nl_6520", + "nl_6521", + "nl_6522", + "nl_6792", + "nl_6766", + "nl_6799", + ], + type: "Natte teelt", + is_winterteelt: false, + is_vanggewas: false, + norms: { + klei: { standard: 0, nv_area: 0 }, + zand_nwc: { standard: 0, nv_area: 0 }, + zand_zuid: { standard: 0, nv_area: 0 }, + loess: { standard: 0, nv_area: 0 }, + veen: { standard: 0, nv_area: 0 }, + }, + }, +] diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts new file mode 100644 index 000000000..e15fe3190 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts @@ -0,0 +1,558 @@ +import type { Field } from "@svenvw/fdm-core" +import { describe, expect, it } from "vitest" +import { calculateNL2026StikstofGebruiksNorm } from "./stikstofgebruiksnorm" +import type { NL2026NormsInput, NL2026NormsInputForCultivation } from "./types" + +describe(" calculateNL2026StikstofGebruiksNorm", () => { + it("should return the correct norm for grasland (beweiden)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_265", + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(345) + expect(result.normSource).toEqual("Grasland (beweiden).") + }) + + it("should return the correct norm for grasland (volledig maaien)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_265", + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(385) + expect(result.normSource).toEqual("Grasland (volledig maaien).") + }) + + it("should return the correct norm for potatoes", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2015", // Pootaardappel + b_lu_variety: "Adora", + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(140) + expect(result.normSource).toEqual( + "Akkerbouwgewas, pootaardappelen (hoge norm).", + ) + }) + + it("should apply 0 korting if winterteelt is present in zand_nwc region (hoofdteelt 2026)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_265", // Grasland (is_winterteelt: true) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + + // The base norm for Grasland in zand_nwc is 200 in nv-gebied. With winterteelt, korting should be 0. + expect(result.normValue).toBe(200) + expect(result.normSource).toEqual( + "Grasland (beweiden). Geen korting: winterteelt aanwezig", + ) + }) + + it("should apply 0 korting if vanggewas is present (sown <= Oct 1st)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) + b_lu_start: new Date(2024, 9, 1), // Oct 1st, 2024 + b_lu_end: new Date(2026, 1, 31), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in zand_nwc is 108. With vanggewas sown <= Oct 1st, korting should be 0. + expect(result.normValue).toBe(108) + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad. Geen korting: vanggewas gezaaid uiterlijk 1 oktober", + ) + }) + + it("should apply 5 korting if vanggewas is present (sown Oct 2nd - Oct 14th)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) + b_lu_start: new Date(2024, 9, 5), // Oct 5th, 2024 + b_lu_end: new Date(2026, 1, 31), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in zand_nwc in nv-gebied is 108. With vanggewas sown Oct 2-14, korting should be 5. + expect(result.normValue).toBe(103) // 108 - 5 + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad. Korting: 5kg N/ha, vanggewas gezaaid tussen 2 t/m 14 oktober", + ) + }) + + it("should apply 10 korting if vanggewas is present (sown Oct 15th - Oct 31st)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) + b_lu_start: new Date(2024, 9, 20), // Oct 20th, 2024 + b_lu_end: new Date(2026, 1, 31), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in zand_nwc in nv-gebied is 108. With vanggewas sown Oct 15-31, korting should be 10. + expect(result.normValue).toBe(98) // 108 - 10 + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad. Korting: 10kg N/ha, vanggewas gezaaid tussen 15 t/m 31 oktober", + ) + }) + + it("should apply 20 korting if vanggewas is present (sown Nov 1st or later)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) + b_lu_start: new Date(2024, 10, 1), // Nov 1st, 2024 + b_lu_end: new Date(2026, 1, 31), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in zand_nwc in nv-gebied is 108. With vanggewas sown Nov 1st+, korting should be 20. + expect(result.normValue).toBe(88) // 108 - 20 + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad. Korting: 20kg N/ha, vanggewas gezaaid op of na 1 november", + ) + }) + + it("should apply 20 korting if no winterteelt or vanggewas is present in zand_nwc region", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.656346970245633, 51.987872886419524], // This centroid is in 'zand_nwc' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_234", // Zomertarwe (not winterteelt or vanggewas) + b_lu_start: new Date(2024, 5, 1), + b_lu_end: new Date(2024, 8, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in zand_nwc in nv-gebied is 108. With no exception, korting should be 20. + expect(result.normValue).toBe(88) // 108 - 20 + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad. Korting: 20kg N/ha: geen vanggewas of winterteelt", + ) + }) + + it("should not apply korting if region is not sandy or loess, even without winterteelt/vanggewas", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.648307588666836, 51.96484772224782], // This centroid is in 'klei' + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2751", // Vruchtgewassen (2026 hoofdteelt) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_234", // Zomertarwe (not winterteelt or vanggewas) + b_lu_start: new Date(2024, 5, 1), + b_lu_end: new Date(2026, 1, 31), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + // The base norm for Vruchtgewassen in klei is 135. Korting should not apply in non-sandy/loess regions. + expect(result.normValue).toBe(135) + expect(result.normSource).toEqual( + "Vruchtgewassen, Landbouwstambonen, rijp zaad.", + ) + }) + + it("should return the correct norm for Gras voor industriƫle verwerking (eerste jaar)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_3805", // Gras voor industriƫle verwerking + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(30) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Gras voor industriƫle verwerking (inzaai in september en eerste jaar).", + ) + }) + + it("should return the correct norm for Gras voor industriƫle verwerking (volgende jaren)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_3805", // Gras voor industriƫle verwerking (current year) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_3805", // Gras voor industriƫle verwerking (previous year) + b_lu_start: new Date(2024, 0, 1), + b_lu_end: new Date(2024, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(310) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Gras voor industriƫle verwerking (inzaai voor 15 mei en volgende jaren).", + ) + }) + + it("should return the correct norm for Graszaad, Engels raaigras (1e jaars)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_6750", // Graszaad, Engels raaigras + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(165) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Graszaad, Engels raaigras (1e jaars).", + ) + }) + + it("should return the correct norm for Graszaad, Engels raaigras (overjarig)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_6750", // Graszaad, Engels raaigras (current year) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_6750", // Graszaad, Engels raaigras (previous year) + b_lu_start: new Date(2024, 0, 1), + b_lu_end: new Date(2024, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(200) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Graszaad, Engels raaigras (overjarig).", + ) + }) + + it("should return the correct norm for Akkerbouwgewassen, Roodzwenkgras (1e jaars)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_6784", // Akkerbouwgewassen, Roodzwenkgras + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(85) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Roodzwenkgras (1e jaars).", + ) + }) + + it("should return the correct norm for Akkerbouwgewassen, Roodzwenkgras (overjarig)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_6784", // Akkerbouwgewassen, Roodzwenkgras (current year) + b_lu_start: new Date(2026, 0, 1), + b_lu_end: new Date(2026, 5, 1), + } as Partial, + { + b_lu_catalogue: "nl_6784", // Akkerbouwgewassen, Roodzwenkgras (previous year) + b_lu_start: new Date(2024, 0, 1), + b_lu_end: new Date(2024, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(115) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Roodzwenkgras (overjarig).", + ) + }) + + it("should return the correct norm for Winterui (1e jaars)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_1932", // Winterui, 1e jaars + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(170) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Ui overig, zaaiui of winterui. (1e jaars).", + ) + }) + + it("should return the correct norm for Winterui (2e jaars)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_1933", // Winterui, 2e jaars + b_lu_start: new Date(2026, 0, 1), // Current year cultivation + b_lu_end: new Date(2026, 5, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(170) + expect(result.normSource).toEqual( + "Akkerbouwgewassen, Ui overig, zaaiui of winterui. (2e jaars).", + ) + }) + + it("should return the correct norm for Bladgewassen, Spinazie (1e teelt)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2773", // Bladgewassen, Spinazie + b_lu_start: new Date(2026, 4, 15), // May 15th, 2026 (hoofdteelt) + b_lu_end: new Date(2026, 6, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(260) + expect(result.normSource).toEqual("Bladgewassen, Spinazie (1e teelt).") + }) + + it("should return the correct norm for Bladgewassen, Slasoorten (1e teelt)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2767", // Bladgewassen, Slasoorten + b_lu_start: new Date(2026, 4, 15), // May 15th, 2026 (hoofdteelt) + b_lu_end: new Date(2026, 6, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(180) + expect(result.normSource).toEqual( + "Bladgewassen, Slasoorten (1e teelt).", + ) + }) + + it("should return the correct norm for Bladgewassen, Andijvie eerste teelt volgteelt (1e teelt)", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], // Klei region + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_2708", // Bladgewassen, Andijvie eerste teelt volgteelt + b_lu_start: new Date(2026, 4, 15), // May 15th, 2026 (hoofdteelt) + b_lu_end: new Date(2026, 6, 1), + } as Partial, + ] as NL2026NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2026StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(180) + expect(result.normSource).toEqual( + "Bladgewassen, Andijvie eerste teelt volgteelt (1e teelt).", + ) + }) +}) diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts new file mode 100644 index 000000000..4cb10b869 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -0,0 +1,569 @@ +import { withCalculationCache } from "@svenvw/fdm-core" +import Decimal from "decimal.js" +import pkg from "../../../../package" +import { determineNLHoofdteelt } from "norms/nl/2025/value/hoofdteelt" +import { nitrogenStandardsData } from "./stikstofgebruiksnorm-data" +import type { + NitrogenStandard, + NL2026NormsInput, + NL2026NormsInputForCultivation, + NormsByRegion, + RegionKey, +} from "./types" +import type { GebruiksnormResult } from "norms/nl/types" +import { + getRegion, + isFieldInNVGebied, +} from "norms/nl/2025/value/stikstofgebruiksnorm" + +/** + * Retrieves the appropriate set of nitrogen norms (`NormsByRegion`) for a given cultivation. + * This function applies a set of specific rules and conditions to select the most accurate + * norm from the available `NitrogenStandard` data, considering factors like cultivation + * sub-types, specific varieties, and farm derogation status. + * + * @param selectedStandard - The base `NitrogenStandard` object that broadly matches the cultivation. + * This object contains various norm categories (e.g., general, sub-type specific, variety-specific). + * @param b_lu_variety - Optional. The specific variety of the cultivation (e.g., a potato variety). + * This is used to apply variety-specific norms where applicable. + * @param b_lu_end - The termination date of the cultivation. This is crucial for determining + * applicable sub-type periods, especially for temporary grasslands where norms can vary + * based on the period of the year. + * @returns A `NormsByRegion` object containing standard and NV-gebied norms for all regions + * (e.g., "zand_nwc", "zand_zuid", "klei", "veen", "loess") that apply to the specific cultivation and conditions. + * Returns `undefined` if no applicable norms can be found based on the provided criteria. + * + * @remarks + * The function prioritizes norm selection based on the following hierarchy: + * 1. **Sub-Type Norms (e.g., Temporary Grasslands)**: + * If `selectedStandard` has `sub_types` defined (e.g., for temporary grasslands), + * it checks if the `b_lu_end` date falls within any of the specified `period_start_month` + * and `period_end_month` ranges. If a matching sub-type period is found, its associated + * norms are returned. This ensures that time-sensitive norms are correctly applied. + * 2. **Variety-Specific Norms (e.g., Potatoes)**: + * If the `selectedStandard` is for "aardappel" (potato) and `b_lu_variety` is provided, + * the function checks if the variety matches any in `varieties_hoge_norm` (high norms) + * or `varieties_lage_norm` (low norms). If a match is found, the corresponding norms + * (`norms_hoge_norm` or `norms_lage_norm`) are returned. If the variety doesn't match + * these specific lists, it falls back to `norms_overig` (other) potato norms if available. + * 4. **Default Norms**: + * If none of the above specific conditions are met, the function defaults to returning + * the primary `norms` defined directly within the `selectedStandard` object. + * + * This structured approach ensures that the most specific and applicable nitrogen norm + * is always retrieved for the given cultivation context. + */ +function getNormsForCultivation( + selectedStandard: NitrogenStandard, + b_lu_end: Date, + subTypeOmschrijving?: string, +): NormsByRegion | undefined { + if (selectedStandard.sub_types) { + type SubType = NonNullable[number] + let matchingSubType: SubType | undefined + + // 1. Check for a direct match on omschrijving + if (subTypeOmschrijving) { + matchingSubType = selectedStandard.sub_types.find( + (sub) => sub.omschrijving === subTypeOmschrijving, + ) + if (matchingSubType) { + return matchingSubType.norms + } + } + + // 2. Fallback to time-based logic for temporary grasslands if no omschrijving match + const endDate = new Date(b_lu_end) + matchingSubType = selectedStandard.sub_types.find((sub) => { + if (sub.period_start_month && sub.period_end_month) { + const startPeriod = new Date( + endDate.getFullYear(), + sub.period_start_month - 1, + sub.period_start_day || 1, + ) + const endPeriod = new Date( + endDate.getFullYear(), + sub.period_end_month - 1, + sub.period_end_day || 1, + ) + if (sub.period_start_month > sub.period_end_month) { + endPeriod.setFullYear(endDate.getFullYear() + 1) + } + return endDate >= startPeriod && endDate <= endPeriod + } + return false + }) + + return matchingSubType?.norms + } + + // Default case if no sub_types are defined + return selectedStandard.norms +} + +/** + * Determines the specific sub-type 'omschrijving' for a cultivation that is part of a larger group. + * This is necessary for standards that use sub_types to differentiate norms, e.g., for winter vs. summer varieties. + * + * @param cultivation - The specific cultivation for which to determine the sub-type. + * @param standard - The matched NitrogenStandard which may contain sub_types. + * @param cultivations - An array of cultivation objects for the current and previous year. + * @returns The 'omschrijving' of the matching sub-type as a string, or undefined if no specific sub-type applies. + */ +function determineSubTypeOmschrijving( + cultivation: NL2026NormsInputForCultivation, + standard: NitrogenStandard, + cultivations: NL2026NormsInputForCultivation[], + has_grazing_intention: boolean | undefined, +): string | undefined { + // Grasland logic based on grazing intention + if (standard.type === "grasland") { + return has_grazing_intention ? "beweiden" : "volledig maaien" + } + + // Potato logic based on variety + if (standard.type === "aardappel") { + if (cultivation.b_lu_variety) { + const varietyLower = cultivation.b_lu_variety.toLowerCase() + const subType = standard.sub_types?.find((sub) => + sub.varieties?.some((v) => v.toLowerCase() === varietyLower), + ) + if (subType) { + return subType.omschrijving + } + } + + // Fallback for potatoes is 'overig' if a variety is present but not in a specific list + return standard.sub_types?.find((s) => s.omschrijving === "overig") + ?.omschrijving + } + + // Luzerne logic based on cultivation history + if (standard.cultivation_rvo_table2 === "Akkerbouwgewassen, Luzerne") { + const lucerneCultivationCodes = standard.b_lu_catalogue_match + const hasLucernceCultivationInPreviousYear = cultivations.some( + (c) => + lucerneCultivationCodes.includes(c.b_lu_catalogue) && + c.b_lu_start.getFullYear() <= 2025, + ) + return hasLucernceCultivationInPreviousYear + ? "volgende jaren" + : "eerste jaar" + } + + // Koolzaad logic based on specific BRP code + if (standard.cultivation_rvo_table2 === "Akkerbouwgewassen, koolzaad") { + if (cultivation.b_lu_catalogue === "nl_1922") return "winter" + if (cultivation.b_lu_catalogue === "nl_1923") return "zomer" + } + + // Gras voor industriƫle verwerking logic based on cultivation history + if ( + standard.cultivation_rvo_table2 === + "Akkerbouwgewassen, Gras voor industriƫle verwerking" + ) { + const grasCultivationCodes = standard.b_lu_catalogue_match + const hasGrasCultivationInPreviousYear = cultivations.some( + (c) => + grasCultivationCodes.includes(c.b_lu_catalogue) && + c.b_lu_start.getFullYear() <= 2025, + ) + return hasGrasCultivationInPreviousYear + ? "inzaai voor 15 mei en volgende jaren" + : "inzaai in september en eerste jaar" + } + + // Graszaad, Engels raaigras logic based on cultivation history + if ( + standard.cultivation_rvo_table2 === + "Akkerbouwgewassen, Graszaad, Engels raaigras" + ) { + const graszaadCultivationCodes = standard.b_lu_catalogue_match + const hasGraszaadCultivationInPreviousYear = cultivations.some( + (c) => + graszaadCultivationCodes.includes(c.b_lu_catalogue) && + c.b_lu_start.getFullYear() <= 2025, + ) + return hasGraszaadCultivationInPreviousYear ? "overjarig" : "1e jaars" + } + + // Roodzwenkgras logic based on cultivation history + if ( + standard.cultivation_rvo_table2 === "Akkerbouwgewassen, Roodzwenkgras" + ) { + const roodzwenkgrasCultivationCodes = standard.b_lu_catalogue_match + const hasRoodzwenkgrasCultivationInPreviousYear = cultivations.some( + (c) => + roodzwenkgrasCultivationCodes.includes(c.b_lu_catalogue) && + c.b_lu_start.getFullYear() <= 2025, + ) + return hasRoodzwenkgrasCultivationInPreviousYear + ? "overjarig" + : "1e jaars" + } + + // Winterui (Onion) logic based on specific BRP codes + if ( + standard.cultivation_rvo_table2 === + "Akkerbouwgewassen, Ui overig, zaaiui of winterui." + ) { + if (cultivation.b_lu_catalogue === "nl_1932") return "1e jaars" + if (cultivation.b_lu_catalogue === "nl_1933") return "2e jaars" + } + + // Bladgewassen logic based on hoofdteelt + const bladgewasRvoTable2s = [ + "Bladgewassen, Spinazie", + "Bladgewassen, Slasoorten", + "Bladgewassen, Andijvie eerste teelt volgteelt", + ] + + if (bladgewasRvoTable2s.includes(standard.cultivation_rvo_table2)) { + const hoofdteeltCatalogue = determineNLHoofdteelt(cultivations, 2026) + if (cultivation.b_lu_catalogue === hoofdteeltCatalogue) { + return "1e teelt" + } + // TODO: Implement volgteelt logic here later + } + + /* + * --- Cultivations with Unclear Differentiation Logic (may require matching on b_lu string or external context): --- + * - Bladgewassen, Bladgewassen overig (e.g., "eenmalige oogst" vs. "meermalige oogst") + * - Kruiden (differentiating between bladgewas, wortelgewassen, zaadgewassen) + * - Bloembollengewassen, Iris (e.g., "grofbollig" vs. "fijnbollig") + * - Bloembollengewassen, Krokus (e.g., "grote gele" vs. "overig") + * - Bloembollengewassen, Gladiool (e.g., "pitten" vs. "kralen") + */ + + return undefined +} + +/** + * Calculates the "korting" (reduction) on the nitrogen usage norm based on the presence + * of winter crops or catch crops in the previous year. + * + * @param cultivations - An array of cultivation objects for the current and previous year. + * @param region - The soil region of the field (e.g., "zand_nwc", "zand_zuid", "loess"). + * @returns An object containing the reduction amount in kilograms of nitrogen per hectare (kg N/ha) and a description. + */ +function calculateKorting( + cultivations: NL2026NormsInputForCultivation[], + region: RegionKey, +): { amount: Decimal; description: string } { + const currentYear = 2026 + const previousYear = currentYear - 1 + + const sandyOrLoessRegions: RegionKey[] = ["zand_nwc", "zand_zuid", "loess"] + + // Check if field is outside regions with korting + if (!sandyOrLoessRegions.includes(region)) { + return { + amount: new Decimal(0), + description: ".", + } + } + + // Determine hoofdteelt for the current year (2026) + const hoofdteelt2026 = determineNLHoofdteelt( + cultivations.filter((c) => c.b_lu_start.getFullYear() === currentYear), + 2026, + ) + const hoofdteelt2026Standard = nitrogenStandardsData.find((ns) => + ns.b_lu_catalogue_match.includes(hoofdteelt2026), + ) + + // Check for winterteelt exception (hoofdteelt of 2026 is winterteelt, sown in late 2025) + if (hoofdteelt2026Standard?.is_winterteelt) { + return { + amount: new Decimal(0), + description: ". Geen korting: winterteelt aanwezig", + } + } + + // Filter cultivations for the previous year (2025) + const cultivations2025 = cultivations.filter( + (c) => c.b_lu_start.getFullYear() === previousYear, + ) + + // Check for vanggewas exception in 2025 + const vanggewassen2025 = cultivations2025.filter((prevCultivation) => { + const matchingStandard = nitrogenStandardsData.find((ns) => + ns.b_lu_catalogue_match.includes(prevCultivation.b_lu_catalogue), + ) + const matchingYear = + prevCultivation.b_lu_start.getTime() < + new Date(currentYear, 1, 1).getTime() && + prevCultivation.b_lu_start.getTime() > + new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2025 and January 31th 2026 + return matchingStandard?.is_vanggewas === true && matchingYear === true + }) + if (vanggewassen2025.length === 0) { + return { + amount: new Decimal(20), + description: ". Korting: 20kg N/ha: geen vanggewas of winterteelt", + } + } + + // Check if a vanggewas is present to February 1st + const vanggewassenCompleted2025 = vanggewassen2025.filter( + (prevCultivation) => { + return ( + prevCultivation.b_lu_end === null || + prevCultivation.b_lu_end.getTime() >= new Date(currentYear, 1) // Month 1 is February + ) + }, + ) + if (vanggewassenCompleted2025.length === 0) { + return { + amount: new Decimal(20), + description: + ". Korting: 20kg N/ha: vanggewas staat niet tot 1 februari", + } + } + // If multiple vanggewassen are completed to February 1st select the vangewas that was first sown + const sortedVanggewassen = vanggewassenCompleted2025.sort((a, b) => { + return a.b_lu_start.getTime() - b.b_lu_start.getTime() + }) + const vanggewas2025 = sortedVanggewassen[0] + + const sowDate = vanggewas2025.b_lu_start + const october1 = new Date(previousYear, 9, 1) // October 1st + const october15 = new Date(previousYear, 9, 15) // October 15th + const november1 = new Date(previousYear, 10, 1) // November 1st + + let kortingAmount = new Decimal(20) // Default korting + let kortingDescription = + ". Korting: 20kg N/ha, geen vanggewas of te laat gezaaid" + + if (sowDate <= october1) { + kortingAmount = new Decimal(0) + kortingDescription = + ". Geen korting: vanggewas gezaaid uiterlijk 1 oktober" + } else if (sowDate > october1 && sowDate <= october15) { + kortingAmount = new Decimal(5) + kortingDescription = + ". Korting: 5kg N/ha, vanggewas gezaaid tussen 2 t/m 14 oktober" + } else if (sowDate > october15 && sowDate < november1) { + kortingAmount = new Decimal(10) + kortingDescription = + ". Korting: 10kg N/ha, vanggewas gezaaid tussen 15 t/m 31 oktober" + } else { + kortingAmount = new Decimal(20) + kortingDescription = + ". Korting: 20kg N/ha, vanggewas gezaaid op of na 1 november" + } + + return { amount: kortingAmount, description: kortingDescription } +} + +/** + * Determines the 'gebruiksnorm' (usage standard) for nitrogen for a given cultivation + * based on its BRP code, geographical location, and other specific characteristics. + * This function is the primary entry point for calculating the nitrogen usage norm + * according to the Dutch RVO's "Tabel 2 Stikstof landbouwgrond 2026" and related annexes. + * + * @param input - An object of type `NL2026NormsInput` containing all necessary data: + * - `farm.is_derogatie_bedrijf`: A boolean indicating if the farm operates under derogation. + * - `field.b_centroid`: An object with the latitude and longitude of the field's centroid. + * - `cultivations`: An array of cultivation objects, from which the `hoofdteelt` (main crop) + * will be determined. + * @returns A promise that resolves to an object of type `GebruiksnormResult` containing: + * - `normValue`: The determined nitrogen usage standard in kilograms per hectare (kg/ha). + * - `normSource`: The descriptive name from RVO Table 2 used for the calculation. + * - `kortingDescription`: A description of any korting (reduction) applied to the norm. + * Returns `null` if no matching standard or applicable norm can be found for the given input. + * @throws {Error} If the `hoofdteelt` cultivation cannot be found or if geographical data + * queries fail. + * + * @remarks + * The function follows a comprehensive, multi-step process to accurately determine the + * correct nitrogen norm: + * + * 1. **Identify Main Crop (`hoofdteelt`)**: + * The `determineNLHoofdteelt` function is called to identify the primary cultivation + * (`b_lu_catalogue`) for the field based on the provided `cultivations` array. This is + * the first step to narrow down the applicable nitrogen standards. + * + * 2. **Determine Geographical Context**: + * - `isFieldInNVGebied`: This asynchronous helper function is called to check if the + * field's centroid falls within a Nitraatkwetsbaar Gebied (NV-gebied). Fields in NV-gebieds + * often have stricter (lower) nitrogen norms. + * - `getRegion`: This asynchronous helper function determines the specific soil region + * (e.g., "zand" for sandy soil, "klei" for clay soil) based on the field's coordinates. + * Nitrogen norms can vary significantly by soil type. + * Both functions use efficient spatial queries to retrieve this information. + * + * 3. **Match Nitrogen Standard Data**: + * The `nitrogenStandardsData` (loaded from a JSON file) is filtered to find all entries + * (`NitrogenStandard` objects) whose `b_lu_catalogue_match` array includes the identified + * `b_lu_catalogue` of the `hoofdteelt`. + * + * 4. **Refine by Variety (for Potatoes)**: + * If the `hoofdteelt` has a specific `b_lu_variety` (e.g., for potatoes), the matching + * standards are further filtered. Potatoes can have "high" (`varieties_hoge_norm`) or + * "low" (`varieties_lage_norm`) nitrogen norms based on their variety. If a specific + * variety match is not found, it falls back to "overig" (other) potato norms if available. + * + * 5. **Select the Most Specific Standard**: + * If multiple `NitrogenStandard` entries still match after initial filtering and + * variety-specific refinement, the function attempts to select the most specific one. + * This prioritization ensures that detailed rules (e.g., those without `variety_type` + * or `sub_types` if a direct match is found) are applied correctly. + * + * 6. **Retrieve Applicable Norms**: + * The `getNormsForCultivation` helper function is called with the `selectedStandard` + * and other relevant parameters (variety, cultivation end date). + * This function applies a hierarchy of rules (sub-type periods, variety-specific, + * derogation-specific) to return the precise `NormsByRegion` object for the cultivation. + * + * 7. **Calculate Final Norm Value**: + * From the `applicableNorms` object, the function retrieves the specific norms for the + * determined `region`. The final `normValue` is then selected: if the field is in an + * `is_nv_area`, the `nv_area` norm is used; otherwise, the `standard` norm is applied. + * + * 8. **Apply "Korting" (Reduction)**: + * The `calculateKorting` function is called to determine if a reduction should be applied + * based on the previous year's cultivations and the field's region. The calculated + * `kortingAmount` is then subtracted from the `normValue`. + * + * This detailed process ensures that the calculated nitrogen usage norm is accurate and + * compliant with RVO regulations, taking into account all relevant agricultural and + * geographical factors. + * + * @see {@link https://www.rvo.nl/sites/default/files/2025-12/Tabel-2-Stikstof-landbouwgrond-2026_0.pdf | RVO Tabel 2 Stikstof landbouwgrond 2026} - Official document for nitrogen norms. + * @see {@link https://www.rvo.nl/onderwerpen/mest/gebruiken-en-uitrijden/stikstof-en-fosfaat/gebruiksnormen-stikstof | RVO Gebruiksnormen stikstof (official page)} - General information on nitrogen and phosphate norms. + */ +export async function calculateNL2026StikstofGebruiksNorm( + input: NL2026NormsInput, +): Promise { + const has_grazing_intention = input.farm.has_grazing_intention + const field = input.field + const cultivations = input.cultivations + + // Determine hoofdteelt + const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2026) + let cultivation = cultivations.find( + (c) => c.b_lu_catalogue === b_lu_catalogue, + ) + + //Create cultivation in case of braak + if (b_lu_catalogue === "nl_6794") { + cultivation = { + b_lu: "Groene braak, spontane opkomst", + b_lu_catalogue: "nl_6794", + b_lu_start: new Date("2026-01-01"), + b_lu_end: new Date("2026-12-31"), + b_lu_variety: null, + } + } + if (!cultivation) { + throw new Error( + `Cultivation with b_lu_catalogue ${b_lu_catalogue} not found`, + ) + } + + // Determine region and NV gebied + const is_nv_area = await isFieldInNVGebied(field.b_centroid) + const region = await getRegion(field.b_centroid) + + // Find matching nitrogen standard data based on b_lu_catalogue_match + const matchingStandards: NitrogenStandard[] = nitrogenStandardsData.filter( + (ns: NitrogenStandard) => + ns.b_lu_catalogue_match.includes(b_lu_catalogue), + ) + + if (matchingStandards.length === 0) { + throw new Error( + `No matching nitrogen standard found for b_lu_catalogue ${b_lu_catalogue}.`, + ) + } + + // Prioritize exact matches if multiple exist (e.g., for specific potato types) + let selectedStandard: NitrogenStandard | undefined + + if (matchingStandards.length === 1) { + selectedStandard = matchingStandards[0] + } else if (matchingStandards.length > 1) { + // If multiple standards match b_lu_catalogue, try to find a more specific one + // This could be based on sub_types with specific omschrijving or varieties + selectedStandard = + matchingStandards.find((ns) => + ns.sub_types?.some((sub) => sub.omschrijving || sub.varieties), + ) || matchingStandards[0] // Fallback to the first if no specific sub_type is found + } + + if (!selectedStandard) { + throw new Error( + `No specific matching nitrogen standard found for b_lu_catalogue ${b_lu_catalogue} with variety ${ + cultivation.b_lu_variety || "N/A" + } in region ${region}.`, + ) + } + + // Determine the sub-type omschrijving + const subTypeOmschrijving = determineSubTypeOmschrijving( + cultivation, + selectedStandard, + cultivations, + has_grazing_intention, + ) + + const applicableNorms = getNormsForCultivation( + selectedStandard, + cultivation.b_lu_end, + subTypeOmschrijving, + ) + + if (!applicableNorms) { + throw new Error( + `Applicable norms object is undefined for ${selectedStandard.cultivation_rvo_table2} in region ${region}.`, + ) + } + + const normsForRegion: { standard: number; nv_area: number } = + applicableNorms[region] + + if (!normsForRegion) { + throw new Error( + `No norms found for region ${region} for ${selectedStandard.cultivation_rvo_table2}.`, + ) + } + + let normValue = new Decimal( + is_nv_area ? normsForRegion.nv_area : normsForRegion.standard, + ) + + // Apply korting + const { amount: kortingAmount, description: kortingDescription } = + calculateKorting(cultivations, region) + normValue = new Decimal(normValue).minus(kortingAmount) + + // If normvalue is negative, e.g. Geen plaatsingsruimte plus korting, set it to 0 + if (normValue.isNegative()) { + normValue = new Decimal(0) + } + + const subTypeText = subTypeOmschrijving ? ` (${subTypeOmschrijving})` : "" + return { + normValue: normValue.toNumber(), + normSource: `${selectedStandard.cultivation_rvo_table2}${subTypeText}${kortingDescription}`, + } +} + +/** + * Memoized version of {@link calculateNL2026StikstofGebruiksNorm}. + * + * This function is wrapped with `withCalculationCache` to optimize performance by caching + * results based on the input and the current calculator version. + * + * @param {NL2026NormsInput} input - An object of type `NL2026NormsInput` containing all necessary data. + * @returns {Promise} A promise that resolves to an object of type `GebruiksnormResult` containing: + * - `normValue`: The determined nitrogen usage standard in kilograms per hectare (kg/ha). + * - `normSource`: The descriptive name from RVO Table 2 used for the calculation. + * - `kortingDescription`: A description of any korting (reduction) applied to the norm. + */ +export const getNL2026StikstofGebruiksNorm = withCalculationCache( + calculateNL2026StikstofGebruiksNorm, + "calculateNL2026StikstofGebruiksNorm", + pkg.calculatorVersion, +) diff --git a/fdm-calculator/src/norms/nl/2026/value/types.d.ts b/fdm-calculator/src/norms/nl/2026/value/types.d.ts new file mode 100644 index 000000000..290972cf9 --- /dev/null +++ b/fdm-calculator/src/norms/nl/2026/value/types.d.ts @@ -0,0 +1,112 @@ +import type { Cultivation, Field, SoilAnalysis } from "@svenvw/fdm-core" + +/** + * Represents the collected input for a single cultivation, required for NL 2026 norm calculations. + */ +export type NL2026NormsInputForCultivation = Pick< + Cultivation, + "b_lu" | "b_lu_catalogue" | "b_lu_start" | "b_lu_end" | "b_lu_variety" +> + +/** + * Represents the complete set of inputs required to calculate all NL 2026 norms for a given farm. + */ +export type NL2026NormsInput = { + /** Farm-level properties, such as grazing intention. */ + farm: { + has_grazing_intention: boolean + } + /** The field record from fdm-core, including its ID and centroid for location-based checks. */ + field: Pick + /** An array of all cultivations on the field with their required norm inputs. */ + cultivations: NL2026NormsInputForCultivation[] + /** The most recent soil analysis data available before the start of the cultivation. */ + soilAnalysis: Pick +} + +/** + * Defines the possible phosphate classes based on RVO's "Tabel Fosfaatgebruiksnormen 2026". + * These classes are determined by P-CaCl2 and P-Al soil analysis values. + */ +export type FosfaatKlasse = "Arm" | "Laag" | "Neutraal" | "Ruim" | "Hoog" + +/** + * Defines the structure for a single nitrogen standard entry, + * based on the RVO's "Tabel 2 Stikstof landbouwgrond 2026" and related documents. + * This interface supports various complexities like different norms for regions, + * specific varieties, derogation status, and sub-types for temporary grasslands. + */ +export interface NitrogenStandard { + /** + * The cultivation name as it appears in RVO's "Tabel 2 Stikstof landbouwgrond 2026". + * This is used for descriptive purposes and as part of the function's return value. + * @example "Grasland", "Akkerbouwgewassen, mais" + */ + cultivation_rvo_table2: string + /** + * An array of BRP (Basisregistratie Percelen) cultivation codes that match this standard. + * This allows a single standard entry to apply to multiple BRP codes. + * @example ["nl_265", "nl_331"] + */ + b_lu_catalogue_match: string[] + /** + * A general type classification for the cultivation (e.g., "grasland", "aardappel", "akkerbouw"). + * Used internally for conditional logic in norm determination. + */ + type: string + /** + * Optional. A more specific classification for varieties, particularly for potatoes. + * @example "consumptie_overig", "poot_overig" + */ + is_winterteelt: boolean + is_vanggewas: boolean + norms?: { + klei: { standard: number; nv_area: number } + zand_nwc: { standard: number; nv_area: number } + zand_zuid: { standard: number; nv_area: number } + loess: { standard: number; nv_area: number } + veen: { standard: number; nv_area: number } + } + sub_types?: Array<{ + omschrijving?: string + period_description?: string + period_start_month?: number + period_start_day?: number + period_end_month?: number + period_end_day?: number + varieties?: string[] // Added for potato varieties + norms: { + klei: { standard: number; nv_area: number } + zand_nwc: { standard: number; nv_area: number } + zand_zuid: { standard: number; nv_area: number } + loess: { standard: number; nv_area: number } + veen: { standard: number; nv_area: number } + } + winterteelt_voor_31_12?: { + klei: { standard: number; nv_area: number } + zand_nwc: { standard: number; nv_area: number } + zand_zuid: { standard: number; nv_area: number } + loess: { standard: number; nv_area: number } + veen: { standard: number; nv_area: number } + } + winterteelt_na_31_12?: { + klei: { standard: number; nv_area: number } + zand_nwc: { standard: number; nv_area: number } + zand_zuid: { standard: number; nv_area: number } + loess: { standard: number; nv_area: number } + veen: { standard: number; nv_area: number } + } + }> +} + +/** + * Defines the valid keys for different soil regions in the Netherlands. + */ +export type RegionKey = "klei" | "zand_nwc" | "zand_zuid" | "loess" | "veen" + +/** + * A utility type to represent nitrogen norms structured by region. + */ +export type NormsByRegion = { + [key in RegionKey]: { standard: number; nv_area: number } +} From 4796549fc2fba0783987df6a48302a31a910866a Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:32:30 +0100 Subject: [PATCH 06/20] feat: make next year also available --- .changeset/small-icons-teach.md | 5 +++++ fdm-app/app/lib/calendar.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/small-icons-teach.md diff --git a/.changeset/small-icons-teach.md b/.changeset/small-icons-teach.md new file mode 100644 index 000000000..039fe1676 --- /dev/null +++ b/.changeset/small-icons-teach.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Make next year also available diff --git a/fdm-app/app/lib/calendar.ts b/fdm-app/app/lib/calendar.ts index d3f4fc0d8..36ffa12f0 100644 --- a/fdm-app/app/lib/calendar.ts +++ b/fdm-app/app/lib/calendar.ts @@ -2,7 +2,7 @@ import type { Timeframe } from "@svenvw/fdm-core" import type { Params } from "react-router" const yearStart = 2020 -const yearEnd = new Date().getFullYear() +const yearEnd = new Date().getFullYear() + 1 export function getCalendar(params: Params): string { const calendar = params.calendar as string @@ -37,7 +37,7 @@ export function getTimeframe(params: Params): Timeframe { } export function getCalendarSelection(): string[] { - // Create array of years from 2020 to current year + // Create array of years from 2020 to next year const years = [] for (let i = yearStart; i <= yearEnd; i++) { years.push(i.toString()) From c8a6e65d4635566230e55ebc9e3573702754ff8e Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:36:44 +0100 Subject: [PATCH 07/20] feat: At Gebruiksruimte make 2026 also available --- .changeset/famous-hats-lick.md | 5 +++++ .../app/routes/farm.$b_id_farm.$calendar.norms.$b_id.tsx | 4 ++-- .../app/routes/farm.$b_id_farm.$calendar.norms._index.tsx | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/famous-hats-lick.md diff --git a/.changeset/famous-hats-lick.md b/.changeset/famous-hats-lick.md new file mode 100644 index 000000000..21b9d854f --- /dev/null +++ b/.changeset/famous-hats-lick.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +At Gebruiksruimte make 2026 also available diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.$b_id.tsx index c299d036f..44ba06f42 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.$b_id.tsx @@ -160,11 +160,11 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) const asyncData = (async () => { - if (calendar !== "2025") { + if (calendar !== "2025" && calendar !== "2026") { return { fieldNormData: undefined, errorMessage: - "Gebruiksnormen zijn alleen beschikbaar voor 2025.", + "Gebruiksnormen zijn alleen beschikbaar voor 2025 en 2026.", } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms._index.tsx index c75afb872..299a36943 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms._index.tsx @@ -123,8 +123,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { ) const asyncData = (async () => { - // Currently only 2025 is supported - if (calendar !== "2025") { + // Currently only 2025 and 2026 are supported + if (calendar !== "2025" && calendar !== "2026") { return {} } @@ -453,7 +453,7 @@ function Norms(loaderData: Awaited>) {

Op dit moment kunnen we alleen nog de gebruiksnormen voor - 2025 berekenen en weergeven. + 2025 en 2026 berekenen en weergeven.

From 9ebf7dcee41520f5cc579060b1c492058ec048a4 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:49:45 +0100 Subject: [PATCH 08/20] fix: imports --- fdm-calculator/src/index.ts | 6 +- fdm-calculator/src/norms/farm.ts | 4 +- .../nl/2025/value/stikstofgebruiksnorm.ts | 2 +- .../nl/2026/value/fosfaatgebruiksnorm.ts | 4 +- .../nl/2026/value/stikstofgebruiksnorm.ts | 6 +- pnpm-lock.yaml | 1942 ++++++++--------- 6 files changed, 921 insertions(+), 1043 deletions(-) diff --git a/fdm-calculator/src/index.ts b/fdm-calculator/src/index.ts index d86f2354b..2ca0b47bc 100644 --- a/fdm-calculator/src/index.ts +++ b/fdm-calculator/src/index.ts @@ -51,7 +51,6 @@ export type { InputAggregateNormFillingsToFarmLevel, InputAggregateNormsToFarmLevel, } from "./norms/farm" -export type { NormFilling } from "./norms/nl/2025/filling/types" export { isFieldInGWGBGebied, isFieldInNatura2000Gebied, @@ -60,10 +59,11 @@ export { getRegion, isFieldInNVGebied, } from "./norms/nl/2025/value/stikstofgebruiksnorm" +export type { NL2025NormsInput } from "./norms/nl/2025/value/types" export type { GebruiksnormResult, - NL2025NormsInput, -} from "./norms/nl/2025/value/types" + NormFilling, +} from "./norms/nl/types" export { getNutrientAdvice, requestNutrientAdvice, diff --git a/fdm-calculator/src/norms/farm.ts b/fdm-calculator/src/norms/farm.ts index 58e242015..095494770 100644 --- a/fdm-calculator/src/norms/farm.ts +++ b/fdm-calculator/src/norms/farm.ts @@ -1,6 +1,6 @@ import { Decimal } from "decimal.js" -import type { NormFilling } from "./nl/2025/filling/types" -import type { GebruiksnormResult } from "./nl/2025/value/types" +import type { NormFilling } from "./nl/types" +import type { GebruiksnormResult } from "./nl/types" /** * Represents the input structure for the `aggregateNormsToFarmLevel` function. diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index f7b0f6d51..cc78cbf26 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -12,7 +12,7 @@ import type { NormsByRegion, RegionKey, } from "./types" -import { GebruiksnormResult } from "norms/nl/types" +import type { GebruiksnormResult } from "../../types" /** * Determines if a field is located within a met nutriƫnten verontreinigde gebied (NV-gebied) in the Netherlands. diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts index 63b81a9f5..3c8315f3e 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -4,8 +4,8 @@ import pkg from "../../../../package" import { fosfaatNormsData } from "./fosfaatgebruiksnorm-data" import { determineNLHoofdteelt } from "../../2025/value/hoofdteelt" import type { FosfaatKlasse, NL2026NormsInput } from "./types.d" -import { FosfaatGebruiksnormResult } from "norms/nl/types" -import { isCultivationGrasland } from "norms/nl/2025/value/fosfaatgebruiksnorm" +import type { FosfaatGebruiksnormResult } from "../../types" +import { isCultivationGrasland } from "../../2025/value/fosfaatgebruiksnorm" /** * Helper function to determine the phosphate class ('Arm', 'Laag', 'Neutraal', 'Ruim', 'Hoog') diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts index 4cb10b869..c5d19c61e 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -1,7 +1,7 @@ import { withCalculationCache } from "@svenvw/fdm-core" import Decimal from "decimal.js" import pkg from "../../../../package" -import { determineNLHoofdteelt } from "norms/nl/2025/value/hoofdteelt" +import { determineNLHoofdteelt } from "../../2025/value/hoofdteelt" import { nitrogenStandardsData } from "./stikstofgebruiksnorm-data" import type { NitrogenStandard, @@ -10,11 +10,11 @@ import type { NormsByRegion, RegionKey, } from "./types" -import type { GebruiksnormResult } from "norms/nl/types" +import type { GebruiksnormResult } from "../../types" import { getRegion, isFieldInNVGebied, -} from "norms/nl/2025/value/stikstofgebruiksnorm" +} from "../../2025/value/stikstofgebruiksnorm" /** * Retrieves the appropriate set of nitrogen norms (`NormsByRegion`) for a given cultivation. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc39724ad..b19455776 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,7 @@ catalogs: version: 1.4.5 drizzle-kit: specifier: ^0.31.7 - version: 0.31.7 + version: 0.31.8 drizzle-orm: specifier: ^0.44.7 version: 0.44.7 @@ -41,7 +41,7 @@ catalogs: version: 0.13.0 typedoc: specifier: ^0.28.14 - version: 0.28.14 + version: 0.28.15 typedoc-plugin-missing-exports: specifier: ^4.1.2 version: 4.1.2 @@ -56,7 +56,7 @@ catalogs: version: 5.1.4 vitest: specifier: ^4.0.14 - version: 4.0.14 + version: 4.0.15 packageExtensionsChecksum: sha256-VQuFGSJ2NQgmUfUYujaw/wyx7PoF3FcUJ5DmmDpAEDo= @@ -69,10 +69,10 @@ importers: version: 2.3.8 '@changesets/cli': specifier: ^2.29.7 - version: 2.29.7(@types/node@24.10.1) + version: 2.29.8(@types/node@24.10.1) turbo: specifier: ^2.6.1 - version: 2.6.1 + version: 2.6.2 fdm-app: dependencies: @@ -90,7 +90,7 @@ importers: version: 1.0.1 '@maplibre/maplibre-gl-geocoder': specifier: ^1.9.1 - version: 1.9.1(maplibre-gl@5.13.0) + version: 1.9.1(maplibre-gl@5.14.0) '@radix-ui/react-alert-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -123,10 +123,10 @@ importers: version: 1.2.2(react@19.2.1) '@react-router/node': specifier: ^7.10.0 - version: 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + version: 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) '@react-router/serve': specifier: ^7.10.0 - version: 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + version: 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) '@remix-run/file-storage': specifier: ^0.10.0 version: 0.10.0 @@ -138,7 +138,7 @@ importers: version: 10.28.0 '@sentry/react-router': specifier: ^10.28.0 - version: 10.28.0(@react-router/node@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 10.28.0(@react-router/node@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) '@svenvw/fdm-calculator': specifier: workspace:^ version: link:../fdm-calculator @@ -150,7 +150,7 @@ importers: version: link:../fdm-data '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) + version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -204,7 +204,7 @@ importers: version: 0.554.0(react@19.2.1) maplibre-gl: specifier: ^5.13.0 - version: 5.13.0 + version: 5.14.0 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -243,16 +243,16 @@ importers: version: 7.68.0(react@19.2.1) react-map-gl: specifier: ^8.1.0 - version: 8.1.0(mapbox-gl@3.16.0)(maplibre-gl@5.13.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 8.1.0(maplibre-gl@5.14.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.7)(react@19.2.1) react-router: specifier: ^7.10.0 - version: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react-router-dom: specifier: ^7.10.0 - version: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) recharts: specifier: ^2.15.4 version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -261,13 +261,13 @@ importers: version: 4.0.1 remix-hook-form: specifier: 7.1.1 - version: 7.1.1(react-dom@19.2.1(react@19.2.1))(react-hook-form@7.68.0(react@19.2.1))(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 7.1.1(react-dom@19.2.1(react@19.2.1))(react-hook-form@7.68.0(react@19.2.1))(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) remix-toast: specifier: ^3.4.0 - version: 3.4.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) + version: 3.4.0(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) remix-utils: specifier: ^9.0.0 - version: 9.0.0(@standard-schema/spec@1.0.0)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + version: 9.0.0(@standard-schema/spec@1.0.0)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) shpjs: specifier: ^6.2.0 version: 6.2.0 @@ -295,10 +295,10 @@ importers: version: 1.51.1 '@react-router/dev': specifier: ^7.10.0 - version: 7.10.0(@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))(yaml@2.8.1) + version: 7.10.1(@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(yaml@2.8.2) '@react-router/fs-routes': specifier: ^7.10.0 - version: 7.10.0(@react-router/dev@7.10.0(@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))(yaml@2.8.1))(typescript@5.9.3) + version: 7.10.1(@react-router/dev@7.10.1(@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3) '@tailwindcss/postcss': specifier: ^4.1.17 version: 4.1.17 @@ -337,13 +337,13 @@ importers: version: 5.9.3 vite: specifier: 'catalog:' - version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) vite-node: specifier: ^5.2.0 - version: 5.2.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + version: 5.2.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: 'catalog:' - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) fdm-calculator: dependencies: @@ -371,7 +371,7 @@ importers: version: 16.0.3(rollup@4.53.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.0.14(vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) + version: 4.0.14(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) postgres: specifier: ^3.4.7 version: 3.4.7 @@ -386,16 +386,16 @@ importers: version: 0.13.0(rollup@4.53.3) typedoc: specifier: 'catalog:' - version: 0.28.14(typescript@5.9.3) + version: 0.28.15(typescript@5.9.3) typedoc-plugin-missing-exports: specifier: 'catalog:' - version: 4.1.2(typedoc@0.28.14(typescript@5.9.3)) + version: 4.1.2(typedoc@0.28.15(typescript@5.9.3)) typescript: specifier: 'catalog:' version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) fdm-core: dependencies: @@ -444,10 +444,10 @@ importers: version: 24.10.1 '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.0.14(vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) + version: 4.0.14(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) drizzle-kit: specifier: 'catalog:' - version: 0.31.7 + version: 0.31.8 fs-extra: specifier: ^11.3.2 version: 11.3.2 @@ -465,16 +465,16 @@ importers: version: 0.13.0(rollup@4.53.3) typedoc: specifier: 'catalog:' - version: 0.28.14(typescript@5.9.3) + version: 0.28.15(typescript@5.9.3) typedoc-plugin-missing-exports: specifier: 'catalog:' - version: 4.1.2(typedoc@0.28.14(typescript@5.9.3)) + version: 4.1.2(typedoc@0.28.15(typescript@5.9.3)) typescript: specifier: 'catalog:' version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) fdm-data: dependencies: @@ -496,7 +496,7 @@ importers: version: 16.0.3(rollup@4.53.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.0.14(vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) + version: 4.0.14(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) rollup: specifier: 'catalog:' version: 4.53.3 @@ -508,82 +508,82 @@ importers: version: 0.13.0(rollup@4.53.3) typedoc: specifier: 'catalog:' - version: 0.28.14(typescript@5.9.3) + version: 0.28.15(typescript@5.9.3) typedoc-plugin-missing-exports: specifier: 'catalog:' - version: 4.1.2(typedoc@0.28.14(typescript@5.9.3)) + version: 4.1.2(typedoc@0.28.15(typescript@5.9.3)) typescript: specifier: 'catalog:' version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) fdm-docs: dependencies: '@docusaurus/core': specifier: 3.9.2 - version: 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + version: 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/faster': specifier: 3.9.2 - version: 3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) + version: 3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) '@docusaurus/preset-classic': specifier: 3.9.2 - version: 3.9.2(@algolia/client-search@5.45.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.9.3) + version: 3.9.2(@algolia/client-search@5.46.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3)(typescript@5.9.3) '@mdx-js/react': specifier: ^3.1.1 - version: 3.1.1(@types/react@19.2.7)(react@19.2.0) + version: 3.1.1(@types/react@19.2.7)(react@19.2.1) clsx: specifier: ^2.1.1 version: 2.1.1 lucide-react: specifier: ^0.552.0 - version: 0.552.0(react@19.2.0) + version: 0.552.0(react@19.2.1) prism-react-renderer: specifier: ^2.4.1 - version: 2.4.1(react@19.2.0) + version: 2.4.1(react@19.2.1) react: specifier: ^19.2.0 - version: 19.2.0 + version: 19.2.1 react-dom: specifier: ^19.2.0 - version: 19.2.0(react@19.2.0) + version: 19.2.1(react@19.2.1) devDependencies: '@docusaurus/module-type-aliases': specifier: 3.9.2 - version: 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@docusaurus/tsconfig': specifier: 3.9.2 version: 3.9.2 '@docusaurus/types': specifier: 3.9.2 - version: 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) docusaurus-plugin-typedoc: specifier: ^1.4.2 - version: 1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3))) + version: 1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.15(typescript@5.9.3))) markdownlint-cli2: specifier: ^0.19.1 version: 0.19.1 typedoc: specifier: 'catalog:' - version: 0.28.14(typescript@5.9.3) + version: 0.28.15(typescript@5.9.3) typedoc-plugin-markdown: specifier: ^4.9.0 - version: 4.9.0(typedoc@0.28.14(typescript@5.9.3)) + version: 4.9.0(typedoc@0.28.15(typescript@5.9.3)) typescript: specifier: 'catalog:' version: 5.9.3 packages: - '@ai-sdk/gateway@2.0.15': - resolution: {integrity: sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==} + '@ai-sdk/gateway@2.0.18': + resolution: {integrity: sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@3.0.17': - resolution: {integrity: sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==} + '@ai-sdk/provider-utils@3.0.18': + resolution: {integrity: sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -592,8 +592,8 @@ packages: resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} - '@ai-sdk/react@2.0.102': - resolution: {integrity: sha512-EQnlat8yvyCRAVG/7ukdFNozuMdTY9DX6pN8KngfnJkBJtH+bpXZXkJlonbmd7RJ8oGMqRUAZhQSaOy0a4E1Yw==} + '@ai-sdk/react@2.0.106': + resolution: {integrity: sha512-TU8ONNhm64GI7O60UDCcOz9CdyCp3emQwSYrSnq+QWBNgS8vDlRQ3ZwXyPNAJQdXyBTafVS2iyS0kvV+KXaPAQ==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -602,8 +602,8 @@ packages: zod: optional: true - '@algolia/abtesting@1.11.0': - resolution: {integrity: sha512-a7oQ8dwiyoyVmzLY0FcuBqyqcNSq78qlcOtHmNBumRlHCSnXDcuoYGBGPN1F6n8JoGhviDDsIaF/oQrzTzs6Lg==} + '@algolia/abtesting@1.12.0': + resolution: {integrity: sha512-EfW0bfxjPs+C7ANkJDw2TATntfBKsFiy7APh+KO0pQ8A6HYa5I0NjFuCGCXWfzzzLXNZta3QUl3n5Kmm6aJo9Q==} engines: {node: '>= 14.0.0'} '@algolia/autocomplete-core@1.19.2': @@ -620,59 +620,59 @@ packages: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/client-abtesting@5.45.0': - resolution: {integrity: sha512-WTW0VZA8xHMbzuQD5b3f41ovKZ0MNTIXkWfm0F2PU+XGcLxmxX15UqODzF2sWab0vSbi3URM1xLhJx+bXbd1eQ==} + '@algolia/client-abtesting@5.46.0': + resolution: {integrity: sha512-eG5xV8rujK4ZIHXrRshvv9O13NmU/k42Rnd3w43iKH5RaQ2zWuZO6Q7XjaoJjAFVCsJWqRbXzbYyPGrbF3wGNg==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.45.0': - resolution: {integrity: sha512-I3g7VtvG/QJOH3tQO7E7zWTwBfK/nIQXShFLR8RvPgWburZ626JNj332M3wHCYcaAMivN9WJG66S2JNXhm6+Xg==} + '@algolia/client-analytics@5.46.0': + resolution: {integrity: sha512-AYh2uL8IUW9eZrbbT+wZElyb7QkkeV3US2NEKY7doqMlyPWE8lErNfkVN1NvZdVcY4/SVic5GDbeDz2ft8YIiQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.45.0': - resolution: {integrity: sha512-/nTqm1tLiPtbUr+8kHKyFiCOfhRfgC+JxLvOCq471gFZZOlsh6VtFRiKI60/zGmHTojFC6B0mD80PB7KeK94og==} + '@algolia/client-common@5.46.0': + resolution: {integrity: sha512-0emZTaYOeI9WzJi0TcNd2k3SxiN6DZfdWc2x2gHt855Jl9jPUOzfVTL6gTvCCrOlT4McvpDGg5nGO+9doEjjig==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.45.0': - resolution: {integrity: sha512-suQTx/1bRL1g/K2hRtbK3ANmbzaZCi13487sxxmqok+alBDKKw0/TI73ZiHjjFXM2NV52inwwcmW4fUR45206Q==} + '@algolia/client-insights@5.46.0': + resolution: {integrity: sha512-wrBJ8fE+M0TDG1As4DDmwPn2TXajrvmvAN72Qwpuv8e2JOKNohF7+JxBoF70ZLlvP1A1EiH8DBu+JpfhBbNphQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.45.0': - resolution: {integrity: sha512-CId/dbjpzI3eoUhPU6rt/z4GrRsDesqFISEMOwrqWNSrf4FJhiUIzN42Ac+Gzg69uC0RnzRYy60K1y4Na5VSMw==} + '@algolia/client-personalization@5.46.0': + resolution: {integrity: sha512-LnkeX4p0ENt0DoftDJJDzQQJig/sFQmD1eQifl/iSjhUOGUIKC/7VTeXRcKtQB78naS8njUAwpzFvxy1CDDXDQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.45.0': - resolution: {integrity: sha512-tjbBKfA8fjAiFtvl9g/MpIPiD6pf3fj7rirVfh1eMIUi8ybHP4ovDzIaE216vHuRXoePQVCkMd2CokKvYq1CLw==} + '@algolia/client-query-suggestions@5.46.0': + resolution: {integrity: sha512-aF9tc4ex/smypXw+W3lBPB1jjKoaGHpZezTqofvDOI/oK1dR2sdTpFpK2Ru+7IRzYgwtRqHF3znmTlyoNs9dpA==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.45.0': - resolution: {integrity: sha512-nxuCid+Nszs4xqwIMDw11pRJPes2c+Th1yup/+LtpjFH8QWXkr3SirNYSD3OXAeM060HgWWPLA8/Fxk+vwxQOA==} + '@algolia/client-search@5.46.0': + resolution: {integrity: sha512-22SHEEVNjZfFWkFks3P6HilkR3rS7a6GjnCIqR22Zz4HNxdfT0FG+RE7efTcFVfLUkTTMQQybvaUcwMrHXYa7Q==} engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/ingestion@1.45.0': - resolution: {integrity: sha512-t+1doBzhkQTeOOjLHMlm4slmXBhvgtEGQhOmNpMPTnIgWOyZyESWdm+XD984qM4Ej1i9FRh8VttOGrdGnAjAng==} + '@algolia/ingestion@1.46.0': + resolution: {integrity: sha512-2LT0/Z+/sFwEpZLH6V17WSZ81JX2uPjgvv5eNlxgU7rPyup4NXXfuMbtCJ+6uc4RO/LQpEJd3Li59ke3wtyAsA==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.45.0': - resolution: {integrity: sha512-IaX3ZX1A/0wlgWZue+1BNWlq5xtJgsRo7uUk/aSiYD7lPbJ7dFuZ+yTLFLKgbl4O0QcyHTj1/mSBj9ryF1Lizg==} + '@algolia/monitoring@1.46.0': + resolution: {integrity: sha512-uivZ9wSWZ8mz2ZU0dgDvQwvVZV8XBv6lYBXf8UtkQF3u7WeTqBPeU8ZoeTyLpf0jAXCYOvc1mAVmK0xPLuEwOQ==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.45.0': - resolution: {integrity: sha512-1jeMLoOhkgezCCPsOqkScwYzAAc1Jr5T2hisZl0s32D94ZV7d1OHozBukgOjf8Dw+6Hgi6j52jlAdUWTtkX9Mg==} + '@algolia/recommend@5.46.0': + resolution: {integrity: sha512-O2BB8DuySuddgOAbhyH4jsGbL+KyDGpzJRtkDZkv091OMomqIA78emhhMhX9d/nIRrzS1wNLWB/ix7Hb2eV5rg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.45.0': - resolution: {integrity: sha512-46FIoUkQ9N7wq4/YkHS5/W9Yjm4Ab+q5kfbahdyMpkBPJ7IBlwuNEGnWUZIQ6JfUZuJVojRujPRHMihX4awUMg==} + '@algolia/requester-browser-xhr@5.46.0': + resolution: {integrity: sha512-eW6xyHCyYrJD0Kjk9Mz33gQ40LfWiEA51JJTVfJy3yeoRSw/NXhAL81Pljpa0qslTs6+LO/5DYPZddct6HvISQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.45.0': - resolution: {integrity: sha512-XFTSAtCwy4HdBhSReN2rhSyH/nZOM3q3qe5ERG2FLbYId62heIlJBGVyAPRbltRwNlotlydbvSJ+SQ0ruWC2cw==} + '@algolia/requester-fetch@5.46.0': + resolution: {integrity: sha512-Vn2+TukMGHy4PIxmdvP667tN/MhS7MPT8EEvEhS6JyFLPx3weLcxSa1F9gVvrfHWCUJhLWoMVJVB2PT8YfRGcw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.45.0': - resolution: {integrity: sha512-8mTg6lHx5i44raCU52APsu0EqMsdm4+7Hch/e4ZsYZw0hzwkuaMFh826ngnkYf9XOl58nHoou63aZ874m8AbpQ==} + '@algolia/requester-node-http@5.46.0': + resolution: {integrity: sha512-xaqXyna5yBZ+r1SJ9my/DM6vfTqJg9FJgVydRJ0lnO+D5NhqGW/qaRG/iBGKr/d4fho34el6WakV7BqJvrl/HQ==} engines: {node: '>= 14.0.0'} '@alloc/quick-lru@5.2.0': @@ -1336,8 +1336,8 @@ packages: '@borewit/text-codec@0.1.1': resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} - '@changesets/apply-release-plan@7.0.13': - resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} + '@changesets/apply-release-plan@7.0.14': + resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} '@changesets/assemble-release-plan@6.0.9': resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} @@ -1345,12 +1345,12 @@ packages: '@changesets/changelog-git@0.2.1': resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/cli@2.29.7': - resolution: {integrity: sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ==} + '@changesets/cli@2.29.8': + resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} hasBin: true - '@changesets/config@3.1.1': - resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} + '@changesets/config@3.1.2': + resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} @@ -1358,8 +1358,8 @@ packages: '@changesets/get-dependents-graph@2.1.3': resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} - '@changesets/get-release-plan@4.0.13': - resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} + '@changesets/get-release-plan@4.0.14': + resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} @@ -1370,14 +1370,14 @@ packages: '@changesets/logger@0.1.1': resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} - '@changesets/parse@0.4.1': - resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} + '@changesets/parse@0.4.2': + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} '@changesets/pre@2.0.2': resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} - '@changesets/read@0.6.5': - resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} + '@changesets/read@0.6.6': + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} '@changesets/should-skip-package@0.1.2': resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} @@ -2224,8 +2224,8 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@gerrit0/mini-shiki@3.15.0': - resolution: {integrity: sha512-L5IHdZIDa4bG4yJaOzfasOH/o22MCesY0mx+n6VATbaiCtMeR59pdRqYk4bEiQkIHfxsHPNgdi7VJlZb2FhdMQ==} + '@gerrit0/mini-shiki@3.19.0': + resolution: {integrity: sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==} '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -2356,9 +2356,6 @@ packages: resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==} engines: {node: '>= 0.6'} - '@mapbox/mapbox-gl-supported@3.0.0': - resolution: {integrity: sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==} - '@mapbox/point-geometry@1.1.0': resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} @@ -2389,8 +2386,8 @@ packages: resolution: {integrity: sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==} hasBin: true - '@maplibre/mlt@1.1.0': - resolution: {integrity: sha512-anR8WxKIgZUJQLlZtID0v06wd9Q//9K/6lLLU3dOzmeO/xLEzAwmEqP24jEnEUBcnZGkM4vidz9H6Q4guNAAlw==} + '@maplibre/mlt@1.1.2': + resolution: {integrity: sha512-SQKdJ909VGROkA6ovJgtHNs9YXV4YXUPS+VaZ50I2Mt951SLlUm2Cv34x5Xwc1HiFlsd3h2Yrs5cn7xzqBmENw==} '@maplibre/vt-pbf@4.1.0': resolution: {integrity: sha512-9LjFAoWtxdGRns8RK9vG3Fcw/fb3eHMxvAn2jffwn3jnVO1k49VOv6+FEza70rK7WzF8GnBiKa0K39RyfevKUw==} @@ -2407,23 +2404,23 @@ packages: '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} - '@module-federation/error-codes@0.21.4': - resolution: {integrity: sha512-ClpL5MereWNXh+EgDjz7w4RrC1JlisQTvXDa1gLxpviHafzNDfdViVmuhi9xXVuj+EYo8KU70Y999KHhk9424Q==} + '@module-federation/error-codes@0.21.6': + resolution: {integrity: sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==} - '@module-federation/runtime-core@0.21.4': - resolution: {integrity: sha512-SGpmoOLGNxZofpTOk6Lxb2ewaoz5wMi93AFYuuJB04HTVcngEK+baNeUZ2D/xewrqNIJoMY6f5maUjVfIIBPUA==} + '@module-federation/runtime-core@0.21.6': + resolution: {integrity: sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==} - '@module-federation/runtime-tools@0.21.4': - resolution: {integrity: sha512-RzFKaL0DIjSmkn76KZRfzfB6dD07cvID84950jlNQgdyoQFUGkqD80L6rIpVCJTY/R7LzR3aQjHnoqmq4JPo3w==} + '@module-federation/runtime-tools@0.21.6': + resolution: {integrity: sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==} - '@module-federation/runtime@0.21.4': - resolution: {integrity: sha512-wgvGqryurVEvkicufJmTG0ZehynCeNLklv8kIk5BLIsWYSddZAE+xe4xov1kgH5fIJQAoQNkRauFFjVNlHoAkA==} + '@module-federation/runtime@0.21.6': + resolution: {integrity: sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==} - '@module-federation/sdk@0.21.4': - resolution: {integrity: sha512-tzvhOh/oAfX++6zCDDxuvioHY4Jurf8vcfoCbKFxusjmyKr32GPbwFDazUP+OPhYCc3dvaa9oWU6X/qpUBLfJw==} + '@module-federation/sdk@0.21.6': + resolution: {integrity: sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==} - '@module-federation/webpack-bundler-runtime@0.21.4': - resolution: {integrity: sha512-dusmR3uPnQh9u9ChQo3M+GLOuGFthfvnh7WitF/a1eoeTfRmXqnMFsXtZCUK+f/uXf+64874Zj/bhAgbBcVHZA==} + '@module-federation/webpack-bundler-runtime@0.21.6': + resolution: {integrity: sha512-7zIp3LrcWbhGuFDTUMLJ2FJvcwjlddqhWGxi/MW3ur1a+HaO8v5tF2nl+vElKmbG1DFLU/52l3PElVcWf/YcsQ==} '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} @@ -3564,14 +3561,14 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-router/dev@7.10.0': - resolution: {integrity: sha512-3UgkV0N5lp3+Ol3q64L4ZHgPXv2XA4KHJ59MVLSK2prokrOrPaYvqbdx40r602M+hRZp/u04ln2A6cOfBW6kxA==} + '@react-router/dev@7.10.1': + resolution: {integrity: sha512-kap9O8rTN6b3vxjd+0SGjhm5vqiAZHMmOX1Hc7Y4KXRVVdusn+0+hxs44cDSfbW6Z6fCLw6GXXe0Kr+DJIRezw==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.10.0 + '@react-router/serve': ^7.10.1 '@vitejs/plugin-rsc': '*' - react-router: ^7.10.0 + react-router: ^7.10.1 typescript: ^5.1.0 vite: ^5.1.0 || ^6.0.0 || ^7.0.0 wrangler: ^3.28.2 || ^4.0.0 @@ -3585,43 +3582,43 @@ packages: wrangler: optional: true - '@react-router/express@7.10.0': - resolution: {integrity: sha512-3cBJ2cyHn5J+wSNFn+XdNSpXVAlQ+nbj7CMa3OsiEpFb+d0GLthirvSESqRjX2Eid94xNHICqKpYS9bR4QqIxg==} + '@react-router/express@7.10.1': + resolution: {integrity: sha512-O7xjg6wWHfrsnPyVWgQG+tCamIE09SqLqtHwa1tAFzKPjcDpCw4S4+/OkJvNXLtBL60H3VhZ1r2OQgXBgGOMpw==} engines: {node: '>=20.0.0'} peerDependencies: express: ^4.17.1 || ^5 - react-router: 7.10.0 + react-router: 7.10.1 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/fs-routes@7.10.0': - resolution: {integrity: sha512-1lbSj/P/xg7mBlsUR9XJbjMjlA0P/YuN67fv9GHTZDitYjMwGra7M05+V38xW/l4saK/neVrIU8GfYCRnxEXCQ==} + '@react-router/fs-routes@7.10.1': + resolution: {integrity: sha512-iqMibGPehjHN0biBjz/SZ/Q1NyRsUsKYvP86TiIQv5vi8YRUUm80CEugBLrzu2FsuMybIGpwHcMpAB/QwVz2cw==} engines: {node: '>=20.0.0'} peerDependencies: - '@react-router/dev': ^7.10.0 + '@react-router/dev': ^7.10.1 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/node@7.10.0': - resolution: {integrity: sha512-pff3Xz3gASrIUUX54QdlPzasdN9XRLnzoFEwUVsH5y2sZ6vijQdjZExLS6aQhPiuUr/uVPwN2WngO0Ryfrxulg==} + '@react-router/node@7.10.1': + resolution: {integrity: sha512-RLmjlR1zQu+ve8ibI0lu91pJrXGcmfkvsrQl7z/eTc5V5FZgl0OvQVWL5JDWBlBZyzdLMQQekUOX5WcPhCP1FQ==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 7.10.0 + react-router: 7.10.1 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - '@react-router/serve@7.10.0': - resolution: {integrity: sha512-tgdbw1lmDkzF3gCMj//iNklgUrYHUxz35rj0sbyLeti8K2gVsNxaZWyt5omanFgkeZ7WYfi0wzLHviqxl228eA==} + '@react-router/serve@7.10.1': + resolution: {integrity: sha512-qYco7sFpbRgoKJKsCgJmFBQwaLVsLv255K8vbPodnXe13YBEzV/ugIqRCYVz2hghvlPiEKgaHh2On0s/5npn6w==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - react-router: 7.10.0 + react-router: 7.10.1 '@remix-run/file-storage@0.10.0': resolution: {integrity: sha512-s+xNp1Tu0HQJcbuN3ASD3ri+6GET+HAU3IIpenUmazXnRAeXe2qBpOp3+nyUZrZXp1kr3wBUnvl0DS8OyFQW/Q==} @@ -3799,60 +3796,60 @@ packages: cpu: [x64] os: [win32] - '@rspack/binding-darwin-arm64@1.6.5': - resolution: {integrity: sha512-DaAJTlaenqZIqFqIYcitn0SzjJ7WpC9234JpiSDZdRyXii9qJJiToVwxSPY/CmkrP0201+aC4pzN4tI9T0Ummw==} + '@rspack/binding-darwin-arm64@1.6.6': + resolution: {integrity: sha512-vGVDP0rlWa2w/gLba/sncVfkCah0HmhdmK5vGj/7sSX0iViwQneA2xjxDHyCNSQrvfq9GJmj4Kmdq/9tGh0KuA==} cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-x64@1.6.5': - resolution: {integrity: sha512-fPVfp7W/GMbHayb5hbefiMI30JxlsqPexOItHGtufHmTCrNne1aHmApspyUZIUUxG36oDRHuGPnfh+IQbHR6+g==} + '@rspack/binding-darwin-x64@1.6.6': + resolution: {integrity: sha512-IcdEG2kOmbPPO70Zl7gDnowDjK7d7C1hWew2vU7dPltr2t1JalRIMnS051lhiur0ULkSxV3cW1zXqv0Oi8AnOg==} cpu: [x64] os: [darwin] - '@rspack/binding-linux-arm64-gnu@1.6.5': - resolution: {integrity: sha512-K68YDoV2e4s+nlrKZxgF0HehiiRwOAGgZFUwJNRMZ7MUrTGMNlPTJlM+bNdaCjDb6GFxBVFcNwIa1sU+0tF1zg==} + '@rspack/binding-linux-arm64-gnu@1.6.6': + resolution: {integrity: sha512-rIguCCtlTcwoFlwheDiUgdImk27spuCRn43zGJogARpM/ZYRFKIuSwFDGUtJT2g0TSLUAHUhWAUqC36NwvrbMQ==} cpu: [arm64] os: [linux] - '@rspack/binding-linux-arm64-musl@1.6.5': - resolution: {integrity: sha512-JPtxFBOq7RRmBIwpdGIStf8iyCILehDsjQtEB0Kkhtm7TsAkVGwtC41GLcNuPxcQBKqNDmD8cy3yLYhXadH2CQ==} + '@rspack/binding-linux-arm64-musl@1.6.6': + resolution: {integrity: sha512-x6X6Gr0fUw6qrJGxZt3Rb6oIX+jd9pdcyp0VbtofcLaqGVQbzustYsYnuLATPOys0q4J/4kWnmEhkjLJHwkhpQ==} cpu: [arm64] os: [linux] - '@rspack/binding-linux-x64-gnu@1.6.5': - resolution: {integrity: sha512-oh4ZNo2HtizZ/E6UK3BEONu20h8VVBw9GAXuWmo1u22cJSihzg+WfRNCMjRDil82LqSsyAgBwnU+dEjEYGKyAA==} + '@rspack/binding-linux-x64-gnu@1.6.6': + resolution: {integrity: sha512-gSlVdASszWHosQKn+nzYOInBijdQboUnmNMGgW9/PijVg3433IvQjzviUuJFno8CMGgrACV9yw+ZFDuK0J57VA==} cpu: [x64] os: [linux] - '@rspack/binding-linux-x64-musl@1.6.5': - resolution: {integrity: sha512-8Xebp5bvPJqjifpkFEAX5nUvoU2JvbMU3gwAkEovRRuvooCXnVT2tqkUBjkR3AhivAGgAxAr9hRzUUz/6QWt3Q==} + '@rspack/binding-linux-x64-musl@1.6.6': + resolution: {integrity: sha512-TZaqVkh7memsTK/hxkOBrbpdzbmBUMea1YnYt++7QjMgco1kWFvAQ+YhAWtIaOaEg8s6C07Lt0Zp8izM2Dja0g==} cpu: [x64] os: [linux] - '@rspack/binding-wasm32-wasi@1.6.5': - resolution: {integrity: sha512-oINZNqzTxM+9dSUOjAORodHXYoJYzXvpaHI2U6ecEmoWaBCs+x3V3Po8DhpNFBwotB+jGlcoVhEHjpg5uaO6pw==} + '@rspack/binding-wasm32-wasi@1.6.6': + resolution: {integrity: sha512-W4mWdlLnYrbUaktyHOGNfATblxMTbgF7CBfDw8PhbDtjd2l8e/TnaHgIDkwITHXAOMEF/QEKfo9FtusbcQJNKw==} cpu: [wasm32] - '@rspack/binding-win32-arm64-msvc@1.6.5': - resolution: {integrity: sha512-UUmep2ayuZxWPdrzkrzAFxVgYUWTB82pa9bkAGyeDO9SNkz8vTpdtbDaTvAzjFb8Pn+ErktDEDBKT57FLjxwxQ==} + '@rspack/binding-win32-arm64-msvc@1.6.6': + resolution: {integrity: sha512-cw5OgxqoDwjoZlk0L3vGEwcjPZsOVFYLwr2ssiC05rsTbhBwxj8coLpAJdvUvbf6C2TTmCB7iPe2sPq1KWD37g==} cpu: [arm64] os: [win32] - '@rspack/binding-win32-ia32-msvc@1.6.5': - resolution: {integrity: sha512-7nx+mMimpmCMstcW7nsyToXy5TK7N+YGPu2W/oioX7qv9ZCuJGTddjzLS84wN8DVrNIirg4mcxpBsmOQMZeHQA==} + '@rspack/binding-win32-ia32-msvc@1.6.6': + resolution: {integrity: sha512-M4ruR+VZ59iy+mPjy6FQPT27cOgeytf3wFBrt7e0suKeNLYGxrNyI9YhgpCTY++SMJsAMgRLGDHoI3ZgWulw1Q==} cpu: [ia32] os: [win32] - '@rspack/binding-win32-x64-msvc@1.6.5': - resolution: {integrity: sha512-pzO7rYFu6f6stgSccolZHiXGTTwKrIGHHNV1ALY1xPRmQEdbHcbMwadeaG99JL2lRLve9iNI+Z9Pr3oDVRN46g==} + '@rspack/binding-win32-x64-msvc@1.6.6': + resolution: {integrity: sha512-q5QTvdhPUh+CA93cQG5zWKRIHMIWPzw+ftFDEwBw52zYdvNAoLniqD8o5Mi8CT0pndhulXgR5aw0Sjd3eMah+A==} cpu: [x64] os: [win32] - '@rspack/binding@1.6.5': - resolution: {integrity: sha512-FzYsr5vdjaVQIlDTxZFlISOQGxl/4grpF2BeiNy60Fpw9eeADeXk55DVacbXPqpiz7Doj6cyhEyMszQOvihrqQ==} + '@rspack/binding@1.6.6': + resolution: {integrity: sha512-noiV+qhyBTVpvG2M4bnOwKk2Ynl6G47Wf7wpCjPCFr87qr3txNwTTnhkEJEU59yj+VvIhbRD2rf5+9TLoT0Wxg==} - '@rspack/core@1.6.5': - resolution: {integrity: sha512-AqaOMA6MTNhqMYYwrhvPA+2uS662SkAi8Rb7B/IFOzh/Z5ooyczL4lUX+qyhAO3ymn50iwM4jikQCf9XfBiaQA==} + '@rspack/core@1.6.6': + resolution: {integrity: sha512-2mR+2YBydlgZ7Q0Rpd6bCC3MBnV9TS0x857K0zIhbDj4BQOqaWVy1n7fx/B3MrS8TR0QCuzKfyDAjNz+XTyJVQ==} engines: {node: '>=18.12.0'} peerDependencies: '@swc/helpers': '>=0.5.1' @@ -4003,17 +4000,17 @@ packages: resolution: {integrity: sha512-Qvys1y3o8/bfL3ikrHnJS9zxdjt0z3POshdBl3967UcflrTqBmnGNkcVk53SlmtJWIfh85fgmrLvGYwZ2YiqNg==} engines: {node: '>= 14'} - '@shikijs/engine-oniguruma@3.15.0': - resolution: {integrity: sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==} + '@shikijs/engine-oniguruma@3.19.0': + resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==} - '@shikijs/langs@3.15.0': - resolution: {integrity: sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==} + '@shikijs/langs@3.19.0': + resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==} - '@shikijs/themes@3.15.0': - resolution: {integrity: sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==} + '@shikijs/themes@3.19.0': + resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==} - '@shikijs/types@3.15.0': - resolution: {integrity: sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==} + '@shikijs/types@3.19.0': + resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -4864,9 +4861,6 @@ packages: '@types/mapbox__geojson-extent@1.0.3': resolution: {integrity: sha512-i7r+3Iencd5PMleZmIRciaN8ntaGvwPA2sHts50YFrZeH9FIJFAjK64HgDaqzrtecmPbMvIltQuwuEbEQfoUJA==} - '@types/mapbox__point-geometry@0.1.4': - resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} - '@types/maplibre-gl@1.14.0': resolution: {integrity: sha512-I6ibscT7UdL1oOqqCz9s1gjcolLaUPkHIIfMLusczTvlsMhjORyS6sE1g4V/NESAOL5KhNQX3/31LJH+OCGjkg==} deprecated: This is a stub types definition. maplibre-gl provides its own type definitions, so you do not need this installed. @@ -4898,9 +4892,6 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - '@types/pbf@3.0.5': - resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} - '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -5026,11 +5017,11 @@ packages: '@vitest/browser': optional: true - '@vitest/expect@4.0.14': - resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} + '@vitest/expect@4.0.15': + resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} - '@vitest/mocker@4.0.14': - resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -5043,18 +5034,24 @@ packages: '@vitest/pretty-format@4.0.14': resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} - '@vitest/runner@4.0.14': - resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + '@vitest/pretty-format@4.0.15': + resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} - '@vitest/snapshot@4.0.14': - resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + '@vitest/runner@4.0.15': + resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} - '@vitest/spy@4.0.14': - resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} + '@vitest/snapshot@4.0.15': + resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} + + '@vitest/spy@4.0.15': + resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} '@vitest/utils@4.0.14': resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} + '@vitest/utils@4.0.15': + resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -5147,8 +5144,8 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@5.0.102: - resolution: {integrity: sha512-snRK3nS5DESOjjpq7S74g8YszWVMzjagfHqlJWZsbtl9PyOS+2XUd8dt2wWg/jdaq/jh0aU66W1mx5qFjUQyEg==} + ai@5.0.106: + resolution: {integrity: sha512-M5obwavxSJJ3tGlAFqI6eltYNJB0D20X6gIBCFx/KVorb/X1fxVVfiZZpZb+Gslu4340droSOjT0aKQFCarNVg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -5182,8 +5179,8 @@ packages: peerDependencies: algoliasearch: '>= 3.1 < 6' - algoliasearch@5.45.0: - resolution: {integrity: sha512-wrj4FGr14heLOYkBKV3Fbq5ZBGuIFeDJkTilYq/G+hH1CSlQBtYvG2X1j67flwv0fUeQJwnWxxRIunSemAZirA==} + algoliasearch@5.46.0: + resolution: {integrity: sha512-7ML6fa2K93FIfifG3GMWhDEwT5qQzPTmoHKCTvhzGEwdbQ4n0yYUWZlLYT75WllTGJCJtNUI0C1ybN4BCegqvg==} engines: {node: '>= 14.0.0'} ansi-align@3.0.1: @@ -5324,8 +5321,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.31: - resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + baseline-browser-mapping@2.9.2: + resolution: {integrity: sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw==} hasBin: true basic-auth@2.0.1: @@ -5389,8 +5386,8 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} bonjour-service@1.3.0: @@ -5417,8 +5414,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5488,8 +5485,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001757: - resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + caniuse-lite@1.0.30001759: + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -5525,9 +5522,6 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - cheap-ruler@4.0.0: - resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==} - cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -5694,15 +5688,15 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.1.0: - resolution: {integrity: sha512-vXiThu1/rlos7EGu8TuNZQEg2e9TvhH9dmS4T4ZVzB7Ao1agEZ6EG3sn5n+hZRYUgduISd1HpngFzAZiDGm5vQ==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} copy-webpack-plugin@11.0.0: @@ -5819,11 +5813,8 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} - csscolorparser@1.0.3: - resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==} - - cssdb@8.4.2: - resolution: {integrity: sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==} + cssdb@8.5.1: + resolution: {integrity: sha512-nFf5vkr7tCF4PZOIqrTlP5Bp+3j+7ad3c0doFlbFIQk0vxcnTriDIF/+qYsZk9EbgX2p8bVJFoLmE3h5qe9jlg==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -6111,8 +6102,8 @@ packages: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} - drizzle-kit@0.31.7: - resolution: {integrity: sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A==} + drizzle-kit@0.31.8: + resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==} hasBin: true drizzle-orm@0.44.7: @@ -6230,8 +6221,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.260: - resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + electron-to-chromium@1.5.264: + resolution: {integrity: sha512-1tEf0nLgltC3iy9wtlYDlQDc5Rg9lEKVjEmIHJ21rI9OcqkvD45K1oyNIRA4rR1z3LgJ7KeGzEBojVcV6m4qjA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -6437,8 +6428,8 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} exsolve@1.0.8: @@ -6522,8 +6513,8 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} find-cache-dir@4.0.0: @@ -6778,9 +6769,6 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - grid-index@1.1.0: - resolution: {integrity: sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==} - gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -6915,6 +6903,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} @@ -6939,8 +6931,8 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - human-id@4.1.2: - resolution: {integrity: sha512-v/J+4Z/1eIJovEBdlV5TYj1IR+ZiohcYGRY+qN/oC9dAfKzVT023N/Bgw37hrKCoVRBvk3bqyzpr2PP5YeTMSg==} + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true human-signals@2.1.0: @@ -7035,8 +7027,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-alphabetical@2.0.1: @@ -7332,8 +7324,8 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - jose@6.1.2: - resolution: {integrity: sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==} + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -7572,8 +7564,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -7606,11 +7598,8 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - mapbox-gl@3.16.0: - resolution: {integrity: sha512-rluV1Zp/0oHf1Y9BV+nePRNnKyTdljko3E19CzO5rBqtQaNUYS0ePCMPRtxOuWRwSdKp3f9NWJkOCjemM8nmjw==} - - maplibre-gl@5.13.0: - resolution: {integrity: sha512-UsIVP34rZdM4TjrjhwBAhbC3HT7AzFx9p/draiAPlLr8/THozZF6WmJnZ9ck4q94uO55z7P7zoGCh+AZVoagsQ==} + maplibre-gl@5.14.0: + resolution: {integrity: sha512-O2ok6N/bQ9NA9nJ22r/PRQQYkUe9JwfDMjBPkQ+8OwsVH4TpA5skIAM2wc0k+rni5lVbAVONVyBvgi1rF2vEPA==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} markdown-extensions@2.0.0: @@ -7651,9 +7640,6 @@ packages: engines: {node: '>= 20'} hasBin: true - martinez-polygon-clipping@0.7.4: - resolution: {integrity: sha512-jBEwrKtA0jTagUZj2bnmb4Yg2s4KnJGRePStgI7bAVjtcipKiF39R4LZ2V/UT61jMYWrTcBhPazexeqd6JAVtw==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -7725,8 +7711,8 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - memfs@4.51.0: - resolution: {integrity: sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A==} + memfs@4.51.1: + resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -8035,8 +8021,8 @@ packages: encoding: optional: true - node-forge@1.3.2: - resolution: {integrity: sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==} + node-forge@1.3.3: + resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} engines: {node: '>= 6.13.0'} node-releases@2.0.27: @@ -8708,8 +8694,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-sort-media-queries@5.2.0: @@ -8776,16 +8762,16 @@ packages: potpack@2.1.0: resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} - preact@10.27.2: - resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==} + preact@10.28.0: + resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -8853,8 +8839,8 @@ packages: resolution: {integrity: sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==} engines: {node: '>=12.20'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -8904,8 +8890,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} rbush@2.0.2: @@ -8927,11 +8913,6 @@ packages: peerDependencies: react: '>=16.8.0' - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} - peerDependencies: - react: ^19.2.0 - react-dom@19.2.1: resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: @@ -9001,8 +8982,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -9022,8 +9003,8 @@ packages: peerDependencies: react: '>=15' - react-router-dom@7.10.0: - resolution: {integrity: sha512-Q4haR150pN/5N75O30iIsRJcr3ef7p7opFaKpcaREy0GQit6uCRu1NEiIFIwnHJQy0bsziRFBweR/5EkmHgVUQ==} + react-router-dom@7.10.1: + resolution: {integrity: sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -9034,8 +9015,8 @@ packages: peerDependencies: react: '>=15' - react-router@7.10.0: - resolution: {integrity: sha512-FVyCOH4IZ0eDDRycODfUqoN8ZSR2LbTvtx6RPsBgzvJ8xAXlMZNCrOFpu+jb8QbtZnpAd/cEki2pwE848pNGxw==} + react-router@7.10.1: + resolution: {integrity: sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -9066,10 +9047,6 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} - engines: {node: '>=0.10.0'} - react@19.2.1: resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} @@ -9413,13 +9390,13 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@0.19.1: + resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} + engines: {node: '>= 0.8.0'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serialize-to-js@3.1.2: - resolution: {integrity: sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==} - engines: {node: '>=4.0.0'} - serve-handler@6.1.6: resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} @@ -9597,9 +9574,6 @@ packages: splaytree-ts@1.0.2: resolution: {integrity: sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA==} - splaytree@0.1.4: - resolution: {integrity: sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==} - split-string@3.1.0: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} @@ -9622,6 +9596,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -9744,8 +9722,8 @@ packages: sweepline-intersections@1.5.0: resolution: {integrity: sha512-AoVmx72QHpKtItPu72TzFL+kcYjd67BPLDoR0LarIk+xyaRg+pDTMFXndIEvZf9xEKnJv6JdhgRMnocoG0D3AQ==} - swr@2.3.6: - resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==} + swr@2.3.7: + resolution: {integrity: sha512-ZEquQ82QvalqTxhBVv/DlAg2mbmUjF4UgpPg9wwk4ufb9rQnZXh1iKyyKBqV6bQGu1Ie7L1QwSYO07qFIa1p+g==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -9811,8 +9789,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} @@ -9822,9 +9801,6 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - tinyqueue@1.2.3: - resolution: {integrity: sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==} - tinyqueue@2.0.3: resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} @@ -9891,38 +9867,38 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-darwin-64@2.6.1: - resolution: {integrity: sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==} + turbo-darwin-64@2.6.2: + resolution: {integrity: sha512-nF9d/YAyrNkyXn9lp3ZtgXPb7fZsik3cUNe/sBvUO0G5YezUS/kDYYw77IdjizDzairz8pL2ITCTUreG2d5iZQ==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.6.1: - resolution: {integrity: sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw==} + turbo-darwin-arm64@2.6.2: + resolution: {integrity: sha512-mmm0jFaVramST26XE1Lk2qjkjvLJHOe9f3TFjqY+aByjMK/ZmKE5WFPuCWo4L3xhwx+16T37rdPP//76loB3oA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.6.1: - resolution: {integrity: sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw==} + turbo-linux-64@2.6.2: + resolution: {integrity: sha512-IUMHjkVRJDUABGpi+iS1Le59aOl5DX88U5UT/mKaE7nNEjG465+a8UtYno56cZnLP+C6BkX4I93LFgYf9syjGQ==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.6.1: - resolution: {integrity: sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA==} + turbo-linux-arm64@2.6.2: + resolution: {integrity: sha512-0qQdZiimMUZj2Gfq87thYu0E02NaNcsB3lcEK/TD70Zzi7AxQoxye664Gis0Uao2j2L9/+05wC2btZ7SoFX3Gw==} cpu: [arm64] os: [linux] - turbo-windows-64@2.6.1: - resolution: {integrity: sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ==} + turbo-windows-64@2.6.2: + resolution: {integrity: sha512-BmMfFmt0VaoZL4NbtDq/dzGfjHsPoGU2+vFiZtkiYsttHY3fd/Dmgnu9PuRyJN1pv2M22q88rXO+dqYRHztLMw==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.6.1: - resolution: {integrity: sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q==} + turbo-windows-arm64@2.6.2: + resolution: {integrity: sha512-0r4s4M/FgLxfjrdLPdqQUur8vZAtaWEi4jhkQ6wCIN2xzA9aee9IKwM53w7CQcjaLvWhT0AU7LTQHjFaHwxiKw==} cpu: [arm64] os: [win32] - turbo@2.6.1: - resolution: {integrity: sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==} + turbo@2.6.2: + resolution: {integrity: sha512-LiQAFS6iWvnY8ViGtoPgduWBeuGH9B32XR4p8H8jxU5PudwyHiiyf1jQW0fCC8gCCTz9itkIbqZLIyUu5AG33w==} hasBin: true type-fest@0.21.3: @@ -9984,8 +9960,8 @@ packages: peerDependencies: typedoc: ^0.28.1 - typedoc@0.28.14: - resolution: {integrity: sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==} + typedoc@0.28.15: + resolution: {integrity: sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -10092,8 +10068,8 @@ packages: unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -10207,46 +10183,6 @@ packages: vite: optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@7.2.6: resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -10287,18 +10223,18 @@ packages: yaml: optional: true - vitest@4.0.14: - resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.14 - '@vitest/browser-preview': 4.0.14 - '@vitest/browser-webdriverio': 4.0.14 - '@vitest/ui': 4.0.14 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -10510,8 +10446,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -10555,14 +10491,14 @@ packages: snapshots: - '@ai-sdk/gateway@2.0.15(zod@4.1.13)': + '@ai-sdk/gateway@2.0.18(zod@4.1.13)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.13) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.13) '@vercel/oidc': 3.0.5 zod: 4.1.13 - '@ai-sdk/provider-utils@3.0.17(zod@4.1.13)': + '@ai-sdk/provider-utils@3.0.18(zod@4.1.13)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 @@ -10573,123 +10509,123 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@2.0.102(react@19.2.0)(zod@4.1.13)': + '@ai-sdk/react@2.0.106(react@19.2.1)(zod@4.1.13)': dependencies: - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.13) - ai: 5.0.102(zod@4.1.13) - react: 19.2.0 - swr: 2.3.6(react@19.2.0) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.13) + ai: 5.0.106(zod@4.1.13) + react: 19.2.1 + swr: 2.3.7(react@19.2.1) throttleit: 2.1.0 optionalDependencies: zod: 4.1.13 - '@algolia/abtesting@1.11.0': + '@algolia/abtesting@1.12.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0)(search-insights@2.17.3)': + '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0)(search-insights@2.17.3)': + '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0)': + '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)': dependencies: - '@algolia/client-search': 5.45.0 - algoliasearch: 5.45.0 + '@algolia/client-search': 5.46.0 + algoliasearch: 5.46.0 - '@algolia/client-abtesting@5.45.0': + '@algolia/client-abtesting@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/client-analytics@5.45.0': + '@algolia/client-analytics@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/client-common@5.45.0': {} + '@algolia/client-common@5.46.0': {} - '@algolia/client-insights@5.45.0': + '@algolia/client-insights@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/client-personalization@5.45.0': + '@algolia/client-personalization@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/client-query-suggestions@5.45.0': + '@algolia/client-query-suggestions@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/client-search@5.45.0': + '@algolia/client-search@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 '@algolia/events@4.0.1': {} - '@algolia/ingestion@1.45.0': + '@algolia/ingestion@1.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/monitoring@1.45.0': + '@algolia/monitoring@1.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/recommend@5.45.0': + '@algolia/recommend@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + '@algolia/client-common': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 - '@algolia/requester-browser-xhr@5.45.0': + '@algolia/requester-browser-xhr@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 + '@algolia/client-common': 5.46.0 - '@algolia/requester-fetch@5.45.0': + '@algolia/requester-fetch@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 + '@algolia/client-common': 5.46.0 - '@algolia/requester-node-http@5.45.0': + '@algolia/requester-node-http@5.46.0': dependencies: - '@algolia/client-common': 5.45.0 + '@algolia/client-common': 5.46.0 '@alloc/quick-lru@5.2.0': {} @@ -10737,7 +10673,7 @@ snapshots: '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 + jsesc: 3.0.2 '@babel/helper-annotate-as-pure@7.27.3': dependencies: @@ -10747,7 +10683,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.0 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -11458,20 +11394,20 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.1.0)': + '@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.18 '@standard-schema/spec': 1.0.0 better-call: 1.1.4(zod@4.1.13) - jose: 6.1.2 + jose: 6.1.3 kysely: 0.28.8 nanostores: 1.1.0 zod: 4.1.13 - '@better-auth/telemetry@1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.1.0))': + '@better-auth/telemetry@1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))': dependencies: - '@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.1.0) + '@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.18 @@ -11516,9 +11452,9 @@ snapshots: '@borewit/text-codec@0.1.1': {} - '@changesets/apply-release-plan@7.0.13': + '@changesets/apply-release-plan@7.0.14': dependencies: - '@changesets/config': 3.1.1 + '@changesets/config': 3.1.2 '@changesets/get-version-range-type': 0.4.0 '@changesets/git': 3.0.4 '@changesets/should-skip-package': 0.1.2 @@ -11545,19 +11481,19 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.7(@types/node@24.10.1)': + '@changesets/cli@2.29.8(@types/node@24.10.1)': dependencies: - '@changesets/apply-release-plan': 7.0.13 + '@changesets/apply-release-plan': 7.0.14 '@changesets/assemble-release-plan': 6.0.9 '@changesets/changelog-git': 0.2.1 - '@changesets/config': 3.1.1 + '@changesets/config': 3.1.2 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.13 + '@changesets/get-release-plan': 4.0.14 '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.5 + '@changesets/read': 0.6.6 '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 @@ -11578,7 +11514,7 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@changesets/config@3.1.1': + '@changesets/config@3.1.2': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 @@ -11599,12 +11535,12 @@ snapshots: picocolors: 1.1.1 semver: 7.7.3 - '@changesets/get-release-plan@4.0.13': + '@changesets/get-release-plan@4.0.14': dependencies: '@changesets/assemble-release-plan': 6.0.9 - '@changesets/config': 3.1.1 + '@changesets/config': 3.1.2 '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.5 + '@changesets/read': 0.6.6 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 @@ -11622,10 +11558,10 @@ snapshots: dependencies: picocolors: 1.1.1 - '@changesets/parse@0.4.1': + '@changesets/parse@0.4.2': dependencies: '@changesets/types': 6.1.0 - js-yaml: 3.14.2 + js-yaml: 4.1.1 '@changesets/pre@2.0.2': dependencies: @@ -11634,11 +11570,11 @@ snapshots: '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.5': + '@changesets/read@0.6.6': dependencies: '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.1 + '@changesets/parse': 0.4.2 '@changesets/types': 6.1.0 fs-extra: 7.0.1 p-filter: 2.1.0 @@ -11657,7 +11593,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 fs-extra: 7.0.1 - human-id: 4.1.2 + human-id: 4.1.3 prettier: 2.8.8 '@colors/colors@1.5.0': @@ -11704,9 +11640,9 @@ snapshots: '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.6)': dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.6)': dependencies: @@ -11812,9 +11748,9 @@ snapshots: '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.6)': dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.6)': dependencies: @@ -11906,7 +11842,7 @@ snapshots: '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.6)': dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.6)': dependencies: @@ -11939,13 +11875,13 @@ snapshots: dependencies: postcss: 8.5.6 - '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/utilities@2.0.0(postcss@8.5.6)': dependencies: @@ -11955,33 +11891,33 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/core@4.3.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docsearch/core@4.3.1(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': optionalDependencies: '@types/react': 19.2.7 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) '@docsearch/css@4.3.2': {} - '@docsearch/react@4.3.2(@algolia/client-search@5.45.0)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)': + '@docsearch/react@4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3)': dependencies: - '@ai-sdk/react': 2.0.102(react@19.2.0)(zod@4.1.13) - '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.45.0)(algoliasearch@5.45.0)(search-insights@2.17.3) - '@docsearch/core': 4.3.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@ai-sdk/react': 2.0.106(react@19.2.1)(zod@4.1.13) + '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.0)(algoliasearch@5.46.0)(search-insights@2.17.3) + '@docsearch/core': 4.3.1(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@docsearch/css': 4.3.2 - ai: 5.0.102(zod@4.1.13) - algoliasearch: 5.45.0 + ai: 5.0.106(zod@4.1.13) + algoliasearch: 5.46.0 marked: 16.4.2 zod: 4.1.13 optionalDependencies: '@types/react': 19.2.7 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/babel@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.28.5 @@ -11994,7 +11930,7 @@ snapshots: '@babel/runtime-corejs3': 7.28.4 '@babel/traverse': 7.28.5 '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.3.2 tslib: 2.8.1 @@ -12007,18 +11943,18 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/bundler@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: '@babel/core': 7.28.5 - '@docusaurus/babel': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/babel': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@docusaurus/cssnano-preset': 3.9.2 '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.103.0(@swc/core@1.15.3)) clean-css: 5.3.3 copy-webpack-plugin: 11.0.0(webpack@5.103.0(@swc/core@1.15.3)) - css-loader: 6.11.0(@rspack/core@1.6.5)(webpack@5.103.0(@swc/core@1.15.3)) + css-loader: 6.11.0(@rspack/core@1.6.6)(webpack@5.103.0(@swc/core@1.15.3)) css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.103.0(@swc/core@1.15.3)) cssnano: 6.1.2(postcss@8.5.6) file-loader: 6.2.0(webpack@5.103.0(@swc/core@1.15.3)) @@ -12034,7 +11970,7 @@ snapshots: webpack: 5.103.0(@swc/core@1.15.3) webpackbar: 6.0.1(webpack@5.103.0(@swc/core@1.15.3)) optionalDependencies: - '@docusaurus/faster': 3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) + '@docusaurus/faster': 3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) transitivePeerDependencies: - '@parcel/css' - '@rspack/core' @@ -12050,16 +11986,16 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/core@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/babel': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/bundler': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docusaurus/babel': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/bundler': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.0) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.1) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 @@ -12074,20 +12010,20 @@ snapshots: execa: 5.1.1 fs-extra: 11.3.2 html-tags: 3.3.1 - html-webpack-plugin: 5.6.5(@rspack/core@1.6.5)(webpack@5.103.0(@swc/core@1.15.3)) + html-webpack-plugin: 5.6.5(@rspack/core@1.6.6)(webpack@5.103.0(@swc/core@1.15.3)) leven: 3.1.0 lodash: 4.17.21 open: 8.4.2 p-map: 4.0.0 prompts: 2.4.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.0)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.0))(webpack@5.103.0(@swc/core@1.15.3)) - react-router: 5.3.4(react@19.2.0) - react-router-config: 5.1.1(react-router@5.3.4(react@19.2.0))(react@19.2.0) - react-router-dom: 5.3.4(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.1)' + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.1))(webpack@5.103.0(@swc/core@1.15.3)) + react-router: 5.3.4(react@19.2.1) + react-router-config: 5.1.1(react-router@5.3.4(react@19.2.1))(react@19.2.1) + react-router-dom: 5.3.4(react@19.2.1) semver: 7.7.3 serve-handler: 6.1.6 tinypool: 1.1.1 @@ -12121,13 +12057,13 @@ snapshots: postcss-sort-media-queries: 5.2.0(postcss@8.5.6) tslib: 2.8.1 - '@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))': + '@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))': dependencies: - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@rspack/core': 1.6.5 + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@rspack/core': 1.6.6 '@swc/core': 1.15.3 '@swc/html': 1.15.3 - browserslist: 4.28.0 + browserslist: 4.28.1 lightningcss: 1.30.2 swc-loader: 0.2.6(@swc/core@1.15.3)(webpack@5.103.0(@swc/core@1.15.3)) tslib: 2.8.1 @@ -12143,11 +12079,11 @@ snapshots: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/mdx-loader@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 @@ -12157,8 +12093,8 @@ snapshots: image-size: 2.0.2 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) rehype-raw: 7.0.0 remark-directive: 3.0.1 remark-emoji: 4.0.1 @@ -12178,17 +12114,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/module-type-aliases@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/history': 4.7.11 '@types/react': 19.2.7 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.0)' + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.1)' transitivePeerDependencies: - '@swc/core' - esbuild @@ -12196,23 +12132,23 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.3.2 lodash: 4.17.21 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) schema-dts: 1.1.5 srcset: 4.0.0 tslib: 2.8.1 @@ -12237,24 +12173,24 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.2 js-yaml: 4.1.1 lodash: 4.17.21 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 @@ -12277,16 +12213,16 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-content-pages@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) fs-extra: 11.3.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 webpack: 5.103.0(@swc/core@1.15.3) transitivePeerDependencies: @@ -12307,12 +12243,12 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-css-cascade-layers@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12334,15 +12270,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-debug@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) fs-extra: 11.3.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-json-view-lite: 2.5.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-json-view-lite: 2.5.0(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12362,13 +12298,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-google-analytics@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12388,14 +12324,14 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-google-gtag@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/gtag.js': 0.0.12 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12415,13 +12351,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-google-tag-manager@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12441,17 +12377,17 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-sitemap@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) fs-extra: 11.3.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) sitemap: 7.1.2 tslib: 2.8.1 transitivePeerDependencies: @@ -12472,16 +12408,16 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/plugin-svgr@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/webpack': 8.1.0(typescript@5.9.3) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 webpack: 5.103.0(@swc/core@1.15.3) transitivePeerDependencies: @@ -12502,25 +12438,25 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.45.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-content-pages': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-css-cascade-layers': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-debug': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-google-analytics': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-google-gtag': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-google-tag-manager': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-sitemap': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-svgr': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/theme-classic': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.45.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.46.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3)(typescript@5.9.3)': + dependencies: + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-content-pages': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-css-cascade-layers': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-debug': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-google-analytics': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-google-gtag': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-google-tag-manager': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-sitemap': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-svgr': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/theme-classic': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.46.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' @@ -12542,37 +12478,37 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/react-loadable@6.0.0(react@19.2.0)': + '@docusaurus/react-loadable@6.0.0(react@19.2.1)': dependencies: '@types/react': 19.2.7 - react: 19.2.0 + react: 19.2.1 - '@docusaurus/theme-classic@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@docusaurus/theme-classic@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/plugin-content-pages': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/plugin-content-pages': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@docusaurus/theme-translations': 3.9.2 - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.1) clsx: 2.1.1 infima: 0.2.0-alpha.45 lodash: 4.17.21 nprogress: 0.2.0 postcss: 8.5.6 - prism-react-renderer: 2.4.1(react@19.2.0) + prism-react-renderer: 2.4.1(react@19.2.1) prismjs: 1.30.0 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-router-dom: 5.3.4(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-router-dom: 5.3.4(react@19.2.1) rtlcss: 4.3.0 tslib: 2.8.1 utility-types: 3.11.0 @@ -12594,21 +12530,21 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/mdx-loader': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/module-type-aliases': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/history': 4.7.11 '@types/react': 19.2.7 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 - prism-react-renderer: 2.4.1(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + prism-react-renderer: 2.4.1(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: @@ -12618,24 +12554,24 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.45.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.9.3)': + '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.46.0)(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3)(typescript@5.9.3)': dependencies: - '@docsearch/react': 4.3.2(@algolia/client-search@5.45.0)(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3) - '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@docsearch/react': 4.3.2(@algolia/client-search@5.46.0)(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(search-insights@2.17.3) + '@docusaurus/core': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0))(@rspack/core@1.6.5)(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/plugin-content-docs': 3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@docusaurus/faster@3.9.2(@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1))(@rspack/core@1.6.6)(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@docusaurus/theme-translations': 3.9.2 - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - algoliasearch: 5.45.0 - algoliasearch-helper: 3.26.1(algoliasearch@5.45.0) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-validation': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + algoliasearch: 5.46.0 + algoliasearch-helper: 3.26.1(algoliasearch@5.46.0) clsx: 2.1.1 eta: 2.2.0 fs-extra: 11.3.2 lodash: 4.17.21 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: @@ -12666,7 +12602,7 @@ snapshots: '@docusaurus/tsconfig@3.9.2': {} - '@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/types@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 @@ -12674,9 +12610,9 @@ snapshots: '@types/react': 19.2.7 commander: 5.1.0 joi: 17.13.3 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)' + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)' utility-types: 3.11.0 webpack: 5.103.0(@swc/core@1.15.3) webpack-merge: 5.10.0 @@ -12687,9 +12623,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/utils-common@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' @@ -12700,11 +12636,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/utils-validation@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/utils': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) fs-extra: 11.3.2 joi: 17.13.3 js-yaml: 4.1.1 @@ -12719,11 +12655,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@docusaurus/utils@3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@docusaurus/types': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@docusaurus/utils-common': 3.9.2(@swc/core@1.15.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) escape-string-regexp: 4.0.0 execa: 5.1.1 file-loader: 6.2.0(webpack@5.103.0(@swc/core@1.15.3)) @@ -12958,12 +12894,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@gerrit0/mini-shiki@3.15.0': + '@gerrit0/mini-shiki@3.19.0': dependencies: - '@shikijs/engine-oniguruma': 3.15.0 - '@shikijs/langs': 3.15.0 - '@shikijs/themes': 3.15.0 - '@shikijs/types': 3.15.0 + '@shikijs/engine-oniguruma': 3.19.0 + '@shikijs/langs': 3.19.0 + '@shikijs/themes': 3.19.0 + '@shikijs/types': 3.19.0 '@shikijs/vscode-textmate': 10.0.2 '@hapi/hoek@9.3.0': {} @@ -13115,9 +13051,6 @@ snapshots: '@mapbox/jsonlint-lines-primitives@2.0.2': {} - '@mapbox/mapbox-gl-supported@3.0.0': - optional: true - '@mapbox/point-geometry@1.1.0': {} '@mapbox/tiny-sdf@2.0.7': {} @@ -13132,11 +13065,11 @@ snapshots: '@mapbox/whoots-js@3.1.0': {} - '@maplibre/maplibre-gl-geocoder@1.9.1(maplibre-gl@5.13.0)': + '@maplibre/maplibre-gl-geocoder@1.9.1(maplibre-gl@5.14.0)': dependencies: events: 3.3.0 lodash.debounce: 4.0.8 - maplibre-gl: 5.13.0 + maplibre-gl: 5.14.0 subtag: 0.5.0 suggestions-list: 0.0.2 xtend: 4.0.2 @@ -13160,7 +13093,7 @@ snapshots: rw: 1.3.3 tinyqueue: 3.0.0 - '@maplibre/mlt@1.1.0': + '@maplibre/mlt@1.1.2': dependencies: '@mapbox/point-geometry': 1.1.0 @@ -13204,38 +13137,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.0)': + '@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.1)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.2.7 - react: 19.2.0 + react: 19.2.1 '@mjackson/node-fetch-server@0.2.0': {} - '@module-federation/error-codes@0.21.4': {} + '@module-federation/error-codes@0.21.6': {} - '@module-federation/runtime-core@0.21.4': + '@module-federation/runtime-core@0.21.6': dependencies: - '@module-federation/error-codes': 0.21.4 - '@module-federation/sdk': 0.21.4 + '@module-federation/error-codes': 0.21.6 + '@module-federation/sdk': 0.21.6 - '@module-federation/runtime-tools@0.21.4': + '@module-federation/runtime-tools@0.21.6': dependencies: - '@module-federation/runtime': 0.21.4 - '@module-federation/webpack-bundler-runtime': 0.21.4 + '@module-federation/runtime': 0.21.6 + '@module-federation/webpack-bundler-runtime': 0.21.6 - '@module-federation/runtime@0.21.4': + '@module-federation/runtime@0.21.6': dependencies: - '@module-federation/error-codes': 0.21.4 - '@module-federation/runtime-core': 0.21.4 - '@module-federation/sdk': 0.21.4 + '@module-federation/error-codes': 0.21.6 + '@module-federation/runtime-core': 0.21.6 + '@module-federation/sdk': 0.21.6 - '@module-federation/sdk@0.21.4': {} + '@module-federation/sdk@0.21.6': {} - '@module-federation/webpack-bundler-runtime@0.21.4': + '@module-federation/webpack-bundler-runtime@0.21.6': dependencies: - '@module-federation/runtime': 0.21.4 - '@module-federation/sdk': 0.21.4 + '@module-federation/runtime': 0.21.6 + '@module-federation/sdk': 0.21.6 '@napi-rs/wasm-runtime@1.0.7': dependencies: @@ -13697,7 +13630,7 @@ snapshots: aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.1) optionalDependencies: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) @@ -13834,7 +13767,7 @@ snapshots: aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.1) optionalDependencies: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) @@ -13933,7 +13866,7 @@ snapshots: aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.1) optionalDependencies: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) @@ -14090,7 +14023,7 @@ snapshots: aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.1(@types/react@19.2.7)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.1) optionalDependencies: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) @@ -14421,7 +14354,7 @@ snapshots: '@react-email/render@1.4.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: html-to-text: 9.0.5 - prettier: 3.6.2 + prettier: 3.7.4 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-promise-suspense: 0.3.4 @@ -14442,7 +14375,7 @@ snapshots: dependencies: react: 19.2.1 - '@react-router/dev@7.10.0(@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))(yaml@2.8.1)': + '@react-router/dev@7.10.1(@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(yaml@2.8.2)': dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.28.5 @@ -14451,7 +14384,7 @@ snapshots: '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 - '@react-router/node': 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@react-router/node': 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) '@remix-run/node-fetch-server': 0.9.0 arg: 5.0.2 babel-dead-code-elimination: 1.0.10 @@ -14466,16 +14399,16 @@ snapshots: pathe: 1.1.2 picocolors: 1.1.1 pkg-types: 2.3.0 - prettier: 3.6.2 + prettier: 3.7.4 react-refresh: 0.14.2 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) semver: 7.7.3 tinyglobby: 0.2.15 valibot: 1.2.0(typescript@5.9.3) - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) optionalDependencies: - '@react-router/serve': 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@react-router/serve': 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - '@types/node' @@ -14492,38 +14425,38 @@ snapshots: - tsx - yaml - '@react-router/express@7.10.0(express@4.21.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': + '@react-router/express@7.10.1(express@4.22.1)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': dependencies: - '@react-router/node': 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) - express: 4.21.2 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-router/node': 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + express: 4.22.1 + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) optionalDependencies: typescript: 5.9.3 - '@react-router/fs-routes@7.10.0(@react-router/dev@7.10.0(@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))(yaml@2.8.1))(typescript@5.9.3)': + '@react-router/fs-routes@7.10.1(@react-router/dev@7.10.1(@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3)': dependencies: - '@react-router/dev': 7.10.0(@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))(yaml@2.8.1) + '@react-router/dev': 7.10.1(@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(terser@5.44.1)(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))(yaml@2.8.2) minimatch: 9.0.5 optionalDependencies: typescript: 5.9.3 - '@react-router/node@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': + '@react-router/node@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) optionalDependencies: typescript: 5.9.3 - '@react-router/serve@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': + '@react-router/serve@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - '@react-router/express': 7.10.0(express@4.21.2)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) - '@react-router/node': 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@react-router/express': 7.10.1(express@4.22.1)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@react-router/node': 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) compression: 1.8.1 - express: 4.21.2 + express: 4.22.1 get-port: 5.1.1 morgan: 1.10.1 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -14661,55 +14594,55 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@rspack/binding-darwin-arm64@1.6.5': + '@rspack/binding-darwin-arm64@1.6.6': optional: true - '@rspack/binding-darwin-x64@1.6.5': + '@rspack/binding-darwin-x64@1.6.6': optional: true - '@rspack/binding-linux-arm64-gnu@1.6.5': + '@rspack/binding-linux-arm64-gnu@1.6.6': optional: true - '@rspack/binding-linux-arm64-musl@1.6.5': + '@rspack/binding-linux-arm64-musl@1.6.6': optional: true - '@rspack/binding-linux-x64-gnu@1.6.5': + '@rspack/binding-linux-x64-gnu@1.6.6': optional: true - '@rspack/binding-linux-x64-musl@1.6.5': + '@rspack/binding-linux-x64-musl@1.6.6': optional: true - '@rspack/binding-wasm32-wasi@1.6.5': + '@rspack/binding-wasm32-wasi@1.6.6': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rspack/binding-win32-arm64-msvc@1.6.5': + '@rspack/binding-win32-arm64-msvc@1.6.6': optional: true - '@rspack/binding-win32-ia32-msvc@1.6.5': + '@rspack/binding-win32-ia32-msvc@1.6.6': optional: true - '@rspack/binding-win32-x64-msvc@1.6.5': + '@rspack/binding-win32-x64-msvc@1.6.6': optional: true - '@rspack/binding@1.6.5': + '@rspack/binding@1.6.6': optionalDependencies: - '@rspack/binding-darwin-arm64': 1.6.5 - '@rspack/binding-darwin-x64': 1.6.5 - '@rspack/binding-linux-arm64-gnu': 1.6.5 - '@rspack/binding-linux-arm64-musl': 1.6.5 - '@rspack/binding-linux-x64-gnu': 1.6.5 - '@rspack/binding-linux-x64-musl': 1.6.5 - '@rspack/binding-wasm32-wasi': 1.6.5 - '@rspack/binding-win32-arm64-msvc': 1.6.5 - '@rspack/binding-win32-ia32-msvc': 1.6.5 - '@rspack/binding-win32-x64-msvc': 1.6.5 - - '@rspack/core@1.6.5': - dependencies: - '@module-federation/runtime-tools': 0.21.4 - '@rspack/binding': 1.6.5 + '@rspack/binding-darwin-arm64': 1.6.6 + '@rspack/binding-darwin-x64': 1.6.6 + '@rspack/binding-linux-arm64-gnu': 1.6.6 + '@rspack/binding-linux-arm64-musl': 1.6.6 + '@rspack/binding-linux-x64-gnu': 1.6.6 + '@rspack/binding-linux-x64-musl': 1.6.6 + '@rspack/binding-wasm32-wasi': 1.6.6 + '@rspack/binding-win32-arm64-msvc': 1.6.6 + '@rspack/binding-win32-ia32-msvc': 1.6.6 + '@rspack/binding-win32-x64-msvc': 1.6.6 + + '@rspack/core@1.6.6': + dependencies: + '@module-federation/runtime-tools': 0.21.6 + '@rspack/binding': 1.6.6 '@rspack/lite-tapable': 1.1.0 '@rspack/lite-tapable@1.1.0': {} @@ -14885,13 +14818,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@sentry/react-router@10.28.0(@react-router/node@7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': + '@sentry/react-router@10.28.0(@react-router/node@7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3))(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 - '@react-router/node': 7.10.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@react-router/node': 7.10.1(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) '@sentry/browser': 10.28.0 '@sentry/cli': 2.58.2 '@sentry/core': 10.28.0 @@ -14900,7 +14833,7 @@ snapshots: '@sentry/vite-plugin': 4.6.1 glob: 11.1.0 react: 19.2.1 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) transitivePeerDependencies: - encoding - supports-color @@ -14920,20 +14853,20 @@ snapshots: - encoding - supports-color - '@shikijs/engine-oniguruma@3.15.0': + '@shikijs/engine-oniguruma@3.19.0': dependencies: - '@shikijs/types': 3.15.0 + '@shikijs/types': 3.19.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.15.0': + '@shikijs/langs@3.19.0': dependencies: - '@shikijs/types': 3.15.0 + '@shikijs/types': 3.19.0 - '@shikijs/themes@3.15.0': + '@shikijs/themes@3.19.0': dependencies: - '@shikijs/types': 3.15.0 + '@shikijs/types': 3.19.0 - '@shikijs/types@3.15.0': + '@shikijs/types@3.19.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -14956,13 +14889,13 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@slorber/react-helmet-async@1.3.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@babel/runtime': 7.28.4 invariant: 2.2.4 prop-types: 15.8.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) react-fast-compare: 3.2.2 shallowequal: 1.1.0 @@ -15239,12 +15172,12 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))': + '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) '@tanstack/react-table@8.21.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: @@ -16516,12 +16449,9 @@ snapshots: dependencies: '@types/geojson': 7946.0.16 - '@types/mapbox__point-geometry@0.1.4': - optional: true - '@types/maplibre-gl@1.14.0': dependencies: - maplibre-gl: 5.13.0 + maplibre-gl: 5.14.0 '@types/mdast@4.0.4': dependencies: @@ -16549,9 +16479,6 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/pbf@3.0.5': - optional: true - '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.15.6 @@ -16666,22 +16593,20 @@ snapshots: '@vercel/oidc@3.0.5': {} - '@vis.gl/react-mapbox@8.1.0(mapbox-gl@3.16.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@vis.gl/react-mapbox@8.1.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - mapbox-gl: 3.16.0 - '@vis.gl/react-maplibre@8.1.0(maplibre-gl@5.13.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@vis.gl/react-maplibre@8.1.0(maplibre-gl@5.14.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@maplibre/maplibre-gl-style-spec': 19.3.3 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) optionalDependencies: - maplibre-gl: 5.13.0 + maplibre-gl: 5.14.0 - '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))': + '@vitest/coverage-v8@4.0.14(vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 @@ -16694,49 +16619,58 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.14': + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.14(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.14 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) '@vitest/pretty-format@4.0.14': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.14': + '@vitest/pretty-format@4.0.15': dependencies: - '@vitest/utils': 4.0.14 + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.15': + dependencies: + '@vitest/utils': 4.0.15 pathe: 2.0.3 - '@vitest/snapshot@4.0.14': + '@vitest/snapshot@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.14 + '@vitest/pretty-format': 4.0.15 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.14': {} + '@vitest/spy@4.0.15': {} '@vitest/utils@4.0.14': dependencies: '@vitest/pretty-format': 4.0.14 tinyrainbow: 3.0.3 + '@vitest/utils@4.0.15': + dependencies: + '@vitest/pretty-format': 4.0.15 + tinyrainbow: 3.0.3 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -16853,11 +16787,11 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@5.0.102(zod@4.1.13): + ai@5.0.106(zod@4.1.13): dependencies: - '@ai-sdk/gateway': 2.0.15(zod@4.1.13) + '@ai-sdk/gateway': 2.0.18(zod@4.1.13) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.13) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.13) '@opentelemetry/api': 1.9.0 zod: 4.1.13 @@ -16888,27 +16822,27 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch-helper@3.26.1(algoliasearch@5.45.0): + algoliasearch-helper@3.26.1(algoliasearch@5.46.0): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 5.45.0 - - algoliasearch@5.45.0: - dependencies: - '@algolia/abtesting': 1.11.0 - '@algolia/client-abtesting': 5.45.0 - '@algolia/client-analytics': 5.45.0 - '@algolia/client-common': 5.45.0 - '@algolia/client-insights': 5.45.0 - '@algolia/client-personalization': 5.45.0 - '@algolia/client-query-suggestions': 5.45.0 - '@algolia/client-search': 5.45.0 - '@algolia/ingestion': 1.45.0 - '@algolia/monitoring': 1.45.0 - '@algolia/recommend': 5.45.0 - '@algolia/requester-browser-xhr': 5.45.0 - '@algolia/requester-fetch': 5.45.0 - '@algolia/requester-node-http': 5.45.0 + algoliasearch: 5.46.0 + + algoliasearch@5.46.0: + dependencies: + '@algolia/abtesting': 1.12.0 + '@algolia/client-abtesting': 5.46.0 + '@algolia/client-analytics': 5.46.0 + '@algolia/client-common': 5.46.0 + '@algolia/client-insights': 5.46.0 + '@algolia/client-personalization': 5.46.0 + '@algolia/client-query-suggestions': 5.46.0 + '@algolia/client-search': 5.46.0 + '@algolia/ingestion': 1.46.0 + '@algolia/monitoring': 1.46.0 + '@algolia/recommend': 5.46.0 + '@algolia/requester-browser-xhr': 5.46.0 + '@algolia/requester-fetch': 5.46.0 + '@algolia/requester-node-http': 5.46.0 ansi-align@3.0.1: dependencies: @@ -16988,8 +16922,8 @@ snapshots: autoprefixer@10.4.22(postcss@8.5.6): dependencies: - browserslist: 4.28.0 - caniuse-lite: 1.0.30001757 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001759 fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -17056,7 +16990,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.31: {} + baseline-browser-mapping@2.9.2: {} basic-auth@2.0.1: dependencies: @@ -17066,15 +17000,15 @@ snapshots: better-auth@1.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - '@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.1.0)) + '@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0) + '@better-auth/telemetry': 1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.18 '@noble/ciphers': 2.0.1 '@noble/hashes': 2.0.1 better-call: 1.1.4(zod@4.1.13) defu: 6.1.4 - jose: 6.1.2 + jose: 6.1.3 kysely: 0.28.8 ms: 4.0.0-nightly.202508271359 nanostores: 1.1.0 @@ -17102,18 +17036,18 @@ snapshots: binary-extensions@2.3.0: {} - body-parser@1.20.3: + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 + qs: 6.14.0 + raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: @@ -17161,13 +17095,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.0: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.8.31 - caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.260 + baseline-browser-mapping: 2.9.2 + caniuse-lite: 1.0.30001759 + electron-to-chromium: 1.5.264 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.28.0) + update-browserslist-db: 1.2.2(browserslist@4.28.1) buffer-from@1.1.2: {} @@ -17234,12 +17168,12 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.28.0 - caniuse-lite: 1.0.30001757 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001759 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001757: {} + caniuse-lite@1.0.30001759: {} ccount@2.0.1: {} @@ -17264,9 +17198,6 @@ snapshots: chardet@2.1.1: {} - cheap-ruler@4.0.0: - optional: true - cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -17438,11 +17369,11 @@ snapshots: convert-source-map@2.0.0: {} - cookie-signature@1.0.6: {} + cookie-signature@1.0.7: {} - cookie@0.7.1: {} + cookie@0.7.2: {} - cookie@1.1.0: {} + cookie@1.1.1: {} copy-webpack-plugin@11.0.0(webpack@5.103.0(@swc/core@1.15.3)): dependencies: @@ -17456,7 +17387,7 @@ snapshots: core-js-compat@3.47.0: dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 core-js-pure@3.47.0: {} @@ -17486,7 +17417,7 @@ snapshots: css-blank-pseudo@7.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 css-declaration-sorter@7.3.0(postcss@8.5.6): dependencies: @@ -17494,12 +17425,12 @@ snapshots: css-has-pseudo@7.0.3(postcss@8.5.6): dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - css-loader@6.11.0(@rspack/core@1.6.5)(webpack@5.103.0(@swc/core@1.15.3)): + css-loader@6.11.0(@rspack/core@1.6.6)(webpack@5.103.0(@swc/core@1.15.3)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -17510,7 +17441,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - '@rspack/core': 1.6.5 + '@rspack/core': 1.6.6 webpack: 5.103.0(@swc/core@1.15.3) css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.103.0(@swc/core@1.15.3)): @@ -17557,17 +17488,14 @@ snapshots: css-what@6.2.2: {} - csscolorparser@1.0.3: - optional: true - - cssdb@8.4.2: {} + cssdb@8.5.1: {} cssesc@3.0.0: {} cssnano-preset-advanced@6.1.2(postcss@8.5.6): dependencies: autoprefixer: 10.4.22(postcss@8.5.6) - browserslist: 4.28.0 + browserslist: 4.28.1 cssnano-preset-default: 6.1.2(postcss@8.5.6) postcss: 8.5.6 postcss-discard-unused: 6.0.5(postcss@8.5.6) @@ -17577,7 +17505,7 @@ snapshots: cssnano-preset-default@6.1.2(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 css-declaration-sorter: 7.3.0(postcss@8.5.6) cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 @@ -17785,10 +17713,10 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 - docusaurus-plugin-typedoc@1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3))): + docusaurus-plugin-typedoc@1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.15(typescript@5.9.3))): dependencies: - typedoc-docusaurus-theme: 1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3))) - typedoc-plugin-markdown: 4.9.0(typedoc@0.28.14(typescript@5.9.3)) + typedoc-docusaurus-theme: 1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.15(typescript@5.9.3))) + typedoc-plugin-markdown: 4.9.0(typedoc@0.28.15(typescript@5.9.3)) dom-converter@0.2.0: dependencies: @@ -17846,7 +17774,7 @@ snapshots: dotenv@17.2.3: {} - drizzle-kit@0.31.7: + drizzle-kit@0.31.8: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 @@ -17886,7 +17814,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.260: {} + electron-to-chromium@1.5.264: {} emoji-regex@8.0.0: {} @@ -18176,36 +18104,36 @@ snapshots: expect-type@1.2.2: {} - express@4.21.2: + express@4.22.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.3 + body-parser: 1.20.4 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 + cookie: 0.7.2 + cookie-signature: 1.0.7 debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.1 + finalhandler: 1.3.2 fresh: 0.5.2 - http-errors: 2.0.0 + http-errors: 2.0.1 merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.13.0 + qs: 6.14.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.0 + send: 0.19.1 serve-static: 1.16.2 setprototypeof: 1.2.0 - statuses: 2.0.1 + statuses: 2.0.2 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 @@ -18290,14 +18218,14 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.1: + finalhandler@1.3.2: dependencies: debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.1 + statuses: 2.0.2 unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -18579,9 +18507,6 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - grid-index@1.1.0: - optional: true - gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -18764,7 +18689,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.5(@rspack/core@1.6.5)(webpack@5.103.0(@swc/core@1.15.3)): + html-webpack-plugin@5.6.5(@rspack/core@1.6.6)(webpack@5.103.0(@swc/core@1.15.3)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -18772,7 +18697,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.0 optionalDependencies: - '@rspack/core': 1.6.5 + '@rspack/core': 1.6.6 webpack: 5.103.0(@swc/core@1.15.3) htmlparser2@6.1.0: @@ -18808,6 +18733,14 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-parser-js@0.5.10: {} http-proxy-middleware@2.0.9(@types/express@4.17.25): @@ -18842,7 +18775,7 @@ snapshots: transitivePeerDependencies: - supports-color - human-id@4.1.2: {} + human-id@4.1.3: {} human-signals@2.1.0: {} @@ -18912,7 +18845,7 @@ snapshots: ipaddr.js@1.9.1: {} - ipaddr.js@2.2.0: {} + ipaddr.js@2.3.0: {} is-alphabetical@2.0.1: {} @@ -19189,7 +19122,7 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - jose@6.1.2: {} + jose@6.1.3: {} js-tokens@4.0.0: {} @@ -19372,15 +19305,15 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lucide-react@0.552.0(react@19.2.0): + lucide-react@0.552.0(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 lucide-react@0.554.0(react@19.2.1): dependencies: @@ -19406,38 +19339,7 @@ snapshots: dependencies: semver: 7.7.3 - mapbox-gl@3.16.0: - dependencies: - '@mapbox/jsonlint-lines-primitives': 2.0.2 - '@mapbox/mapbox-gl-supported': 3.0.0 - '@mapbox/point-geometry': 1.1.0 - '@mapbox/tiny-sdf': 2.0.7 - '@mapbox/unitbezier': 0.0.1 - '@mapbox/vector-tile': 2.0.4 - '@mapbox/whoots-js': 3.1.0 - '@types/geojson': 7946.0.16 - '@types/geojson-vt': 3.2.5 - '@types/mapbox__point-geometry': 0.1.4 - '@types/pbf': 3.0.5 - '@types/supercluster': 7.1.3 - cheap-ruler: 4.0.0 - csscolorparser: 1.0.3 - earcut: 3.0.2 - geojson-vt: 4.0.2 - gl-matrix: 3.4.4 - grid-index: 1.1.0 - kdbush: 4.0.2 - martinez-polygon-clipping: 0.7.4 - murmurhash-js: 1.0.0 - pbf: 4.0.1 - potpack: 2.1.0 - quickselect: 3.0.0 - serialize-to-js: 3.1.2 - supercluster: 8.0.1 - tinyqueue: 3.0.0 - optional: true - - maplibre-gl@5.13.0: + maplibre-gl@5.14.0: dependencies: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -19447,7 +19349,7 @@ snapshots: '@mapbox/vector-tile': 2.0.4 '@mapbox/whoots-js': 3.1.0 '@maplibre/maplibre-gl-style-spec': 24.3.1 - '@maplibre/mlt': 1.1.0 + '@maplibre/mlt': 1.1.2 '@maplibre/vt-pbf': 4.1.0 '@types/geojson': 7946.0.16 '@types/geojson-vt': 3.2.5 @@ -19513,13 +19415,6 @@ snapshots: marked@16.4.2: {} - martinez-polygon-clipping@0.7.4: - dependencies: - robust-predicates: 2.0.4 - splaytree: 0.1.4 - tinyqueue: 1.2.3 - optional: true - math-intrinsics@1.1.0: {} mdast-util-directive@3.1.0: @@ -19718,7 +19613,7 @@ snapshots: media-typer@0.3.0: {} - memfs@4.51.0: + memfs@4.51.1: dependencies: '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) @@ -20179,7 +20074,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-forge@1.3.2: {} + node-forge@1.3.3: {} node-releases@2.0.27: {} @@ -20414,7 +20309,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 minipass: 7.1.2 path-to-regexp@0.1.12: {} @@ -20485,7 +20380,7 @@ snapshots: postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-calc@9.0.1(postcss@8.5.6): dependencies: @@ -20521,7 +20416,7 @@ snapshots: postcss-colormin@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.5.6 @@ -20529,7 +20424,7 @@ snapshots: postcss-convert-values@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -20556,12 +20451,12 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-dir-pseudo-class@9.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-discard-comments@6.0.2(postcss@8.5.6): dependencies: @@ -20594,12 +20489,12 @@ snapshots: postcss-focus-visible@10.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-focus-within@9.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-font-variant@5.0.0(postcss@8.5.6): dependencies: @@ -20653,7 +20548,7 @@ snapshots: postcss-merge-rules@6.1.1(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 @@ -20673,7 +20568,7 @@ snapshots: postcss-minify-params@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -20691,13 +20586,13 @@ snapshots: dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: @@ -20706,10 +20601,10 @@ snapshots: postcss-nesting@13.0.2(postcss@8.5.6): dependencies: - '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.0) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-normalize-charset@6.0.2(postcss@8.5.6): dependencies: @@ -20742,7 +20637,7 @@ snapshots: postcss-normalize-unicode@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -20819,11 +20714,11 @@ snapshots: '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.6) '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.6) autoprefixer: 10.4.22(postcss@8.5.6) - browserslist: 4.28.0 + browserslist: 4.28.1 css-blank-pseudo: 7.0.1(postcss@8.5.6) css-has-pseudo: 7.0.3(postcss@8.5.6) css-prefers-color-scheme: 10.0.0(postcss@8.5.6) - cssdb: 8.4.2 + cssdb: 8.5.1 postcss: 8.5.6 postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.6) postcss-clamp: 4.1.0(postcss@8.5.6) @@ -20854,7 +20749,7 @@ snapshots: postcss-pseudo-class-any-link@10.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-reduce-idents@6.0.3(postcss@8.5.6): dependencies: @@ -20863,7 +20758,7 @@ snapshots: postcss-reduce-initial@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 postcss: 8.5.6 @@ -20879,14 +20774,14 @@ snapshots: postcss-selector-not@8.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.0: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -20936,7 +20831,7 @@ snapshots: '@posthog/core': 1.7.0 core-js: 3.47.0 fflate: 0.4.8 - preact: 10.27.2 + preact: 10.28.0 web-vitals: 4.2.4 posthog-node@5.17.0: @@ -20951,11 +20846,11 @@ snapshots: potpack@2.1.0: {} - preact@10.27.2: {} + preact@10.28.0: {} prettier@2.8.8: {} - prettier@3.6.2: {} + prettier@3.7.4: {} pretty-error@4.0.0: dependencies: @@ -20964,11 +20859,11 @@ snapshots: pretty-time@1.1.0: {} - prism-react-renderer@2.4.1(react@19.2.0): + prism-react-renderer@2.4.1(react@19.2.1): dependencies: '@types/prismjs': 1.26.5 clsx: 2.1.1 - react: 19.2.0 + react: 19.2.1 prismjs@1.30.0: {} @@ -21015,7 +20910,7 @@ snapshots: dependencies: escape-goat: 4.0.0 - qs@6.13.0: + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -21104,10 +20999,10 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.5.2: + raw-body@2.5.3: dependencies: bytes: 3.1.2 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 unpipe: 1.0.0 @@ -21138,11 +21033,6 @@ snapshots: date-fns-jalali: 4.1.0-0 react: 19.2.1 - react-dom@19.2.0(react@19.2.0): - dependencies: - react: 19.2.0 - scheduler: 0.27.0 - react-dom@19.2.1(react@19.2.1): dependencies: react: 19.2.1 @@ -21158,25 +21048,24 @@ snapshots: react-is@18.3.1: {} - react-json-view-lite@2.5.0(react@19.2.0): + react-json-view-lite@2.5.0(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 - react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.0))(webpack@5.103.0(@swc/core@1.15.3)): + react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.1))(webpack@5.103.0(@swc/core@1.15.3)): dependencies: '@babel/runtime': 7.28.4 - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.0)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.1)' webpack: 5.103.0(@swc/core@1.15.3) - react-map-gl@8.1.0(mapbox-gl@3.16.0)(maplibre-gl@5.13.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-map-gl@8.1.0(maplibre-gl@5.14.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - '@vis.gl/react-mapbox': 8.1.0(mapbox-gl@3.16.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@vis.gl/react-maplibre': 8.1.0(maplibre-gl@5.13.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@vis.gl/react-mapbox': 8.1.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@vis.gl/react-maplibre': 8.1.0(maplibre-gl@5.14.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) optionalDependencies: - mapbox-gl: 3.16.0 - maplibre-gl: 5.13.0 + maplibre-gl: 5.14.0 react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.1): dependencies: @@ -21210,7 +21099,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - react-remove-scroll@2.7.1(@types/react@19.2.7)(react@19.2.1): + react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.2.1): dependencies: react: 19.2.1 react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.1) @@ -21221,30 +21110,30 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - react-router-config@5.1.1(react-router@5.3.4(react@19.2.0))(react@19.2.0): + react-router-config@5.1.1(react-router@5.3.4(react@19.2.1))(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.0 - react-router: 5.3.4(react@19.2.0) + react: 19.2.1 + react-router: 5.3.4(react@19.2.1) - react-router-dom@5.3.4(react@19.2.0): + react-router-dom@5.3.4(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.0 - react-router: 5.3.4(react@19.2.0) + react: 19.2.1 + react-router: 5.3.4(react@19.2.1) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-router-dom@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-router-dom@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-router@5.3.4(react@19.2.0): + react-router@5.3.4(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 history: 4.10.1 @@ -21252,14 +21141,14 @@ snapshots: loose-envify: 1.4.0 path-to-regexp: 1.9.0 prop-types: 15.8.1 - react: 19.2.0 + react: 19.2.1 react-is: 16.13.1 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - cookie: 1.1.0 + cookie: 1.1.1 react: 19.2.1 set-cookie-parser: 2.7.2 optionalDependencies: @@ -21290,8 +21179,6 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react@19.2.0: {} - react@19.2.1: {} read-yaml-file@1.1.0: @@ -21501,25 +21388,25 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remix-hook-form@7.1.1(react-dom@19.2.1(react@19.2.1))(react-hook-form@7.68.0(react@19.2.1))(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1): + remix-hook-form@7.1.1(react-dom@19.2.1(react@19.2.1))(react-hook-form@7.68.0(react@19.2.1))(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1): dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-hook-form: 7.68.0(react@19.2.1) - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - remix-toast@3.4.0(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)): + remix-toast@3.4.0(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)): dependencies: - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) zod: 3.25.76 - remix-utils@9.0.0(@standard-schema/spec@1.0.0)(react-router@7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1): + remix-utils@9.0.0(@standard-schema/spec@1.0.0)(react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1): dependencies: type-fest: 4.41.0 optionalDependencies: '@standard-schema/spec': 1.0.0 react: 19.2.1 - react-router: 7.10.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) renderkid@3.0.0: dependencies: @@ -21701,7 +21588,7 @@ snapshots: selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.14 - node-forge: 1.3.2 + node-forge: 1.3.3 semver-diff@4.0.0: dependencies: @@ -21729,13 +21616,28 @@ snapshots: transitivePeerDependencies: - supports-color + send@0.19.1: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 - serialize-to-js@3.1.2: - optional: true - serve-handler@6.1.6: dependencies: bytes: 3.0.0 @@ -21957,9 +21859,6 @@ snapshots: splaytree-ts@1.0.2: {} - splaytree@0.1.4: - optional: true - split-string@3.1.0: dependencies: extend-shallow: 3.0.2 @@ -21974,6 +21873,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + std-env@3.10.0: {} stop-iteration-iterator@1.1.0: @@ -22067,7 +21968,7 @@ snapshots: stylehacks@6.1.1(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 postcss-selector-parser: 6.1.2 @@ -22114,11 +22015,11 @@ snapshots: dependencies: tinyqueue: 2.0.3 - swr@2.3.6(react@19.2.0): + swr@2.3.7(react@19.2.1): dependencies: dequal: 2.0.3 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) tailwind-merge@3.4.0: {} @@ -22164,7 +22065,7 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: dependencies: @@ -22173,9 +22074,6 @@ snapshots: tinypool@1.1.1: {} - tinyqueue@1.2.3: - optional: true - tinyqueue@2.0.3: {} tinyqueue@3.0.0: {} @@ -22226,32 +22124,32 @@ snapshots: tslib@2.8.1: {} - turbo-darwin-64@2.6.1: + turbo-darwin-64@2.6.2: optional: true - turbo-darwin-arm64@2.6.1: + turbo-darwin-arm64@2.6.2: optional: true - turbo-linux-64@2.6.1: + turbo-linux-64@2.6.2: optional: true - turbo-linux-arm64@2.6.1: + turbo-linux-arm64@2.6.2: optional: true - turbo-windows-64@2.6.1: + turbo-windows-64@2.6.2: optional: true - turbo-windows-arm64@2.6.1: + turbo-windows-arm64@2.6.2: optional: true - turbo@2.6.1: + turbo@2.6.2: optionalDependencies: - turbo-darwin-64: 2.6.1 - turbo-darwin-arm64: 2.6.1 - turbo-linux-64: 2.6.1 - turbo-linux-arm64: 2.6.1 - turbo-windows-64: 2.6.1 - turbo-windows-arm64: 2.6.1 + turbo-darwin-64: 2.6.2 + turbo-darwin-arm64: 2.6.2 + turbo-linux-64: 2.6.2 + turbo-linux-arm64: 2.6.2 + turbo-windows-64: 2.6.2 + turbo-windows-arm64: 2.6.2 type-fest@0.21.3: {} @@ -22314,26 +22212,26 @@ snapshots: typed-array-buffer: 1.0.3 typed-array-byte-offset: 1.0.4 - typedoc-docusaurus-theme@1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3))): + typedoc-docusaurus-theme@1.4.2(typedoc-plugin-markdown@4.9.0(typedoc@0.28.15(typescript@5.9.3))): dependencies: - typedoc-plugin-markdown: 4.9.0(typedoc@0.28.14(typescript@5.9.3)) + typedoc-plugin-markdown: 4.9.0(typedoc@0.28.15(typescript@5.9.3)) - typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3)): + typedoc-plugin-markdown@4.9.0(typedoc@0.28.15(typescript@5.9.3)): dependencies: - typedoc: 0.28.14(typescript@5.9.3) + typedoc: 0.28.15(typescript@5.9.3) - typedoc-plugin-missing-exports@4.1.2(typedoc@0.28.14(typescript@5.9.3)): + typedoc-plugin-missing-exports@4.1.2(typedoc@0.28.15(typescript@5.9.3)): dependencies: - typedoc: 0.28.14(typescript@5.9.3) + typedoc: 0.28.15(typescript@5.9.3) - typedoc@0.28.14(typescript@5.9.3): + typedoc@0.28.15(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.15.0 + '@gerrit0/mini-shiki': 3.19.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 typescript: 5.9.3 - yaml: 2.8.1 + yaml: 2.8.2 typescript@5.9.3: {} @@ -22439,9 +22337,9 @@ snapshots: webpack-sources: 3.3.3 webpack-virtual-modules: 0.5.0 - update-browserslist-db@1.1.4(browserslist@4.28.0): + update-browserslist-db@1.2.2(browserslist@4.28.1): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -22490,10 +22388,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - use-sync-external-store@1.6.0(react@19.2.0): - dependencies: - react: 19.2.0 - use-sync-external-store@1.6.0(react@19.2.1): dependencies: react: 19.2.1 @@ -22550,13 +22444,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1): + vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -22571,13 +22465,13 @@ snapshots: - tsx - yaml - vite-node@5.2.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1): + vite-node@5.2.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): dependencies: cac: 6.7.14 es-module-lexer: 1.7.0 obug: 2.1.1 pathe: 2.0.3 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -22591,34 +22485,18 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.53.3 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 24.10.1 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.30.2 - terser: 5.44.1 - yaml: 2.8.1 - - vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1): + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -22632,17 +22510,17 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 terser: 5.44.1 - yaml: 2.8.1 + yaml: 2.8.2 - vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1): + vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.14 - '@vitest/runner': 4.0.14 - '@vitest/snapshot': 4.0.14 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 @@ -22651,10 +22529,10 @@ snapshots: picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -22710,7 +22588,7 @@ snapshots: webpack-dev-middleware@7.4.5(webpack@5.103.0(@swc/core@1.15.3)): dependencies: colorette: 2.0.20 - memfs: 4.51.0 + memfs: 4.51.1 mime-types: 3.0.2 on-finished: 2.4.1 range-parser: 1.2.1 @@ -22734,10 +22612,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -22782,7 +22660,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.28.0 + browserslist: 4.28.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -22932,7 +22810,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.8.1: {} + yaml@2.8.2: {} yocto-queue@0.1.0: {} From b4ff19a1aa557ae12ecefc3bf94d48412a57742f Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:07:12 +0100 Subject: [PATCH 09/20] feat: Enable to create a farm for next year --- .changeset/ready-geckos-notice.md | 5 +++++ fdm-app/app/routes/farm.create._index.tsx | 23 ++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 .changeset/ready-geckos-notice.md diff --git a/.changeset/ready-geckos-notice.md b/.changeset/ready-geckos-notice.md new file mode 100644 index 000000000..2d0d6c25d --- /dev/null +++ b/.changeset/ready-geckos-notice.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Enable to create a farm for next year diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx index 6c33bec68..305a8b17d 100644 --- a/fdm-app/app/routes/farm.create._index.tsx +++ b/fdm-app/app/routes/farm.create._index.tsx @@ -51,6 +51,7 @@ import { clientConfig } from "~/lib/config" import { handleActionError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { extractFormValuesFromRequest } from "~/lib/form" +import { getCalendarSelection } from "../lib/calendar" // Meta export const meta: MetaFunction = () => { @@ -89,9 +90,11 @@ const FormSchema = z.object({ // Loader export async function loader({ request }: LoaderFunctionArgs) { + const yearSelection = getCalendarSelection() + return { - b_name_farm: null, year: new Date().getFullYear(), + yearSelection: yearSelection, } } @@ -101,25 +104,19 @@ export async function loader({ request }: LoaderFunctionArgs) { * @returns The JSX element representing the add farm page. */ export default function AddFarmPage() { - const loaderData = useLoaderData() + const { year, yearSelection } = useLoaderData() const form = useRemixForm>({ mode: "onTouched", resolver: zodResolver(FormSchema), defaultValues: { - b_name_farm: loaderData.b_name_farm ?? "", - year: loaderData.year, + b_name_farm: "", + year: year, has_derogation: false, - derogation_start_year: loaderData.year, + derogation_start_year: 2025, }, }) - const currentYear = new Date().getFullYear() - const years = Array.from( - { length: currentYear - 2020 + 1 }, - (_, i) => currentYear - i, - ) - return (
@@ -289,9 +286,9 @@ export default function AddFarmPage() { - {years.map( + {yearSelection.map( ( - year, + year: number, ) => ( Date: Fri, 5 Dec 2025 09:45:59 +0100 Subject: [PATCH 10/20] fix: imports, mocks and comments --- fdm-calculator/src/norms/index.test.ts | 2 +- fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts | 2 +- .../src/norms/nl/2025/value/stikstofgebruiksnorm.ts | 4 ++-- .../norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts | 2 +- .../src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts | 2 +- .../src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts | 3 ++- .../src/norms/nl/2026/value/fosfaatgebruiksnorm.ts | 3 ++- fdm-calculator/src/norms/nl/2026/value/input.test.ts | 7 ------- .../src/norms/nl/2026/value/stikstofgebruiksnorm.ts | 2 +- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/fdm-calculator/src/norms/index.test.ts b/fdm-calculator/src/norms/index.test.ts index 236ddcafc..46e50dde1 100644 --- a/fdm-calculator/src/norms/index.test.ts +++ b/fdm-calculator/src/norms/index.test.ts @@ -97,7 +97,7 @@ describe("createFunctionsForFertilizerApplicationFilling", () => { ) }) - it("should return the correct functions for NL region and year 2025", () => { + it("should return the correct functions for NL region and year 2026", () => { const functions = createFunctionsForFertilizerApplicationFilling( "NL", "2026", diff --git a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts index a92d0d679..35ba128b9 100644 --- a/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts +++ b/fdm-calculator/src/norms/nl/2025/value/hoofdteelt.ts @@ -13,7 +13,7 @@ import type { NL2025NormsInputForCultivation } from "./types" * { cultivation: { b_lu_start: '2025-05-01', b_lu_end: '2025-06-10', b_lu_catalogue: 'cat_A' } }, * { cultivation: { b_lu_start: '2025-06-01', b_lu_end: '2025-07-20', b_lu_catalogue: 'cat_B' } } * ]; - * const hoofdteelt = await determineNLHoofdteelt(cultivations); + * const hoofdteelt = await determineNLHoofdteelt(cultivations, 2025); * // returns 'cat_B' */ export function determineNLHoofdteelt( diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index cc78cbf26..494c68647 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -365,7 +365,7 @@ function calculateKorting( // Determine hoofdteelt for the current year (2025) const hoofdteelt2025 = determineNLHoofdteelt( cultivations.filter((c) => c.b_lu_start.getFullYear() === currentYear), - 2025 + 2025, ) const hoofdteelt2025Standard = nitrogenStandardsData.find((ns) => ns.b_lu_catalogue_match.includes(hoofdteelt2025), @@ -393,7 +393,7 @@ function calculateKorting( prevCultivation.b_lu_start.getTime() < new Date(currentYear, 1, 1).getTime() && prevCultivation.b_lu_start.getTime() > - new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2024 and January 31th 2025 + new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2025 and February 1st 2026 (exclusive) return matchingStandard?.is_vanggewas === true && matchingYear === true }) if (vanggewassen2024.length === 0) { diff --git a/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts index efc3cb8bc..da814be37 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/dierlijke-mest-gebruiksnorm.ts @@ -3,7 +3,7 @@ import Decimal from "decimal.js" import pkg from "../../../../package" import { table11Mestcodes } from "./table-11-mestcodes" import type { NL2026NormsFillingInput } from "./types" -import type { NormFilling } from "norms/nl/types" +import type { NormFilling } from "../../types" /** * Calculates the nitrogen usage from animal manure for a list of fertilizer applications. diff --git a/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts index 774aeee6f..4501de976 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/fosfaatgebruiksnorm.ts @@ -7,7 +7,7 @@ import Decimal from "decimal.js" import pkg from "../../../../package" import { table11Mestcodes } from "./table-11-mestcodes" import type { NL2026NormsFillingInput} from "./types" -import type { NormFilling } from "norms/nl/types" +import type { NormFilling } from "../../types" const rvoMestcodesOrganicRich25Percent = ["111", "112"] // Compost, Zeer schone compost const rvoMestcodesOrganicRich75Percent = ["110", "10", "61", "25", "56"] // Champost, Rundvee - Vaste mest, Geiten - Vaste mest, Paarden - Vaste mest, Schapen - Mest, alle systemen diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts index 5553e1d1f..f8464ca86 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts @@ -14,7 +14,7 @@ import { import type { NL2026NormsFillingInput } from "./types" // Mock getRegion -vi.mock("../value/stikstofgebruiksnorm", () => ({ +vi.mock("../../2025/value/stikstofgebruiksnorm", () => ({ getRegion: vi.fn(), })) @@ -647,6 +647,7 @@ describe("calculateNL2026FertilizerApplicationFillingForStikstofGebruiksNorm", ( }) it("should throw an error if fertilizer cannot be found", async () => { + vi.mocked(getRegion).mockResolvedValue("klei") const applications: FertilizerApplication[] = [ { p_app_id: "app1", diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts index 3c8315f3e..4093a4a7c 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -166,7 +166,8 @@ export async function calculateNL2026FosfaatGebruiksNorm( * @param {NL2026NormsInput} input - An object containing all necessary parameters for the calculation. * @returns {Promise} An object of type `FosfaatGebruiksnormResult` containing the determined * phosphate usage standard (`normValue`) and the `fosfaatKlasse` (the phosphate - * class determined from the soil analysis). Returns `null` if a norm cannot be determined. + * class determined from the soil analysis). + * @throws {Error} If soil analysis data is missing or no phosphate norms are found for the determined class. */ export const getNL2026FosfaatGebruiksNorm = withCalculationCache( calculateNL2026FosfaatGebruiksNorm, diff --git a/fdm-calculator/src/norms/nl/2026/value/input.test.ts b/fdm-calculator/src/norms/nl/2026/value/input.test.ts index 59564d21f..3a8b7fbc7 100644 --- a/fdm-calculator/src/norms/nl/2026/value/input.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/input.test.ts @@ -52,7 +52,6 @@ describe("collectNL2026InputForNorms", () => { vi.mocked(fdmCore.getCurrentSoilData).mockResolvedValue( mockSoilAnalysis as unknown as SoilAnalysis[], ) - vi.mocked(fdmCore.isDerogationGrantedForYear).mockResolvedValue(false) vi.mocked(fdmCore.getGrazingIntention).mockResolvedValue(false) const result = await collectNL2026InputForNorms( @@ -70,12 +69,6 @@ describe("collectNL2026InputForNorms", () => { mockPrincipalId, mockFieldId, ) - expect(fdmCore.isDerogationGrantedForYear).toHaveBeenCalledWith( - mockFdm, - mockPrincipalId, - "farm-1", - 2026, - ) expect(fdmCore.getGrazingIntention).toHaveBeenCalledWith( mockFdm, mockPrincipalId, diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts index c5d19c61e..2c495b676 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -294,7 +294,7 @@ function calculateKorting( prevCultivation.b_lu_start.getTime() < new Date(currentYear, 1, 1).getTime() && prevCultivation.b_lu_start.getTime() > - new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2025 and January 31th 2026 + new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2025 and February 1st 2026 (exclusive) return matchingStandard?.is_vanggewas === true && matchingYear === true }) if (vanggewassen2025.length === 0) { From e933a1c2da42c19e7d7e1f767c90feba2a86e1b3 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:51:51 +0100 Subject: [PATCH 11/20] tests: fixes --- .../2026/filling/stikstofgebruiksnorm.test.ts | 17 +++++++++++++---- .../src/norms/nl/2026/value/input.test.ts | 2 +- .../nl/2026/value/stikstofgebruiksnorm.test.ts | 8 ++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts index f8464ca86..6b9658fa7 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.test.ts @@ -3,7 +3,7 @@ import type { Fertilizer, FertilizerApplication, } from "@svenvw/fdm-core" -import { afterEach, describe, expect, it, vi } from "vitest" +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import { getRegion } from "../../2025/value/stikstofgebruiksnorm" import type { RegionKey } from "../value/types" import { @@ -14,9 +14,18 @@ import { import type { NL2026NormsFillingInput } from "./types" // Mock getRegion -vi.mock("../../2025/value/stikstofgebruiksnorm", () => ({ - getRegion: vi.fn(), -})) +vi.mock("../../2025/value/stikstofgebruiksnorm", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + getRegion: vi.fn(actual.getRegion), + } +}) + +// Default mock implementation for getRegion +beforeEach(() => { + vi.mocked(getRegion).mockResolvedValue("zand_nwc") +}) describe("isBouwland", () => { it("should return true if cultivation is not in non-bouwland codes", () => { diff --git a/fdm-calculator/src/norms/nl/2026/value/input.test.ts b/fdm-calculator/src/norms/nl/2026/value/input.test.ts index 3a8b7fbc7..2ab6691cd 100644 --- a/fdm-calculator/src/norms/nl/2026/value/input.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/input.test.ts @@ -43,7 +43,7 @@ describe("collectNL2026InputForNorms", () => { } const timeframeCultivation = { - start: new Date(2024, 0, 1), + start: new Date(2025, 0, 1), end: new Date(2026, 11, 31), } diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts index e15fe3190..94258e271 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts @@ -114,7 +114,7 @@ describe(" calculateNL2026StikstofGebruiksNorm", () => { } as Partial, { b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) - b_lu_start: new Date(2024, 9, 1), // Oct 1st, 2024 + b_lu_start: new Date(2025, 9, 1), // Oct 1st, 2025 b_lu_end: new Date(2026, 1, 31), } as Partial, ] as NL2026NormsInputForCultivation[], @@ -144,7 +144,7 @@ describe(" calculateNL2026StikstofGebruiksNorm", () => { } as Partial, { b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) - b_lu_start: new Date(2024, 9, 5), // Oct 5th, 2024 + b_lu_start: new Date(2025, 9, 5), // Oct 5th, 2025 b_lu_end: new Date(2026, 1, 31), } as Partial, ] as NL2026NormsInputForCultivation[], @@ -174,7 +174,7 @@ describe(" calculateNL2026StikstofGebruiksNorm", () => { } as Partial, { b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) - b_lu_start: new Date(2024, 9, 20), // Oct 20th, 2024 + b_lu_start: new Date(2025, 9, 20), // Oct 20th, 2025 b_lu_end: new Date(2026, 1, 31), } as Partial, ] as NL2026NormsInputForCultivation[], @@ -204,7 +204,7 @@ describe(" calculateNL2026StikstofGebruiksNorm", () => { } as Partial, { b_lu_catalogue: "nl_428", // Gele mosterd (is_vanggewas: true) - b_lu_start: new Date(2024, 10, 1), // Nov 1st, 2024 + b_lu_start: new Date(2025, 10, 1), // Nov 1st, 2025 b_lu_end: new Date(2026, 1, 31), } as Partial, ] as NL2026NormsInputForCultivation[], From 05bc8e7bb5f747a036988cec68f76908f49061ee Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:59:54 +0100 Subject: [PATCH 12/20] refacctor: simplify fosfaat norms data object --- .../nl/2025/value/fosfaatgebruiksnorm-data.ts | 16 +++++++--------- .../norms/nl/2025/value/fosfaatgebruiksnorm.ts | 11 ++++------- .../nl/2026/value/fosfaatgebruiksnorm-data.ts | 16 +++++++--------- .../norms/nl/2026/value/fosfaatgebruiksnorm.ts | 4 ++-- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm-data.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm-data.ts index c358e5562..e39b8fd78 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm-data.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm-data.ts @@ -1,9 +1,7 @@ -export const fosfaatNormsData = [ - { - Arm: { grasland: 120, bouwland: 120 }, - Laag: { grasland: 105, bouwland: 80 }, - Neutraal: { grasland: 95, bouwland: 70 }, - Ruim: { grasland: 90, bouwland: 60 }, - Hoog: { grasland: 75, bouwland: 40 }, - }, -] +export const fosfaatNormsData = { + Arm: { grasland: 120, bouwland: 120 }, + Laag: { grasland: 105, bouwland: 80 }, + Neutraal: { grasland: 95, bouwland: 70 }, + Ruim: { grasland: 90, bouwland: 60 }, + Hoog: { grasland: 75, bouwland: 40 }, +} diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index 0cd5b570a..1455c061d 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -3,11 +3,8 @@ import Decimal from "decimal.js" import pkg from "../../../../package" import { fosfaatNormsData } from "./fosfaatgebruiksnorm-data" import { determineNLHoofdteelt } from "./hoofdteelt" -import type { - FosfaatKlasse, - NL2025NormsInput, -} from "./types.d" -import { FosfaatGebruiksnormResult } from "norms/nl/types" +import type { FosfaatKlasse, NL2025NormsInput } from "./types.d" +import type { FosfaatGebruiksnormResult } from "../../types" /** * Determines if a cultivation is a type of grassland based on its catalogue entry. @@ -129,7 +126,7 @@ function getFosfaatKlasse( * based on the provided `a_p_cc` and `a_p_al` values and whether it's grassland or arable land. * This classification directly uses the lookup tables provided by RVO for 2025. * 2. **Retrieve Base Norm**: The determined `fosfaatKlasse` is then used to look up the - * corresponding base phosphate norm from the `fosfaatNormsData.json` file. + * corresponding base phosphate norm from the `fosfaatgebruiksnorm-data.ts` file. * 3. **Apply Land Type**: The specific norm for either `grasland` or `bouwland` is selected * from the base norm based on the `is_grasland` input parameter. * 4. **Return Result**: The function returns the final `normValue` and the `fosfaatKlasse`. @@ -157,7 +154,7 @@ export async function calculateNL2025FosfaatGebruiksNorm( const fosfaatKlasse = getFosfaatKlasse(a_p_cc, a_p_al, is_grasland) // Retrieve the base norms for the determined phosphate class. - const normsForKlasse = fosfaatNormsData[0][fosfaatKlasse] + const normsForKlasse = fosfaatNormsData[fosfaatKlasse] if (!normsForKlasse) { throw new Error(`No phosphate norms found for class ${fosfaatKlasse}.`) diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts index c358e5562..e39b8fd78 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm-data.ts @@ -1,9 +1,7 @@ -export const fosfaatNormsData = [ - { - Arm: { grasland: 120, bouwland: 120 }, - Laag: { grasland: 105, bouwland: 80 }, - Neutraal: { grasland: 95, bouwland: 70 }, - Ruim: { grasland: 90, bouwland: 60 }, - Hoog: { grasland: 75, bouwland: 40 }, - }, -] +export const fosfaatNormsData = { + Arm: { grasland: 120, bouwland: 120 }, + Laag: { grasland: 105, bouwland: 80 }, + Neutraal: { grasland: 95, bouwland: 70 }, + Ruim: { grasland: 90, bouwland: 60 }, + Hoog: { grasland: 75, bouwland: 40 }, +} diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts index 4093a4a7c..129f0c7b7 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -112,7 +112,7 @@ function getFosfaatKlasse( * based on the provided `a_p_cc` and `a_p_al` values and whether it's grassland or arable land. * This classification directly uses the lookup tables provided by RVO for 2026. * 2. **Retrieve Base Norm**: The determined `fosfaatKlasse` is then used to look up the - * corresponding base phosphate norm from the `fosfaatNormsData.json` file. + * corresponding base phosphate norm from the `fosfaatgebruiksnorm-data.ts` file. * 3. **Apply Land Type**: The specific norm for either `grasland` or `bouwland` is selected * from the base norm based on the `is_grasland` input parameter. * 4. **Return Result**: The function returns the final `normValue` and the `fosfaatKlasse`. @@ -140,7 +140,7 @@ export async function calculateNL2026FosfaatGebruiksNorm( const fosfaatKlasse = getFosfaatKlasse(a_p_cc, a_p_al, is_grasland) // Retrieve the base norms for the determined phosphate class. - const normsForKlasse = fosfaatNormsData[0][fosfaatKlasse] + const normsForKlasse = fosfaatNormsData[fosfaatKlasse] if (!normsForKlasse) { throw new Error(`No phosphate norms found for class ${fosfaatKlasse}.`) From 74c4194cb92f321f142ee627481197cbc0ba8f4b Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:00:09 +0100 Subject: [PATCH 13/20] fix: default year for derogation --- fdm-app/app/routes/farm.create._index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx index 305a8b17d..1a9b81354 100644 --- a/fdm-app/app/routes/farm.create._index.tsx +++ b/fdm-app/app/routes/farm.create._index.tsx @@ -214,7 +214,7 @@ export default function AddFarmPage() { field.onChange } defaultValue={String( - new Date().getFullYear(), + field.value, )} > From 1673a6eecf5deec9cea76281c247b1d8f8c8c65a Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:03:23 +0100 Subject: [PATCH 14/20] nitpicks --- .../src/norms/nl/2026/filling/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts | 7 ++----- fdm-calculator/src/norms/nl/2026/value/input.ts | 2 +- .../src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts | 2 +- fdm-calculator/src/norms/nl/types.ts | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts index 02070038e..dfefffb9d 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts @@ -213,7 +213,7 @@ export function getWorkingCoefficient( if ( !( (appMonth >= 8 && appMonth <= 11) || - (appMonth === 0 && appDay <= 31) + (appMonth === 0) ) ) { return false diff --git a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts index 489d3e769..1c76e061b 100644 --- a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts @@ -18,11 +18,9 @@ import type { DierlijkeMestGebruiksnormResult } from "norms/nl/types" * - **Standard Norm**: The norm is 170 kg N/ha from animal manure. */ export async function calculateNL2026DierlijkeMestGebruiksNorm(): Promise { - let normValue: number - let normSource: string - normValue = 170 - normSource = "Standaard - geen derogatie" + const normValue = 170 + const normSource = "Standaard - geen derogatie" return { normValue, normSource } } @@ -33,7 +31,6 @@ export async function calculateNL2026DierlijkeMestGebruiksNorm(): Promise} An object of type `DierlijkeMestGebruiksnormResult` containing the determined * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied. */ diff --git a/fdm-calculator/src/norms/nl/2026/value/input.ts b/fdm-calculator/src/norms/nl/2026/value/input.ts index 61b126c90..d69d68771 100644 --- a/fdm-calculator/src/norms/nl/2026/value/input.ts +++ b/fdm-calculator/src/norms/nl/2026/value/input.ts @@ -55,7 +55,7 @@ export async function collectNL2026InputForNorms( timeframe2026Cultivation, ) - // 5. Get the details of the soil analyses + // 4. Get the details of the soil analyses const soilAnalysis = await getCurrentSoilData( fdm, principal_id, diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts index 94258e271..c132e3b48 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest" import { calculateNL2026StikstofGebruiksNorm } from "./stikstofgebruiksnorm" import type { NL2026NormsInput, NL2026NormsInputForCultivation } from "./types" -describe(" calculateNL2026StikstofGebruiksNorm", () => { +describe("calculateNL2026StikstofGebruiksNorm", () => { it("should return the correct norm for grasland (beweiden)", async () => { const mockInput: NL2026NormsInput = { farm: { has_grazing_intention: true }, diff --git a/fdm-calculator/src/norms/nl/types.ts b/fdm-calculator/src/norms/nl/types.ts index 2a2db7387..c16a4ff6d 100644 --- a/fdm-calculator/src/norms/nl/types.ts +++ b/fdm-calculator/src/norms/nl/types.ts @@ -49,7 +49,7 @@ export interface GebruiksnormResult { */ normValue: number /** - * The cultivation name according to RVO's "Tabel 2 Stikstof landbouwgrond 2025" + * The cultivation name according to RVO's "Tabel 2 Stikstof landbouwgrond" * that was used to determine the legal limit. */ normSource: string From 5093d9e1d9122f1d2b0be33556a144134910cd42 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:14:38 +0100 Subject: [PATCH 15/20] fixes --- .../src/norms/nl/2025/value/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index 494c68647..034b0bbd6 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -393,7 +393,7 @@ function calculateKorting( prevCultivation.b_lu_start.getTime() < new Date(currentYear, 1, 1).getTime() && prevCultivation.b_lu_start.getTime() > - new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2025 and February 1st 2026 (exclusive) + new Date(previousYear, 6, 15).getTime() // Vanggewas should be sown between July 15th, 2024 and February 1st 2025 (exclusive) return matchingStandard?.is_vanggewas === true && matchingYear === true }) if (vanggewassen2024.length === 0) { diff --git a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts index 1c76e061b..51d814ec3 100644 --- a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts @@ -8,17 +8,15 @@ import type { DierlijkeMestGebruiksnormResult } from "norms/nl/types" * * This function implements the rules and norms specified by the RVO for 2026. * - * @param input - An object containing all necessary parameters for the calculation. - * See {@link DierlijkeMestGebruiksnormInput} for details. * @returns An object of type `DierlijkeMestGebruiksnormResult` containing the determined * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied. * * @remarks * The rules for 2026 are as follows: * - **Standard Norm**: The norm is 170 kg N/ha from animal manure. + * - **No Derogation**: Derogation rules do not apply for 2026. */ export async function calculateNL2026DierlijkeMestGebruiksNorm(): Promise { - const normValue = 170 const normSource = "Standaard - geen derogatie" From ba2c7dc9ca22e5bc268e75837ffe52e2d9285f58 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:21:25 +0100 Subject: [PATCH 16/20] Fix: calculating korting at stikstofgebruiksnorm when vangewas is sown on October 15th --- .changeset/perky-heads-smash.md | 5 +++++ .../src/norms/nl/2025/value/stikstofgebruiksnorm.ts | 4 ++-- .../src/norms/nl/2026/value/stikstofgebruiksnorm.ts | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/perky-heads-smash.md diff --git a/.changeset/perky-heads-smash.md b/.changeset/perky-heads-smash.md new file mode 100644 index 000000000..0c02556af --- /dev/null +++ b/.changeset/perky-heads-smash.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": patch +--- + +Fix calculating korting at stikstofgebruiksnorm when vangewas is sown on October 15th diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index 034b0bbd6..27ea05541 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -438,11 +438,11 @@ function calculateKorting( kortingAmount = new Decimal(0) kortingDescription = ". Geen korting: vanggewas gezaaid uiterlijk 1 oktober" - } else if (sowDate > october1 && sowDate <= october15) { + } else if (sowDate > october1 && sowDate < october15) { kortingAmount = new Decimal(5) kortingDescription = ". Korting: 5kg N/ha, vanggewas gezaaid tussen 2 t/m 14 oktober" - } else if (sowDate > october15 && sowDate < november1) { + } else if (sowDate >= october15 && sowDate < november1) { kortingAmount = new Decimal(10) kortingDescription = ". Korting: 10kg N/ha, vanggewas gezaaid tussen 15 t/m 31 oktober" diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts index 2c495b676..3e86d5551 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -339,11 +339,11 @@ function calculateKorting( kortingAmount = new Decimal(0) kortingDescription = ". Geen korting: vanggewas gezaaid uiterlijk 1 oktober" - } else if (sowDate > october1 && sowDate <= october15) { + } else if (sowDate > october1 && sowDate < october15) { kortingAmount = new Decimal(5) kortingDescription = ". Korting: 5kg N/ha, vanggewas gezaaid tussen 2 t/m 14 oktober" - } else if (sowDate > october15 && sowDate < november1) { + } else if (sowDate >= october15 && sowDate < november1) { kortingAmount = new Decimal(10) kortingDescription = ". Korting: 10kg N/ha, vanggewas gezaaid tussen 15 t/m 31 oktober" From 7b9dbe598b9992f06bbcd7b9c972fd96f766b28a Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:26:19 +0100 Subject: [PATCH 17/20] nitpicks --- fdm-app/app/routes/farm.create._index.tsx | 12 +++++++----- .../src/norms/nl/2025/value/fosfaatgebruiksnorm.ts | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx index 1a9b81354..ececfc69f 100644 --- a/fdm-app/app/routes/farm.create._index.tsx +++ b/fdm-app/app/routes/farm.create._index.tsx @@ -104,7 +104,7 @@ export async function loader({ request }: LoaderFunctionArgs) { * @returns The JSX element representing the add farm page. */ export default function AddFarmPage() { - const { year, yearSelection } = useLoaderData() + const { year, yearSelection } = useLoaderData() const form = useRemixForm>({ mode: "onTouched", @@ -288,16 +288,18 @@ export default function AddFarmPage() { {yearSelection.map( ( - year: number, + yearOption: string, ) => ( { - year + yearOption } ), diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index 1455c061d..d44e77fe1 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -117,7 +117,8 @@ function getFosfaatKlasse( * See {@link FosfaatGebruiksnormInput} for details. * @returns An object of type `FosfaatGebruiksnormResult` containing the determined * phosphate usage standard (`normValue`) and the `fosfaatKlasse` (the phosphate - * class determined from the soil analysis). Returns `null` if a norm cannot be determined. + * class determined from the soil analysis). + * @throws {Error} If soil analysis data is missing or no phosphate norms are found for the determined class. * * @remarks * The function operates as follows: From cf41dbf1319358b69850210cd90ff38b4a50b8ce Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:29:44 +0100 Subject: [PATCH 18/20] fix: type error --- .../src/norms/nl/2026/filling/stikstofgebruiksnorm.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts index dfefffb9d..35725f13e 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts @@ -203,7 +203,6 @@ export function getWorkingCoefficient( if (subType.applicationPeriod) { const appMonth = p_app_date.getMonth() // 0-11 (Jan is 0, Dec is 11) - const appDay = p_app_date.getDate() if ( subType.applicationPeriod === @@ -213,7 +212,7 @@ export function getWorkingCoefficient( if ( !( (appMonth >= 8 && appMonth <= 11) || - (appMonth === 0) + appMonth === 0 ) ) { return false From 6d28fd747435ba1d6625fa31c81dd11ba31e77da Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:58:06 +0100 Subject: [PATCH 19/20] fix: Include at NL Stikstofgebruiksnormen that nl_335 (Natuurterreinen (incl. heide)) is set to be not bouwland --- .changeset/thick-bottles-push.md | 5 +++++ .../src/norms/nl/2025/filling/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2026/filling/stikstofgebruiksnorm.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/thick-bottles-push.md diff --git a/.changeset/thick-bottles-push.md b/.changeset/thick-bottles-push.md new file mode 100644 index 000000000..fb8e17e35 --- /dev/null +++ b/.changeset/thick-bottles-push.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": minor +--- + +Include at NL Stikstofgebruiksnormen that nl_335 (Natuurterreinen (incl. heide)) is set to be not bouwland diff --git a/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts index 7c3c8f57d..572b520f5 100644 --- a/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/filling/stikstofgebruiksnorm.ts @@ -116,7 +116,7 @@ export function isBouwland( cultivations: Cultivation[], p_app_date: Date, ): boolean { - const nonBouwlandCodes = ["nl_265", "nl_266", "nl_331", "nl_332"] + const nonBouwlandCodes = ["nl_265", "nl_266", "nl_331", "nl_332", "nl_335"] const activeCultivation = cultivations.find((c) => { const startDate = new Date(c.b_start) diff --git a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts index 35725f13e..d8c32b160 100644 --- a/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/filling/stikstofgebruiksnorm.ts @@ -116,7 +116,7 @@ export function isBouwland( cultivations: Cultivation[], p_app_date: Date, ): boolean { - const nonBouwlandCodes = ["nl_265", "nl_266", "nl_331", "nl_332"] + const nonBouwlandCodes = ["nl_265", "nl_266", "nl_331", "nl_332", "nl_335"] const activeCultivation = cultivations.find((c) => { const startDate = new Date(c.b_start) From 990846b194f0582ee3b567430d5377f5af4e2e2f Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:02:49 +0100 Subject: [PATCH 20/20] docs: fix descriptions --- .../src/norms/nl/2025/value/fosfaatgebruiksnorm.ts | 9 ++------- .../norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index d44e77fe1..b2b4649bd 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -9,16 +9,11 @@ import type { FosfaatGebruiksnormResult } from "../../types" /** * Determines if a cultivation is a type of grassland based on its catalogue entry. * @param b_lu_catalogue - The cultivation catalogue code. - * @returns A promise that resolves to a boolean. + * @returns `true` if the catalogue code represents a grassland cultivation, otherwise `false`. */ export function isCultivationGrasland(b_lu_catalogue: string): boolean { const graslandCodes = ["nl_265", "nl_266", "nl_331", "nl_332", "nl_335"] - - if (graslandCodes.includes(b_lu_catalogue)) { - return true - } - - return false + return graslandCodes.includes(b_lu_catalogue) } /** diff --git a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts index 51d814ec3..6cc222a92 100644 --- a/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/dierlijke-mest-gebruiksnorm.ts @@ -27,7 +27,7 @@ export async function calculateNL2026DierlijkeMestGebruiksNorm(): Promise} An object of type `DierlijkeMestGebruiksnormResult` containing the determined * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied.