From da3e50a571483c576dd88abecd3e70ca0b9f22ba Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:53:45 +0100 Subject: [PATCH 01/28] refactor: Replace at field the derived parameter `b_isproductive` with the stored parameter `b_buffer` to allow users setting the value explicitly --- .changeset/huge-cars-peel.md | 5 + fdm-core/src/cultivation.d.ts | 2 +- fdm-core/src/cultivation.ts | 8 +- .../src/db/migrations/0021_exotic_mesmero.sql | 1 + .../src/db/migrations/meta/0021_snapshot.json | 3623 +++++++++++++++++ fdm-core/src/db/migrations/meta/_journal.json | 7 + fdm-core/src/db/schema.ts | 1 + fdm-core/src/field.d.ts | 2 +- fdm-core/src/field.test.ts | 50 +- fdm-core/src/field.ts | 75 +- 10 files changed, 3718 insertions(+), 56 deletions(-) create mode 100644 .changeset/huge-cars-peel.md create mode 100644 fdm-core/src/db/migrations/0021_exotic_mesmero.sql create mode 100644 fdm-core/src/db/migrations/meta/0021_snapshot.json diff --git a/.changeset/huge-cars-peel.md b/.changeset/huge-cars-peel.md new file mode 100644 index 000000000..b90317124 --- /dev/null +++ b/.changeset/huge-cars-peel.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-core": minor +--- + +Replace at field the derived parameter `b_isproductive` with the stored parameter `b_buffer` to allow users setting the value explicitly diff --git a/fdm-core/src/cultivation.d.ts b/fdm-core/src/cultivation.d.ts index 7409b04e5..6b2a8fb0f 100644 --- a/fdm-core/src/cultivation.d.ts +++ b/fdm-core/src/cultivation.d.ts @@ -38,7 +38,7 @@ export interface CultivationPlan { b_id: schema.fieldsTypeSelect["b_id"] b_area: number b_name: schema.fieldsTypeSelect["b_name"] - b_isproductive: boolean + b_buffer: boolean fertilizer_applications: Array<{ p_id_catalogue: schema.fertilizersCatalogueTypeSelect["p_id_catalogue"] p_name_nl: schema.fertilizersCatalogueTypeSelect["p_name_nl"] diff --git a/fdm-core/src/cultivation.ts b/fdm-core/src/cultivation.ts index 023913d79..994a2dd51 100644 --- a/fdm-core/src/cultivation.ts +++ b/fdm-core/src/cultivation.ts @@ -23,7 +23,6 @@ import type { import * as schema from "./db/schema" import { handleError } from "./error" import type { FdmType } from "./fdm" -import { determineIfFieldIsProductive } from "./field" import { addHarvest, getDefaultsForHarvestParameters, @@ -786,6 +785,7 @@ export async function getCultivationPlan( b_name: schema.fields.b_name, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, b_perimeter: sql`ROUND((ST_Length(ST_ExteriorRing(b_geometry)::geography))::NUMERIC, 2)::FLOAT`, + b_buffer: schema.fields.b_buffer, b_lu_start: schema.cultivationStarting.b_lu_start, b_lu_end: schema.cultivationEnding.b_lu_end, m_cropresidue: schema.cultivationEnding.m_cropresidue, @@ -957,11 +957,7 @@ export async function getCultivationPlan( b_id: curr.b_id, b_area: curr.b_area, b_name: curr.b_name, - b_isproductive: determineIfFieldIsProductive( - curr.b_area, - curr.b_perimeter, - curr.b_name, - ), + b_buffer: curr.b_buffer, fertilizer_applications: [], harvests: [], } diff --git a/fdm-core/src/db/migrations/0021_exotic_mesmero.sql b/fdm-core/src/db/migrations/0021_exotic_mesmero.sql new file mode 100644 index 000000000..b668c30a9 --- /dev/null +++ b/fdm-core/src/db/migrations/0021_exotic_mesmero.sql @@ -0,0 +1 @@ +ALTER TABLE "fdm"."fields" ADD COLUMN "b_buffer" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/fdm-core/src/db/migrations/meta/0021_snapshot.json b/fdm-core/src/db/migrations/meta/0021_snapshot.json new file mode 100644 index 000000000..e6c048546 --- /dev/null +++ b/fdm-core/src/db/migrations/meta/0021_snapshot.json @@ -0,0 +1,3623 @@ +{ + "id": "68820e10-7b74-4632-9d86-500508549bb8", + "prevId": "1eb64507-01b4-41c6-8583-c114db99b60f", + "version": "7", + "dialect": "postgresql", + "tables": { + "fdm.cultivation_catalogue_selecting": { + "name": "cultivation_catalogue_selecting", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_source": { + "name": "b_lu_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk": { + "name": "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "cultivation_catalogue_selecting", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_ending": { + "name": "cultivation_ending", + "schema": "fdm", + "columns": { + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_end": { + "name": "b_lu_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "m_cropresidue": { + "name": "m_cropresidue", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_ending_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_ending_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_ending", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_harvesting": { + "name": "cultivation_harvesting", + "schema": "fdm", + "columns": { + "b_id_harvesting": { + "name": "b_id_harvesting", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_harvest_date": { + "name": "b_lu_harvest_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk": { + "name": "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk", + "tableFrom": "cultivation_harvesting", + "tableTo": "harvestables", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable"], + "columnsTo": ["b_id_harvestable"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cultivation_harvesting_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_harvesting_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_harvesting", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_starting": { + "name": "cultivation_starting", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_start": { + "name": "b_lu_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_sowing_amount": { + "name": "b_sowing_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_sowing_method": { + "name": "b_sowing_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_starting_b_id_fields_b_id_fk": { + "name": "cultivation_starting_b_id_fields_b_id_fk", + "tableFrom": "cultivation_starting", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cultivation_starting_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_starting_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_starting", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivations": { + "name": "cultivations", + "schema": "fdm", + "columns": { + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_catalogue": { + "name": "b_lu_catalogue", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_variety": { + "name": "b_lu_variety", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_lu_idx": { + "name": "b_lu_idx", + "columns": [ + { + "expression": "b_lu", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk": { + "name": "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk", + "tableFrom": "cultivations", + "tableTo": "cultivations_catalogue", + "schemaTo": "fdm", + "columnsFrom": ["b_lu_catalogue"], + "columnsTo": ["b_lu_catalogue"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivations_catalogue": { + "name": "cultivations_catalogue", + "schema": "fdm", + "columns": { + "b_lu_catalogue": { + "name": "b_lu_catalogue", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_source": { + "name": "b_lu_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_name": { + "name": "b_lu_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_name_en": { + "name": "b_lu_name_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_harvestable": { + "name": "b_lu_harvestable", + "type": "b_lu_harvestable", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": true + }, + "b_lu_harvestcat": { + "name": "b_lu_harvestcat", + "type": "b_lu_harvestcat", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_lu_hcat3": { + "name": "b_lu_hcat3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_hcat3_name": { + "name": "b_lu_hcat3_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_croprotation": { + "name": "b_lu_croprotation", + "type": "b_lu_croprotation", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield": { + "name": "b_lu_yield", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_dm": { + "name": "b_lu_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_hi": { + "name": "b_lu_hi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_harvestable": { + "name": "b_lu_n_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_residue": { + "name": "b_lu_n_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_n_fixation": { + "name": "b_n_fixation", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_eom": { + "name": "b_lu_eom", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_eom_residues": { + "name": "b_lu_eom_residues", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_rest_oravib": { + "name": "b_lu_rest_oravib", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "b_lu_variety_options": { + "name": "b_lu_variety_options", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "b_lu_start_default": { + "name": "b_lu_start_default", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_date_harvest_default": { + "name": "b_date_harvest_default", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_lu_catalogue_idx": { + "name": "b_lu_catalogue_idx", + "columns": [ + { + "expression": "b_lu_catalogue", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "b_lu_start_default_format": { + "name": "b_lu_start_default_format", + "value": "b_lu_start_default IS NULL OR b_lu_start_default ~ '^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$'" + }, + "b_date_harvest_default_format": { + "name": "b_date_harvest_default_format", + "value": "b_date_harvest_default IS NULL OR b_date_harvest_default ~ '^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$'" + } + }, + "isRLSEnabled": false + }, + "fdm.derogation_applying": { + "name": "derogation_applying", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_derogation": { + "name": "b_id_derogation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "derogation_one_per_farm_per": { + "name": "derogation_one_per_farm_per", + "columns": [ + { + "expression": "b_id_derogation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "derogation_applying_b_id_farm_farms_b_id_farm_fk": { + "name": "derogation_applying_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "derogation_applying", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "derogation_applying_b_id_derogation_derogations_b_id_derogation_fk": { + "name": "derogation_applying_b_id_derogation_derogations_b_id_derogation_fk", + "tableFrom": "derogation_applying", + "tableTo": "derogations", + "schemaTo": "fdm", + "columnsFrom": ["b_id_derogation"], + "columnsTo": ["b_id_derogation"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.derogations": { + "name": "derogations", + "schema": "fdm", + "columns": { + "b_id_derogation": { + "name": "b_id_derogation", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_derogation_year": { + "name": "b_derogation_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.farms": { + "name": "farms", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_name_farm": { + "name": "b_name_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_businessid_farm": { + "name": "b_businessid_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_address_farm": { + "name": "b_address_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_postalcode_farm": { + "name": "b_postalcode_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_farm_idx": { + "name": "b_id_farm_idx", + "columns": [ + { + "expression": "b_id_farm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_acquiring": { + "name": "fertilizer_acquiring", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_acquiring_amount": { + "name": "p_acquiring_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_acquiring_date": { + "name": "p_acquiring_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk": { + "name": "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "fertilizer_acquiring", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_acquiring_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_acquiring_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_acquiring", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_applying": { + "name": "fertilizer_applying", + "schema": "fdm", + "columns": { + "p_app_id": { + "name": "p_app_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_app_amount": { + "name": "p_app_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_app_method": { + "name": "p_app_method", + "type": "p_app_method", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "p_app_date": { + "name": "p_app_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_app_idx": { + "name": "p_app_idx", + "columns": [ + { + "expression": "p_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fertilizer_applying_b_id_fields_b_id_fk": { + "name": "fertilizer_applying_b_id_fields_b_id_fk", + "tableFrom": "fertilizer_applying", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_applying_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_applying_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_applying", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_catalogue_enabling": { + "name": "fertilizer_catalogue_enabling", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_source": { + "name": "p_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk": { + "name": "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "fertilizer_catalogue_enabling", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_picking": { + "name": "fertilizer_picking", + "schema": "fdm", + "columns": { + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id_catalogue": { + "name": "p_id_catalogue", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_picking_date": { + "name": "p_picking_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_picking_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_picking_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_picking", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk": { + "name": "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk", + "tableFrom": "fertilizer_picking", + "tableTo": "fertilizers_catalogue", + "schemaTo": "fdm", + "columnsFrom": ["p_id_catalogue"], + "columnsTo": ["p_id_catalogue"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizers": { + "name": "fertilizers", + "schema": "fdm", + "columns": { + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_id_idx": { + "name": "p_id_idx", + "columns": [ + { + "expression": "p_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizers_catalogue": { + "name": "fertilizers_catalogue", + "schema": "fdm", + "columns": { + "p_id_catalogue": { + "name": "p_id_catalogue", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "p_source": { + "name": "p_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_name_nl": { + "name": "p_name_nl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_name_en": { + "name": "p_name_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "p_description": { + "name": "p_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "p_app_method_options": { + "name": "p_app_method_options", + "type": "p_app_method[]", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "p_dm": { + "name": "p_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_density": { + "name": "p_density", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_om": { + "name": "p_om", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_a": { + "name": "p_a", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_hc": { + "name": "p_hc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_eom": { + "name": "p_eom", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_eoc": { + "name": "p_eoc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_rt": { + "name": "p_c_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_of": { + "name": "p_c_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_if": { + "name": "p_c_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_fr": { + "name": "p_c_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cn_of": { + "name": "p_cn_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_rt": { + "name": "p_n_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_if": { + "name": "p_n_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_of": { + "name": "p_n_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_wc": { + "name": "p_n_wc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_no3_rt": { + "name": "p_no3_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_nh4_rt": { + "name": "p_nh4_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_p_rt": { + "name": "p_p_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_k_rt": { + "name": "p_k_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mg_rt": { + "name": "p_mg_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ca_rt": { + "name": "p_ca_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ne": { + "name": "p_ne", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_s_rt": { + "name": "p_s_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_s_wc": { + "name": "p_s_wc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cu_rt": { + "name": "p_cu_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_zn_rt": { + "name": "p_zn_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_na_rt": { + "name": "p_na_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_si_rt": { + "name": "p_si_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_b_rt": { + "name": "p_b_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mn_rt": { + "name": "p_mn_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ni_rt": { + "name": "p_ni_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_fe_rt": { + "name": "p_fe_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mo_rt": { + "name": "p_mo_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_co_rt": { + "name": "p_co_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_as_rt": { + "name": "p_as_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cd_rt": { + "name": "p_cd_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cr_rt": { + "name": "p_cr_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cr_vi": { + "name": "p_cr_vi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_pb_rt": { + "name": "p_pb_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_hg_rt": { + "name": "p_hg_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cl_rt": { + "name": "p_cl_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ef_nh3": { + "name": "p_ef_nh3", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_type_manure": { + "name": "p_type_manure", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_mineral": { + "name": "p_type_mineral", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_compost": { + "name": "p_type_compost", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_rvo": { + "name": "p_type_rvo", + "type": "p_type_rvo", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_id_catalogue_idx": { + "name": "p_id_catalogue_idx", + "columns": [ + { + "expression": "p_id_catalogue", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.field_acquiring": { + "name": "field_acquiring", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_start": { + "name": "b_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_acquiring_method": { + "name": "b_acquiring_method", + "type": "b_acquiring_method", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "field_acquiring_b_id_fields_b_id_fk": { + "name": "field_acquiring_b_id_fields_b_id_fk", + "tableFrom": "field_acquiring", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "field_acquiring_b_id_farm_farms_b_id_farm_fk": { + "name": "field_acquiring_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "field_acquiring", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.field_discarding": { + "name": "field_discarding", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_end": { + "name": "b_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "field_discarding_b_id_fields_b_id_fk": { + "name": "field_discarding_b_id_fields_b_id_fk", + "tableFrom": "field_discarding", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fields": { + "name": "fields", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_name": { + "name": "b_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_geometry": { + "name": "b_geometry", + "type": "geometry(Polygon,4326)", + "primaryKey": false, + "notNull": false + }, + "b_id_source": { + "name": "b_id_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_buffer": { + "name": "b_buffer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_idx": { + "name": "b_id_idx", + "columns": [ + { + "expression": "b_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "b_geom_idx": { + "name": "b_geom_idx", + "columns": [ + { + "expression": "b_geometry", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestable_analyses": { + "name": "harvestable_analyses", + "schema": "fdm", + "columns": { + "b_id_harvestable_analysis": { + "name": "b_id_harvestable_analysis", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_yield": { + "name": "b_lu_yield", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield_fresh": { + "name": "b_lu_yield_fresh", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield_bruto": { + "name": "b_lu_yield_bruto", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_tarra": { + "name": "b_lu_tarra", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_dm": { + "name": "b_lu_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_moist": { + "name": "b_lu_moist", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_uww": { + "name": "b_lu_uww", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_cp": { + "name": "b_lu_cp", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_harvestable": { + "name": "b_lu_n_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_residue": { + "name": "b_lu_n_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_p_harvestable": { + "name": "b_lu_p_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_p_residue": { + "name": "b_lu_p_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_k_harvestable": { + "name": "b_lu_k_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_k_residue": { + "name": "b_lu_k_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_harvestable_analyses_idx": { + "name": "b_id_harvestable_analyses_idx", + "columns": [ + { + "expression": "b_id_harvestable_analysis", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestable_sampling": { + "name": "harvestable_sampling", + "schema": "fdm", + "columns": { + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_harvestable_analysis": { + "name": "b_id_harvestable_analysis", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_sampling_date": { + "name": "b_sampling_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk": { + "name": "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk", + "tableFrom": "harvestable_sampling", + "tableTo": "harvestables", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable"], + "columnsTo": ["b_id_harvestable"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk": { + "name": "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk", + "tableFrom": "harvestable_sampling", + "tableTo": "harvestable_analyses", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable_analysis"], + "columnsTo": ["b_id_harvestable_analysis"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestables": { + "name": "harvestables", + "schema": "fdm", + "columns": { + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_harvestable_idx": { + "name": "b_id_harvestable_idx", + "columns": [ + { + "expression": "b_id_harvestable", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.intending_grazing": { + "name": "intending_grazing", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_grazing_intention": { + "name": "b_grazing_intention", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "b_grazing_intention_year": { + "name": "b_grazing_intention_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "intending_grazing_b_id_farm_farms_b_id_farm_fk": { + "name": "intending_grazing_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "intending_grazing", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "intending_grazing_b_id_farm_b_grazing_intention_year_pk": { + "name": "intending_grazing_b_id_farm_b_grazing_intention_year_pk", + "columns": ["b_id_farm", "b_grazing_intention_year"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.organic_certifications": { + "name": "organic_certifications", + "schema": "fdm", + "columns": { + "b_id_organic": { + "name": "b_id_organic", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_organic_traces": { + "name": "b_organic_traces", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_organic_skal": { + "name": "b_organic_skal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_organic_issued": { + "name": "b_organic_issued", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_organic_expires": { + "name": "b_organic_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.organic_certifications_holding": { + "name": "organic_certifications_holding", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_organic": { + "name": "b_id_organic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organic_one_farm_per_cert": { + "name": "organic_one_farm_per_cert", + "columns": [ + { + "expression": "b_id_organic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organic_certifications_holding_b_id_farm_farms_b_id_farm_fk": { + "name": "organic_certifications_holding_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "organic_certifications_holding", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organic_certifications_holding_b_id_organic_organic_certifications_b_id_organic_fk": { + "name": "organic_certifications_holding_b_id_organic_organic_certifications_b_id_organic_fk", + "tableFrom": "organic_certifications_holding", + "tableTo": "organic_certifications", + "schemaTo": "fdm", + "columnsFrom": ["b_id_organic"], + "columnsTo": ["b_id_organic"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.soil_analysis": { + "name": "soil_analysis", + "schema": "fdm", + "columns": { + "a_id": { + "name": "a_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "a_date": { + "name": "a_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "a_source": { + "name": "a_source", + "type": "a_source", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false, + "default": "'other'" + }, + "a_al_ox": { + "name": "a_al_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_c_of": { + "name": "a_c_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ca_co": { + "name": "a_ca_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ca_co_po": { + "name": "a_ca_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_caco3_if": { + "name": "a_caco3_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cec_co": { + "name": "a_cec_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_clay_mi": { + "name": "a_clay_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cn_fr": { + "name": "a_cn_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_com_fr": { + "name": "a_com_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cu_cc": { + "name": "a_cu_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_density_sa": { + "name": "a_density_sa", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_fe_ox": { + "name": "a_fe_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_cc": { + "name": "a_k_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_co": { + "name": "a_k_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_co_po": { + "name": "a_k_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_cc": { + "name": "a_mg_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_co": { + "name": "a_mg_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_co_po": { + "name": "a_mg_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_n_pmn": { + "name": "a_n_pmn", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_n_rt": { + "name": "a_n_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_nh4_cc": { + "name": "a_nh4_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_nmin_cc": { + "name": "a_nmin_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_no3_cc": { + "name": "a_no3_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_al": { + "name": "a_p_al", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_cc": { + "name": "a_p_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_ox": { + "name": "a_p_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_rt": { + "name": "a_p_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_sg": { + "name": "a_p_sg", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_wa": { + "name": "a_p_wa", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ph_cc": { + "name": "a_ph_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_s_rt": { + "name": "a_s_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_sand_mi": { + "name": "a_sand_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_silt_mi": { + "name": "a_silt_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_som_loi": { + "name": "a_som_loi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_zn_cc": { + "name": "a_zn_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_gwl_class": { + "name": "b_gwl_class", + "type": "b_gwl_class", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_soiltype_agr": { + "name": "b_soiltype_agr", + "type": "b_soiltype_agr", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.soil_sampling": { + "name": "soil_sampling", + "schema": "fdm", + "columns": { + "b_id_sampling": { + "name": "b_id_sampling", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "a_id": { + "name": "a_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "a_depth_upper": { + "name": "a_depth_upper", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "a_depth_lower": { + "name": "a_depth_lower", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_sampling_date": { + "name": "b_sampling_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_sampling_geometry": { + "name": "b_sampling_geometry", + "type": "geometry(MultiPoint,4326)", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "soil_sampling_b_id_fields_b_id_fk": { + "name": "soil_sampling_b_id_fields_b_id_fk", + "tableFrom": "soil_sampling", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "soil_sampling_a_id_soil_analysis_a_id_fk": { + "name": "soil_sampling_a_id_soil_analysis_a_id_fk", + "tableFrom": "soil_sampling", + "tableTo": "soil_analysis", + "schemaTo": "fdm", + "columnsFrom": ["a_id"], + "columnsTo": ["a_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.account": { + "name": "account", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.invitation": { + "name": "invitation", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "schemaTo": "fdm-authn", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.member": { + "name": "member", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "schemaTo": "fdm-authn", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.organization": { + "name": "organization", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.rate_limit": { + "name": "rate_limit", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.session": { + "name": "session", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.user": { + "name": "user", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surname": { + "name": "surname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lang": { + "name": "lang", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'nl-NL'" + }, + "farm_active": { + "name": "farm_active", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.verification": { + "name": "verification", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authz.audit": { + "name": "audit", + "schema": "fdm-authz", + "columns": { + "audit_id": { + "name": "audit_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "audit_timestamp": { + "name": "audit_timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "audit_origin": { + "name": "audit_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_resource": { + "name": "target_resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_resource_id": { + "name": "target_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granting_resource": { + "name": "granting_resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granting_resource_id": { + "name": "granting_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authz.role": { + "name": "role", + "schema": "fdm-authz", + "columns": { + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted": { + "name": "deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "role_idx": { + "name": "role_idx", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-calculator.calculation_cache": { + "name": "calculation_cache", + "schema": "fdm-calculator", + "columns": { + "calculation_hash": { + "name": "calculation_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calculation_function": { + "name": "calculation_function", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "calculator_version": { + "name": "calculator_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-calculator.calculation_errors": { + "name": "calculation_errors", + "schema": "fdm-calculator", + "columns": { + "calculation_error_id": { + "name": "calculation_error_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calculation_function": { + "name": "calculation_function", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "calculator_version": { + "name": "calculator_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stack_trace": { + "name": "stack_trace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "fdm.b_acquiring_method": { + "name": "b_acquiring_method", + "schema": "fdm", + "values": [ + "nl_01", + "nl_02", + "nl_03", + "nl_04", + "nl_07", + "nl_09", + "nl_10", + "nl_11", + "nl_12", + "nl_13", + "nl_61", + "nl_63", + "unknown" + ] + }, + "fdm.p_app_method": { + "name": "p_app_method", + "schema": "fdm", + "values": [ + "slotted coulter", + "incorporation", + "incorporation 2 tracks", + "injection", + "shallow injection", + "spraying", + "broadcasting", + "spoke wheel", + "pocket placement", + "narrowband" + ] + }, + "fdm.b_gwl_class": { + "name": "b_gwl_class", + "schema": "fdm", + "values": [ + "I", + "Ia", + "Ic", + "II", + "IIa", + "IIb", + "IIc", + "III", + "IIIa", + "IIIb", + "IV", + "IVu", + "IVc", + "V", + "Va", + "Vao", + "Vad", + "Vb", + "Vbo", + "Vbd", + "sV", + "sVb", + "VI", + "VIo", + "VId", + "VII", + "VIIo", + "VIId", + "VIII", + "VIIIo", + "VIIId" + ] + }, + "fdm.b_lu_harvestcat": { + "name": "b_lu_harvestcat", + "schema": "fdm", + "values": [ + "HC010", + "HC020", + "HC031", + "HC040", + "HC041", + "HC042", + "HC050" + ] + }, + "fdm.b_lu_harvestable": { + "name": "b_lu_harvestable", + "schema": "fdm", + "values": ["none", "once", "multiple"] + }, + "fdm.b_lu_croprotation": { + "name": "b_lu_croprotation", + "schema": "fdm", + "values": [ + "other", + "clover", + "nature", + "potato", + "grass", + "rapeseed", + "starch", + "maize", + "cereal", + "sugarbeet", + "alfalfa", + "catchcrop" + ] + }, + "fdm.a_source": { + "name": "a_source", + "schema": "fdm", + "values": [ + "nl-rva-l122", + "nl-rva-l136", + "nl-rva-l264", + "nl-rva-l320", + "nl-rva-l335", + "nl-rva-l610", + "nl-rva-l648", + "nl-rva-l697", + "nl-other-nmi", + "other" + ] + }, + "fdm.b_soiltype_agr": { + "name": "b_soiltype_agr", + "schema": "fdm", + "values": [ + "moerige_klei", + "rivierklei", + "dekzand", + "zeeklei", + "dalgrond", + "veen", + "loess", + "duinzand", + "maasklei" + ] + }, + "fdm.p_type_rvo": { + "name": "p_type_rvo", + "schema": "fdm", + "values": [ + "10", + "11", + "12", + "13", + "14", + "17", + "18", + "19", + "23", + "30", + "31", + "32", + "33", + "35", + "39", + "40", + "41", + "42", + "43", + "46", + "50", + "56", + "60", + "61", + "75", + "76", + "80", + "81", + "90", + "91", + "92", + "25", + "26", + "27", + "95", + "96", + "97", + "98", + "99", + "100", + "101", + "102", + "103", + "104", + "105", + "106", + "107", + "108", + "109", + "110", + "111", + "112", + "113", + "114", + "115", + "116", + "117", + "120" + ] + } + }, + "schemas": { + "fdm": "fdm", + "fdm-authn": "fdm-authn", + "fdm-authz": "fdm-authz", + "fdm-calculator": "fdm-calculator" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/fdm-core/src/db/migrations/meta/_journal.json b/fdm-core/src/db/migrations/meta/_journal.json index 26da9ff47..ca8688f23 100644 --- a/fdm-core/src/db/migrations/meta/_journal.json +++ b/fdm-core/src/db/migrations/meta/_journal.json @@ -148,6 +148,13 @@ "when": 1767697779747, "tag": "0020_thick_zodiak", "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1768485087752, + "tag": "0021_exotic_mesmero", + "breakpoints": true } ] } diff --git a/fdm-core/src/db/schema.ts b/fdm-core/src/db/schema.ts index 721062bbe..210b4e77c 100644 --- a/fdm-core/src/db/schema.ts +++ b/fdm-core/src/db/schema.ts @@ -98,6 +98,7 @@ export const fields = fdmSchema.table( type: "Polygon", }), // PGLite does not support PostGIS yet; I expect to be supported in Q4 2024: https://github.com/electric-sql/pglite/issues/11 b_id_source: text(), + b_buffer: boolean().notNull().default(false), created: timestamp({ withTimezone: true }).notNull().defaultNow(), updated: timestamp({ withTimezone: true }), }, diff --git a/fdm-core/src/field.d.ts b/fdm-core/src/field.d.ts index 4d7bbb72f..9e394c97c 100644 --- a/fdm-core/src/field.d.ts +++ b/fdm-core/src/field.d.ts @@ -12,5 +12,5 @@ export interface Field { b_start: schema.fieldAcquiringTypeSelect["b_start"] b_end: schema.fieldDiscardingTypeSelect["b_end"] b_acquiring_method: schema.fieldAcquiringTypeSelect["b_acquiring_method"] - b_isproductive: boolean | null + b_buffer: boolean } diff --git a/fdm-core/src/field.test.ts b/fdm-core/src/field.test.ts index ab4896e47..f271cab95 100644 --- a/fdm-core/src/field.test.ts +++ b/fdm-core/src/field.test.ts @@ -9,7 +9,7 @@ import { createFdmServer } from "./fdm-server" import type { FdmServerType } from "./fdm-server.d" import { addField, - determineIfFieldIsProductive, + determineIfFieldIsBuffer, getField, getFields, listAvailableAcquiringMethods, @@ -106,7 +106,7 @@ describe("Farm Data Model", () => { expect(field.b_centroid[1]).toBeTypeOf("number") expect(field.b_area).toBeGreaterThan(0) expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) expect(field.b_start).toEqual(AcquireDate) expect(field.b_end).toEqual(discardingDate) expect(field.b_acquiring_method).toBe(AcquiringMethod) @@ -238,7 +238,7 @@ describe("Farm Data Model", () => { ) for (const field of fields) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) } }) @@ -379,7 +379,7 @@ describe("Farm Data Model", () => { expect(fields1.length).toBe(1) expect(fields1[0].b_name).toBe(field2Name) expect(fields1[0].b_perimeter).toBeGreaterThan(0) - expect(fields1[0].b_isproductive).toBe(true) + expect(fields1[0].b_buffer).toBe(false) // Test with a timeframe that includes both Field 1 and Field 2 const timeframe2 = { @@ -398,7 +398,7 @@ describe("Farm Data Model", () => { ) for (const field of fields2) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) } // Test with a timeframe that includes field 2 and field 3 @@ -419,7 +419,7 @@ describe("Farm Data Model", () => { ) for (const field of fields3) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) } //Test with only start date const fields4 = await getFields(fdm, principal_id, b_id_farm, { @@ -437,7 +437,7 @@ describe("Farm Data Model", () => { ) for (const field of fields4) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) } //Test with only end date const fields5 = await getFields(fdm, principal_id, b_id_farm, { @@ -450,7 +450,7 @@ describe("Farm Data Model", () => { ) for (const field of fields5) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) } }) }) @@ -721,7 +721,7 @@ describe("Farm Data Model", () => { expect(field.b_name).toBe(fieldName) expect(field.b_perimeter).toBeGreaterThan(0) expect(field.b_perimeter).toBeGreaterThan(4000000) - expect(field.b_isproductive).toBe(true) + expect(field.b_buffer).toBe(false) }) }) @@ -960,47 +960,47 @@ describe("Farm Data Model", () => { }) }) - describe("determineIfFieldIsProductive", () => { - it("should determine if a field is productive by checking name", async () => { - const b_isproductive = determineIfFieldIsProductive( + describe("determineIfFieldIsBuffer", () => { + it("should determine if a field is buffer by checking name", async () => { + const isBuffer = determineIfFieldIsBuffer( 1.0, 100.0, "Bufferstrip", ) - expect(b_isproductive).toBe(false) + expect(isBuffer).toBe(true) }) - it("should determine if a field is productive by checking shape", async () => { - const b_isproductive = determineIfFieldIsProductive( + it("should determine if a field is buffer by checking shape", async () => { + const isBuffer = determineIfFieldIsBuffer( 1.0, 10000.0, "Field", ) - expect(b_isproductive).toBe(false) + expect(isBuffer).toBe(true) }) - it("should determine if a field is productive by checking shape (area is large enough)", async () => { - const b_isproductive = determineIfFieldIsProductive( + it("should determine if a field is productive (not buffer) by checking shape (area is large enough)", async () => { + const isBuffer = determineIfFieldIsBuffer( 2.5, 10000.0, "Field", ) - expect(b_isproductive).toBe(true) + expect(isBuffer).toBe(false) }) - it("should determine if a field is productive by checking shape and name", async () => { - const b_isproductive = determineIfFieldIsProductive( + it("should determine if a field is buffer by checking shape and name", async () => { + const isBuffer = determineIfFieldIsBuffer( 1.0, 1000.0, "Bufferstrip", ) - expect(b_isproductive).toBe(false) + expect(isBuffer).toBe(true) }) - it("should determine if a field is productive by checking shape and name (productive)", async () => { - const b_isproductive = determineIfFieldIsProductive( + it("should determine if a field is buffer by checking shape and name (productive)", async () => { + const isBuffer = determineIfFieldIsBuffer( 10.0, 100.0, "Field", ) - expect(b_isproductive).toBe(true) + expect(isBuffer).toBe(false) }) }) diff --git a/fdm-core/src/field.ts b/fdm-core/src/field.ts index eb15abb9e..33318965c 100644 --- a/fdm-core/src/field.ts +++ b/fdm-core/src/field.ts @@ -53,6 +53,7 @@ export async function addField( b_start: schema.fieldAcquiringTypeInsert["b_start"], b_acquiring_method: schema.fieldAcquiringTypeInsert["b_acquiring_method"], b_end?: schema.fieldDiscardingTypeInsert["b_end"], + b_buffer?: schema.fieldsTypeInsert["b_buffer"], ): Promise { try { await checkPermission( @@ -74,6 +75,7 @@ export async function addField( b_name: b_name, b_id_source: b_id_source, b_geometry: b_geometry, + b_buffer: b_buffer ?? false, } await tx.insert(schema.fields).values(fieldData) @@ -111,6 +113,34 @@ export async function addField( } await tx.insert(schema.fieldDiscarding).values(fieldDiscardingData) + // If buffer status is not provided try to determine + if (b_buffer === undefined) { + const field = await tx + .select({ + b_id: schema.fields.b_id, + b_name: schema.fields.b_name, + b_geometry: schema.fields.b_geometry, + b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, + b_perimeter: sql`ROUND((ST_Length(ST_ExteriorRing(b_geometry)::geography))::NUMERIC, 2)::FLOAT`, + }) + .from(schema.fields) + .where(eq(schema.fields.b_id, b_id)) + .limit(1) + + if (field.length > 0) { + const isBuffer = determineIfFieldIsBuffer( + field[0].b_area, + field[0].b_perimeter, + field[0].b_name, + ) + + await tx + .update(schema.fields) + .set({ b_buffer: isBuffer }) + .where(eq(schema.fields.b_id, b_id)) + } + } + return b_id }) } catch (err) { @@ -122,6 +152,7 @@ export async function addField( b_start, b_acquiring_method, b_end, + b_buffer, }) } } @@ -163,6 +194,7 @@ export async function getField( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, + b_buffer: schema.fields.b_buffer, b_centroid_x: sql`ST_X(ST_Centroid(b_geometry))`, b_centroid_y: sql`ST_Y(ST_Centroid(b_geometry))`, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, @@ -187,11 +219,6 @@ export async function getField( field[0].b_centroid = [field[0].b_centroid_x, field[0].b_centroid_y] field[0].b_centroid_x = undefined field[0].b_centroid_y = undefined - field[0].b_isproductive = determineIfFieldIsProductive( - field[0].b_area, - field[0].b_perimeter, - field[0].b_name, - ) return field[0] } catch (err) { @@ -276,6 +303,7 @@ export async function getFields( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, + b_buffer: schema.fields.b_buffer, b_centroid_x: sql`ST_X(ST_Centroid(b_geometry))`, b_centroid_y: sql`ST_Y(ST_Centroid(b_geometry))`, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, @@ -301,11 +329,6 @@ export async function getFields( field.b_centroid = [field.b_centroid_x, field.b_centroid_y] field.b_centroid_x = undefined field.b_centroid_y = undefined - field.b_isproductive = determineIfFieldIsProductive( - field.b_area, - field.b_perimeter, - field.b_name, - ) } return fields @@ -344,6 +367,7 @@ export async function updateField( b_start?: schema.fieldAcquiringTypeInsert["b_start"], b_acquiring_method?: schema.fieldAcquiringTypeInsert["b_acquiring_method"], b_end?: schema.fieldDiscardingTypeInsert["b_end"], + b_buffer?: schema.fieldsTypeInsert["b_buffer"], ): Promise { return await fdm.transaction(async (tx: FdmType) => { try { @@ -368,6 +392,9 @@ export async function updateField( if (b_geometry !== undefined) { setFields.b_geometry = b_geometry } + if (b_buffer !== undefined) { + setFields.b_buffer = b_buffer + } setFields.updated = updated await tx @@ -409,6 +436,7 @@ export async function updateField( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, + b_buffer: schema.fields.b_buffer, b_start: schema.fieldAcquiring.b_start, b_acquiring_method: schema.fieldAcquiring.b_acquiring_method, @@ -427,11 +455,12 @@ export async function updateField( ) .where(eq(schema.fields.b_id, b_id)) .limit(1) - const field = result[0] + const field = result[0] as unknown as Field // Check if acquiring date is before discarding date if ( field.b_end && + field.b_start && field.b_start.getTime() >= field.b_end.getTime() ) { throw new Error("Acquiring date must be before discarding date") @@ -652,34 +681,34 @@ export function listAvailableAcquiringMethods(): { } /** - * Determines if a field is considered productive based on its area, perimeter, and name. + * Determines if a field is considered a buffer based on its area, perimeter, and name. * - * This function uses two heuristics to differentiate between productive fields and non-productive areas like buffer strips: - * 1. Shape-based: A field is classified as non-productive if its area is less than 2.5 hectares and the ratio of its perimeter + * This function uses two heuristics to differentiate between productive fields and buffer strips: + * 1. Shape-based: A field is classified as buffer if its area is less than 2.5 hectares and the ratio of its perimeter * to the square root of its area (in square meters) is greater than or equal to a predefined constant (20). - * 2. Name-based: A field is classified as non-productive if its name contains "buffer" (case-insensitive). + * 2. Name-based: A field is classified as buffer if its name contains "buffer" (case-insensitive). * - * A field is considered productive only if both checks pass. + * A field is considered buffer only if one of the checks pass. * * @param b_area The area of the field in hectares. * @param b_perimeter The perimeter of the field in meters. * @param b_name The name of the field. - * @returns `true` if the field is determined to be productive, `false` otherwise. + * @returns `true` if the field is determined to be buffer, `false` otherwise. * @alpha */ -export function determineIfFieldIsProductive( +export function determineIfFieldIsBuffer( b_area: number, b_perimeter: number, b_name: schema.fieldsTypeSelect["b_name"], ) { // Sven found that a ratio for a field with Perimeter (m) / SQRT(Area (m^2)) usually differentiates buffferstrips from "normal" fields when the ratio is larger than 20 and area smaller than 2.5 ha const BUFFERSTROKEN_CONSTANT = 20 - const productiveAssumedByShape = - b_perimeter / Math.sqrt(b_area * 10000) < BUFFERSTROKEN_CONSTANT || - b_area >= 2.5 + const bufferAssumedByShape = + b_perimeter / Math.sqrt(b_area * 10000) >= BUFFERSTROKEN_CONSTANT && + b_area < 2.5 // Check if name contains 'buffer' - const productiveAssumedByName = !b_name.toLowerCase().includes("buffer") + const bufferAssumedByName = !b_name.toLowerCase().includes("buffer") - return productiveAssumedByShape && productiveAssumedByName + return bufferAssumedByShape || !bufferAssumedByName } From 4687738e3b8ef35d071ae16b218d567a3cfbf3be Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:45:21 +0100 Subject: [PATCH 02/28] feat: take buffer strips into account in calculations --- .changeset/crisp-toys-laugh.md | 5 + .changeset/plenty-doors-design.md | 5 + .changeset/rich-rabbits-yell.md | 5 + .../src/balance/nitrogen/index.test.ts | 125 +++++++++++++++++- fdm-calculator/src/balance/nitrogen/index.ts | 47 ++++++- .../src/balance/nitrogen/types.d.ts | 6 +- .../src/balance/organic-matter/index.test.ts | 54 ++++++++ .../src/balance/organic-matter/index.ts | 24 +++- .../src/balance/organic-matter/types.ts | 11 +- .../value/dierlijke-mest-gebruiksnorm.test.ts | 16 +++ .../2025/value/dierlijke-mest-gebruiksnorm.ts | 8 ++ .../nl/2025/value/fosfaatgebruiksnorm.test.ts | 20 +++ .../nl/2025/value/fosfaatgebruiksnorm.ts | 9 ++ .../2025/value/stikstofgebruiksnorm.test.ts | 23 ++++ .../nl/2025/value/stikstofgebruiksnorm.ts | 8 ++ .../src/norms/nl/2025/value/types.d.ts | 2 +- .../value/dierlijke-mest-gebruiksnorm.test.ts | 19 ++- .../2026/value/dierlijke-mest-gebruiksnorm.ts | 17 ++- .../nl/2026/value/fosfaatgebruiksnorm.test.ts | 20 +++ .../nl/2026/value/fosfaatgebruiksnorm.ts | 9 ++ .../2026/value/stikstofgebruiksnorm.test.ts | 23 ++++ .../nl/2026/value/stikstofgebruiksnorm.ts | 8 ++ .../src/norms/nl/2026/value/types.d.ts | 2 +- .../src/nutrient-advice/index.test.ts | 17 +++ fdm-calculator/src/nutrient-advice/index.ts | 23 ++++ fdm-calculator/src/nutrient-advice/types.d.ts | 2 + 26 files changed, 498 insertions(+), 10 deletions(-) create mode 100644 .changeset/crisp-toys-laugh.md create mode 100644 .changeset/plenty-doors-design.md create mode 100644 .changeset/rich-rabbits-yell.md diff --git a/.changeset/crisp-toys-laugh.md b/.changeset/crisp-toys-laugh.md new file mode 100644 index 000000000..f6d2e118b --- /dev/null +++ b/.changeset/crisp-toys-laugh.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": minor +--- + +Exclude buffer strips from calculating farm balances and set field values to 0 diff --git a/.changeset/plenty-doors-design.md b/.changeset/plenty-doors-design.md new file mode 100644 index 000000000..f25b77d8a --- /dev/null +++ b/.changeset/plenty-doors-design.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": minor +--- + +Do not provide nutrient advice for buffer strips by setting the output to 0 diff --git a/.changeset/rich-rabbits-yell.md b/.changeset/rich-rabbits-yell.md new file mode 100644 index 000000000..79448d951 --- /dev/null +++ b/.changeset/rich-rabbits-yell.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-calculator": minor +--- + +For buffer strips set the norm values to 0 as they have no 'plaatsingsruimte' diff --git a/fdm-calculator/src/balance/nitrogen/index.test.ts b/fdm-calculator/src/balance/nitrogen/index.test.ts index 21d627a96..873b004fa 100644 --- a/fdm-calculator/src/balance/nitrogen/index.test.ts +++ b/fdm-calculator/src/balance/nitrogen/index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest" -import { calculateNitrogenBalance } from "." -import type { NitrogenBalanceInput } from "./types" +import { calculateNitrogenBalance, calculateNitrogenBalancesFieldToFarm } from "." +import type { NitrogenBalanceInput, NitrogenBalanceFieldResultNumeric } from "./types" import type { FdmType } from "@svenvw/fdm-core" import Decimal from "decimal.js" @@ -198,4 +198,125 @@ describe("calculateNitrogenBalance", () => { expect(result.hasErrors).toBe(true) expect(result.fieldErrorMessages.length).toBeGreaterThan(0) }) + + it("should return zero balance for buffer strips", async () => { + const mockNitrogenBalanceInput: NitrogenBalanceInput = { + fields: [ + { + field: { + b_id: "field1", + b_centroid: [5.0, 52.0], + b_area: 100, + b_start: new Date("2023-01-01"), + b_end: new Date("2023-12-31"), + b_buffer: true, + }, + cultivations: [], + harvests: [], + soilAnalyses: [], + fertilizerApplications: [], + depositionSupply: { + total: new Decimal(0), + }, + }, + ], + fertilizerDetails: [], + cultivationDetails: [], + timeFrame: { + start: new Date("2023-01-01"), + end: new Date("2023-12-31"), + }, + } + + const result = await calculateNitrogenBalance( + mockFdm, + mockNitrogenBalanceInput, + ) + + expect(result.balance).toBe(0) + expect(result.supply.total).toBe(0) + expect(result.removal.total).toBe(0) + expect(result.emission.total).toBe(0) + }) + + it("should ignore buffer strips in farm-level aggregation", () => { + const results: NitrogenBalanceFieldResultNumeric[] = [ + { + b_id: "field1", + b_area: 10, + b_buffer: false, + balance: { + balance: 100, + supply: { + total: 100, + deposition: { total: 0 }, + fixation: { total: 0 }, + mineralisation: { total: 0 }, + fertilizers: { + total: 0, + mineral: { total: 0 }, + manure: { total: 0 }, + compost: { total: 0 }, + other: { total: 0 }, + }, + } as any, + removal: { + total: 0, + harvests: { total: 0 }, + residues: { total: 0 }, + } as any, + target: 0, + emission: { + total: 0, + ammonia: { + total: 0, + fertilizers: { + total: 0, + mineral: { total: 0 }, + manure: { total: 0 }, + compost: { total: 0 }, + other: { total: 0 }, + }, + residues: { total: 0 }, + }, + nitrate: { total: 0 }, + } as any, + } as any, + }, + { + b_id: "buffer1", + b_area: 100, + b_buffer: true, + balance: { + balance: 0, + supply: { + total: 0, + deposition: { total: 0 }, + fixation: { total: 0 }, + mineralisation: { total: 0 }, + fertilizers: { + total: 0, + mineral: { total: 0 }, + manure: { total: 0 }, + compost: { total: 0 }, + other: { total: 0 }, + }, + } as any, + removal: { total: 0 } as any, + target: 0, + emission: { total: 0 } as any, + } as any, + }, + ] + + const farmBalance = calculateNitrogenBalancesFieldToFarm( + results, + false, + [], + ) + + // Should match field1 values exactly + expect(farmBalance.balance).toBe(100) + expect(farmBalance.supply.total).toBe(100) + }) }) diff --git a/fdm-calculator/src/balance/nitrogen/index.ts b/fdm-calculator/src/balance/nitrogen/index.ts index 88d11acf8..c9e2572a9 100644 --- a/fdm-calculator/src/balance/nitrogen/index.ts +++ b/fdm-calculator/src/balance/nitrogen/index.ts @@ -53,12 +53,14 @@ export async function calculateNitrogenBalance( return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, + b_buffer: fieldInput.field.b_buffer ?? false, balance, } } catch (error) { return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, + b_buffer: fieldInput.field.b_buffer ?? false, errorMessage: error instanceof Error ? error.message @@ -121,6 +123,48 @@ export function calculateNitrogenBalanceField( throw new Error("Timeframe start and end dates must be provided.") } + if (field.b_buffer) { + return { + b_id: field.b_id, + balance: 0, + supply: { + total: 0, + fertilizers: { + total: 0, + mineral: { total: 0, applications: [] }, + manure: { total: 0, applications: [] }, + compost: { total: 0, applications: [] }, + other: { total: 0, applications: [] }, + }, + fixation: { total: 0, cultivations: [] }, + deposition: { total: 0 }, + mineralisation: { total: 0 }, + }, + removal: { + total: 0, + harvests: { total: 0, harvests: [] }, + residues: { total: 0, cultivations: [] }, + }, + emission: { + total: 0, + ammonia: { + total: 0, + fertilizers: { + total: 0, + mineral: { total: 0, applications: [] }, + manure: { total: 0, applications: [] }, + compost: { total: 0, applications: [] }, + other: { total: 0, applications: [] }, + }, + residues: { total: 0, cultivations: [] }, + grazing: undefined, + }, + nitrate: { total: 0 }, + }, + target: 0, + } + } + const fertilizerDetailsMap = new Map( fertilizerDetails.map((detail) => [detail.p_id_catalogue, detail]), ) @@ -261,8 +305,9 @@ export function calculateNitrogenBalancesFieldToFarm( fieldErrorMessages: string[], ): NitrogenBalanceNumeric { // Filter out fields that have errors for aggregation + // Also filter out buffer strips as they should be ignored in the farm-level aggregation const successfulFieldBalances = fieldsWithBalanceResults.filter( - (result) => result.balance !== undefined, + (result) => result.balance !== undefined && !result.b_buffer, ) as (NitrogenBalanceFieldResultNumeric & { balance: NitrogenBalanceFieldNumeric })[] diff --git a/fdm-calculator/src/balance/nitrogen/types.d.ts b/fdm-calculator/src/balance/nitrogen/types.d.ts index 5e661e32a..b153fd9a5 100644 --- a/fdm-calculator/src/balance/nitrogen/types.d.ts +++ b/fdm-calculator/src/balance/nitrogen/types.d.ts @@ -469,7 +469,10 @@ export type SoilAnalysisPicked = Pick< * Represents the structure of fields with related entities for nitrogen balance calculation */ export type FieldInput = { - field: Pick + field: Pick< + Field, + "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_buffer" + > cultivations: Pick< Cultivation, | "b_lu" @@ -670,6 +673,7 @@ export type NitrogenBalanceFieldNumeric = { export type NitrogenBalanceFieldResultNumeric = { b_id: string b_area: number + b_buffer: boolean balance?: NitrogenBalanceFieldNumeric errorMessage?: string } diff --git a/fdm-calculator/src/balance/organic-matter/index.test.ts b/fdm-calculator/src/balance/organic-matter/index.test.ts index 4f0dbf336..c2114695d 100644 --- a/fdm-calculator/src/balance/organic-matter/index.test.ts +++ b/fdm-calculator/src/balance/organic-matter/index.test.ts @@ -61,6 +61,24 @@ describe("Organic Matter Balance Calculation", () => { expect(result.supply.total).toBe(500) expect(result.degradation.total).toBe(-200) }) + + it("should return zero balance for buffer strips", () => { + const result = calculateOrganicMatterBalanceField({ + fieldInput: { + field: { ...mockField, b_buffer: true }, + cultivations: mockCultivations, + fertilizerApplications: mockFertilizerApplications, + soilAnalyses: mockSoilAnalyses, + }, + fertilizerDetails: [], + cultivationDetails: [], + timeFrame, + }) + + expect(result.balance).toBe(0) + expect(result.supply.total).toBe(0) + expect(result.degradation.total).toBe(0) + }) }) describe("calculateOrganicMatterBalancesFieldToFarm", () => { @@ -125,6 +143,42 @@ describe("Organic Matter Balance Calculation", () => { expect(farmBalance.degradation).toBeCloseTo(-200) expect(farmBalance.balance).toBeCloseTo(300) }) + + it("should ignore buffer strips in farm-level aggregation", () => { + const results: OrganicMatterBalanceFieldResultNumeric[] = [ + { + b_id: "field1", + b_area: 10, + b_buffer: false, + balance: { + supply: { total: 500 }, + degradation: { total: -200 }, + balance: 300, + } as OrganicMatterBalanceFieldNumeric, + }, + { + b_id: "buffer1", + b_area: 100, + b_buffer: true, + balance: { + supply: { total: 0 }, + degradation: { total: 0 }, + balance: 0, + } as OrganicMatterBalanceFieldNumeric, + }, + ] + + const farmBalance = calculateOrganicMatterBalancesFieldToFarm( + results, + false, + [], + ) + + // Should match field1 values exactly + expect(farmBalance.supply).toBe(500) + expect(farmBalance.degradation).toBe(-200) + expect(farmBalance.balance).toBe(300) + }) }) describe("convertOrganicMatterBalanceToNumeric", () => { diff --git a/fdm-calculator/src/balance/organic-matter/index.ts b/fdm-calculator/src/balance/organic-matter/index.ts index 3061cddc6..0a704c0bf 100644 --- a/fdm-calculator/src/balance/organic-matter/index.ts +++ b/fdm-calculator/src/balance/organic-matter/index.ts @@ -59,12 +59,14 @@ export async function calculateOrganicMatterBalance( return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, + b_buffer: fieldInput.field.b_buffer ?? false, balance, } } catch (error) { return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, + b_buffer: fieldInput.field.b_buffer ?? false, errorMessage: error instanceof Error ? error.message @@ -111,6 +113,25 @@ export function calculateOrganicMatterBalanceField( const { field, cultivations, fertilizerApplications, soilAnalyses } = fieldInput + if (field.b_buffer) { + return { + b_id: field.b_id, + balance: 0, + supply: { + total: 0, + fertilizers: { + total: 0, + manure: { total: 0, applications: [] }, + compost: { total: 0, applications: [] }, + other: { total: 0, applications: [] }, + }, + cultivations: { total: 0, cultivations: [] }, + residues: { total: 0, cultivations: [] }, + }, + degradation: { total: 0 }, + } as OrganicMatterBalanceFieldNumeric + } + const fertilizerDetailsMap = new Map( fertilizerDetails.map((detail: FertilizerDetail) => [ detail.p_id_catalogue, @@ -194,8 +215,9 @@ export function calculateOrganicMatterBalancesFieldToFarm( fieldErrorMessages: string[], ): OrganicMatterBalanceNumeric { // Filter out fields that have errors to ensure they are not included in the aggregation. + // Also filter out buffer strips as they should be ignored in the farm-level aggregation const successfulFieldBalances = fieldsWithBalanceResults.filter( - (result) => result.balance !== undefined, + (result) => result.balance !== undefined && !result.b_buffer, ) as (OrganicMatterBalanceFieldResultNumeric & { balance: OrganicMatterBalanceFieldNumeric })[] diff --git a/fdm-calculator/src/balance/organic-matter/types.ts b/fdm-calculator/src/balance/organic-matter/types.ts index a7c9c309f..022592a2a 100644 --- a/fdm-calculator/src/balance/organic-matter/types.ts +++ b/fdm-calculator/src/balance/organic-matter/types.ts @@ -193,7 +193,10 @@ export type SoilAnalysisPicked = Pick< */ export type FieldInput = { /** The core details of the field. */ - field: Pick + field: Pick< + Field, + "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_buffer" + > /** The list of cultivations that took place on the field. */ cultivations: Pick< Cultivation, @@ -328,9 +331,15 @@ export type OrganicMatterBalanceFieldNumeric = { /** Numeric version of `OrganicMatterBalanceFieldResult`. */ export type OrganicMatterBalanceFieldResultNumeric = { + /** The unique identifier of the field. */ b_id: string + /** The area of the field in hectares. */ b_area: number + /** Whether the field is a buffer strip */ + b_buffer: boolean + /** The detailed organic matter balance for the field. Undefined if an error occurred. */ balance?: OrganicMatterBalanceFieldNumeric + /** An error message if the calculation for this field failed. */ errorMessage?: string } diff --git a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts index 5cc52cb10..0c5cba91f 100644 --- a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts @@ -113,6 +113,22 @@ describe("calculateNL2025DierlijkeMestGebruiksNorm", () => { expect(result.normSource).toBe("Derogatie - Natura2000 Gebied") }) + it("should return 0 for buffer strips", async () => { + const mockInput: NL2025NormsInput = { + farm: { is_derogatie_bedrijf: true, has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.641351453912945, 51.97755938887036], + b_buffer: true, + }, + cultivations: [], + soilAnalysis: { a_p_cc: 0, a_p_al: 0 }, + } + const result = await calculateNL2025DierlijkeMestGebruiksNorm(mockInput) + expect(result.normValue).toBe(0) + expect(result.normSource).toBe("Bufferstrook: geen plaatsingsruimte") + }) + describe("isFieldInDerogatieVrijeZone", () => { it("should return true for a location inside the derogatie-vrije zone", async () => { const locationInside: [number, number] = [ 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 e4b953717..df97b9f60 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 @@ -151,6 +151,14 @@ export async function calculateNL2025DierlijkeMestGebruiksNorm( const is_derogatie_bedrijf = input.farm.is_derogatie_bedrijf || false const field = input.field + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + const [ is_nv_gebied, is_gwbg_gebied, diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts index 08df4dae4..1fe5cacc8 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts @@ -40,4 +40,24 @@ describe("calculateNL2025FosfaatGebruiksNorm", () => { expect(result.normValue).toBe(120) expect(result.normSource).toContain("Bouwland") }) + + it("should return 0 for buffer strips", async () => { + const mockInput: NL2025NormsInput = { + farm: { is_derogatie_bedrijf: false, has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: [5.0, 52.0], + b_buffer: true, + }, + cultivations: [ + { + b_lu_catalogue: "nl_101", + } as Partial, + ] as NL2025NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + const result = await calculateNL2025FosfaatGebruiksNorm(mockInput) + expect(result.normValue).toBe(0) + expect(result.normSource).toBe("Bufferstrook: geen plaatsingsruimte") + }) }) diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index ff2078c35..1ef3f89e6 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -133,6 +133,15 @@ function getFosfaatKlasse( export async function calculateNL2025FosfaatGebruiksNorm( input: NL2025NormsInput, ): Promise { + const field = input.field + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + const cultivations = input.cultivations const a_p_cc = input.soilAnalysis.a_p_cc const a_p_al = input.soilAnalysis.a_p_al diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts index f9332cb23..f40ff2525 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts @@ -90,6 +90,29 @@ describe(" calculateNL2025StikstofGebruiksNorm", () => { expect(result.normSource).toEqual("Grasland (volledig maaien).") }) + it("should return 0 for buffer strips", async () => { + const mockInput: NL2025NormsInput = { + farm: { is_derogatie_bedrijf: false, has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], + b_buffer: true, + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_265", + b_lu_start: new Date(2025, 0, 1), + b_lu_end: new Date(2025, 5, 1), + } as Partial, + ] as NL2025NormsInputForCultivation[], + soilAnalysis: { a_p_al: 20, a_p_cc: 0.9 }, + } + + const result = await calculateNL2025StikstofGebruiksNorm(mockInput) + expect(result.normValue).toBe(0) + expect(result.normSource).toEqual("Bufferstrook: geen plaatsingsruimte") + }) + it("should return the correct norm for potatoes", async () => { const mockInput: NL2025NormsInput = { farm: { is_derogatie_bedrijf: false, has_grazing_intention: false }, diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index ee12f7ce1..0c913b637 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -814,6 +814,14 @@ export async function calculateNL2025StikstofGebruiksNorm( const field = input.field const cultivations = input.cultivations + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + // Determine hoofdteelt const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2025) let cultivation = cultivations.find( 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 8a99739d4..61ba94a41 100644 --- a/fdm-calculator/src/norms/nl/2025/value/types.d.ts +++ b/fdm-calculator/src/norms/nl/2025/value/types.d.ts @@ -18,7 +18,7 @@ export type NL2025NormsInput = { has_grazing_intention: boolean } /** The field record from fdm-core, including its ID and centroid for location-based checks. */ - field: Pick + field: Pick /** An array of all cultivations on the field with their required norm inputs. */ cultivations: NL2025NormsInputForCultivation[] /** The most recent soil analysis data available before the start of the cultivation. */ 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 index d84d497cf..a92f827d8 100644 --- 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 @@ -1,10 +1,27 @@ import { describe, expect, it } from "vitest" import { calculateNL2026DierlijkeMestGebruiksNorm } from "./dierlijke-mest-gebruiksnorm" +import type { NL2026NormsInput } from "./types" describe("calculateNL2026DierlijkeMestGebruiksNorm", () => { it("should return the default norm value", async () => { - const result = await calculateNL2026DierlijkeMestGebruiksNorm() + const input = { + field: { + b_buffer: false, + }, + } as NL2026NormsInput + const result = await calculateNL2026DierlijkeMestGebruiksNorm(input) expect(result.normValue).toBe(170) expect(result.normSource).toBe("Standaard - geen derogatie") }) + + it("should return 0 for buffer strips", async () => { + const input = { + field: { + b_buffer: true, + }, + } as NL2026NormsInput + const result = await calculateNL2026DierlijkeMestGebruiksNorm(input) + expect(result.normValue).toBe(0) + expect(result.normSource).toBe("Bufferstrook: geen plaatsingsruimte") + }) }) 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 bcacb023f..4e21718f8 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 @@ -2,12 +2,15 @@ import { withCalculationCache } from "@svenvw/fdm-core" import pkg from "../../../../package" import type { DierlijkeMestGebruiksnormResult } from "../../types" +import type { NL2026NormsInput } from "./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 of type `NL2026NormsInput` containing all necessary data. * @returns An object of type `DierlijkeMestGebruiksnormResult` containing the determined * nitrogen usage standard (`normValue`) and a `normSource` string explaining the rule applied. * @@ -16,7 +19,19 @@ import type { DierlijkeMestGebruiksnormResult } from "../../types" * - **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 { +export async function calculateNL2026DierlijkeMestGebruiksNorm( + input: NL2026NormsInput, +): Promise { + const field = input.field + + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + const normValue = 170 const normSource = "Standaard - geen derogatie" diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts index 44c5fe200..dbd4bbe26 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts @@ -40,4 +40,24 @@ describe("calculateNL2026FosfaatGebruiksNorm", () => { expect(result.normValue).toBe(120) expect(result.normSource).toContain("Bouwland") }) + + it("should return 0 for buffer strips", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: true }, + field: { + b_id: "1", + b_centroid: [5.0, 52.0], + b_buffer: true, + }, + 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(0) + expect(result.normSource).toBe("Bufferstrook: geen plaatsingsruimte") + }) }) diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts index 4fdbf5776..5d5d9f7cd 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -123,6 +123,15 @@ function getFosfaatKlasse( export async function calculateNL2026FosfaatGebruiksNorm( input: NL2026NormsInput, ): Promise { + const field = input.field + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + const cultivations = input.cultivations const a_p_cc = input.soilAnalysis.a_p_cc const a_p_al = input.soilAnalysis.a_p_al 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 a659a5e37..5ec8bc371 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts @@ -48,6 +48,29 @@ describe("calculateNL2026StikstofGebruiksNorm", () => { expect(result.normSource).toEqual("Grasland (volledig maaien).") }) + it("should return 0 for buffer strips", async () => { + const mockInput: NL2026NormsInput = { + farm: { has_grazing_intention: false }, + field: { + b_id: "1", + b_centroid: [5.6279889, 51.975571], + b_buffer: true, + } as Field, + cultivations: [ + { + b_lu_catalogue: "nl_265", + 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) + expect(result.normValue).toBe(0) + expect(result.normSource).toEqual("Bufferstrook: geen plaatsingsruimte") + }) + it("should return the correct norm for potatoes", async () => { const mockInput: NL2026NormsInput = { farm: { has_grazing_intention: false }, diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts index 27e420cf3..a7a421c66 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -650,6 +650,14 @@ export async function calculateNL2026StikstofGebruiksNorm( const field = input.field const cultivations = input.cultivations + // Check for buffer strip + if (field.b_buffer) { + return { + normValue: 0, + normSource: "Bufferstrook: geen plaatsingsruimte", + } + } + // Determine hoofdteelt const b_lu_catalogue = determineNLHoofdteelt(cultivations, 2026) let cultivation = cultivations.find( diff --git a/fdm-calculator/src/norms/nl/2026/value/types.d.ts b/fdm-calculator/src/norms/nl/2026/value/types.d.ts index 290972cf9..a8bea2f21 100644 --- a/fdm-calculator/src/norms/nl/2026/value/types.d.ts +++ b/fdm-calculator/src/norms/nl/2026/value/types.d.ts @@ -17,7 +17,7 @@ export type NL2026NormsInput = { has_grazing_intention: boolean } /** The field record from fdm-core, including its ID and centroid for location-based checks. */ - field: Pick + 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. */ diff --git a/fdm-calculator/src/nutrient-advice/index.test.ts b/fdm-calculator/src/nutrient-advice/index.test.ts index 91750fe33..18c0e3c6f 100644 --- a/fdm-calculator/src/nutrient-advice/index.test.ts +++ b/fdm-calculator/src/nutrient-advice/index.test.ts @@ -155,4 +155,21 @@ describe("requestNutrientAdvice", () => { ) expect(fetch).toHaveBeenCalledTimes(1) }) + + it("should return zero advice for buffer strips without calling NMI API", async () => { + const inputs: NutrientAdviceInputs = { + b_lu_catalogue: "nl_2014", + b_centroid: [4.3, 52.4], + currentSoilData: mockCurrentSoilData, + nmiApiKey: "mock-api-key", + b_buffer: true, + } + + const result = await requestNutrientAdvice(inputs) + + expect(fetch).not.toHaveBeenCalled() + expect(result.d_n_req).toBe(0) + expect(result.d_p_req).toBe(0) + expect(result.d_k_req).toBe(0) + }) }) diff --git a/fdm-calculator/src/nutrient-advice/index.ts b/fdm-calculator/src/nutrient-advice/index.ts index 5e46c5815..b8708a584 100644 --- a/fdm-calculator/src/nutrient-advice/index.ts +++ b/fdm-calculator/src/nutrient-advice/index.ts @@ -21,8 +21,31 @@ export async function requestNutrientAdvice({ b_centroid, currentSoilData, nmiApiKey, + b_buffer, }: NutrientAdviceInputs): Promise { try { + if (b_buffer) { + return { + d_n_req: 0, + d_n_norm: 0, + d_n_norm_man: 0, + d_p_norm: 0, + d_p_req: 0, + d_k_req: 0, + d_c_req: 0, + d_ca_req: 0, + d_s_req: 0, + d_mg_req: 0, + d_cu_req: 0, + d_zn_req: 0, + d_co_req: 0, + d_mn_req: 0, + d_mo_req: 0, + d_na_req: 0, + d_b_req: 0, + } + } + if (!nmiApiKey) { throw new Error("NMI API key not provided") } diff --git a/fdm-calculator/src/nutrient-advice/types.d.ts b/fdm-calculator/src/nutrient-advice/types.d.ts index b73d7e9d8..bc6ceb87a 100644 --- a/fdm-calculator/src/nutrient-advice/types.d.ts +++ b/fdm-calculator/src/nutrient-advice/types.d.ts @@ -84,4 +84,6 @@ export type NutrientAdviceInputs = { currentSoilData: CurrentSoilData /** NMI API key for authentication */ nmiApiKey: string | undefined + /** Indicates if the field is a buffer strip */ + b_buffer?: boolean } From 39cca64a4092cb35da85d5c1308147511376f324 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:59:16 +0100 Subject: [PATCH 03/28] refactor: rename b_buffer to b_bufferstrip --- .changeset/huge-cars-peel.md | 2 +- .../src/balance/nitrogen/index.test.ts | 20 +++++++--- fdm-calculator/src/balance/nitrogen/index.ts | 8 ++-- .../src/balance/nitrogen/types.d.ts | 4 +- .../src/balance/organic-matter/index.test.ts | 6 +-- .../src/balance/organic-matter/index.ts | 8 ++-- .../src/balance/organic-matter/types.ts | 4 +- .../value/dierlijke-mest-gebruiksnorm.test.ts | 2 +- .../2025/value/dierlijke-mest-gebruiksnorm.ts | 2 +- .../nl/2025/value/fosfaatgebruiksnorm.test.ts | 2 +- .../nl/2025/value/fosfaatgebruiksnorm.ts | 2 +- .../2025/value/stikstofgebruiksnorm.test.ts | 5 ++- .../nl/2025/value/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2025/value/types.d.ts | 2 +- .../value/dierlijke-mest-gebruiksnorm.test.ts | 4 +- .../2026/value/dierlijke-mest-gebruiksnorm.ts | 2 +- .../nl/2026/value/fosfaatgebruiksnorm.test.ts | 2 +- .../nl/2026/value/fosfaatgebruiksnorm.ts | 2 +- .../2026/value/stikstofgebruiksnorm.test.ts | 2 +- .../nl/2026/value/stikstofgebruiksnorm.ts | 2 +- .../src/norms/nl/2026/value/types.d.ts | 2 +- .../src/nutrient-advice/index.test.ts | 2 +- fdm-calculator/src/nutrient-advice/index.ts | 4 +- fdm-calculator/src/nutrient-advice/types.d.ts | 2 +- fdm-core/src/cultivation.d.ts | 2 +- fdm-core/src/cultivation.ts | 4 +- .../src/db/migrations/0021_exotic_mesmero.sql | 2 +- .../src/db/migrations/meta/0021_snapshot.json | 4 +- fdm-core/src/db/schema.ts | 2 +- fdm-core/src/field.d.ts | 2 +- fdm-core/src/field.test.ts | 40 ++++++------------- fdm-core/src/field.ts | 22 +++++----- 32 files changed, 82 insertions(+), 89 deletions(-) diff --git a/.changeset/huge-cars-peel.md b/.changeset/huge-cars-peel.md index b90317124..90ad27e7d 100644 --- a/.changeset/huge-cars-peel.md +++ b/.changeset/huge-cars-peel.md @@ -2,4 +2,4 @@ "@svenvw/fdm-core": minor --- -Replace at field the derived parameter `b_isproductive` with the stored parameter `b_buffer` to allow users setting the value explicitly +Replace at field the derived parameter `b_isproductive` with the stored parameter `b_bufferstrip` to allow users setting the value explicitly diff --git a/fdm-calculator/src/balance/nitrogen/index.test.ts b/fdm-calculator/src/balance/nitrogen/index.test.ts index 873b004fa..387fe3ccd 100644 --- a/fdm-calculator/src/balance/nitrogen/index.test.ts +++ b/fdm-calculator/src/balance/nitrogen/index.test.ts @@ -1,6 +1,12 @@ import { describe, expect, it, vi } from "vitest" -import { calculateNitrogenBalance, calculateNitrogenBalancesFieldToFarm } from "." -import type { NitrogenBalanceInput, NitrogenBalanceFieldResultNumeric } from "./types" +import { + calculateNitrogenBalance, + calculateNitrogenBalancesFieldToFarm, +} from "." +import type { + NitrogenBalanceInput, + NitrogenBalanceFieldResultNumeric, +} from "./types" import type { FdmType } from "@svenvw/fdm-core" import Decimal from "decimal.js" @@ -10,7 +16,9 @@ const mockFdm = { from: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), - then: vi.fn((resolve) => resolve ? Promise.resolve(resolve([])) : Promise.resolve([])), // Simulate cache miss + then: vi.fn((resolve) => + resolve ? Promise.resolve(resolve([])) : Promise.resolve([]), + ), // Simulate cache miss insert: vi.fn().mockReturnThis(), values: vi.fn().mockResolvedValue(undefined), } as unknown as FdmType @@ -209,7 +217,7 @@ describe("calculateNitrogenBalance", () => { b_area: 100, b_start: new Date("2023-01-01"), b_end: new Date("2023-12-31"), - b_buffer: true, + b_bufferstrip: true, }, cultivations: [], harvests: [], @@ -244,7 +252,7 @@ describe("calculateNitrogenBalance", () => { { b_id: "field1", b_area: 10, - b_buffer: false, + b_bufferstrip: false, balance: { balance: 100, supply: { @@ -286,7 +294,7 @@ describe("calculateNitrogenBalance", () => { { b_id: "buffer1", b_area: 100, - b_buffer: true, + b_bufferstrip: true, balance: { balance: 0, supply: { diff --git a/fdm-calculator/src/balance/nitrogen/index.ts b/fdm-calculator/src/balance/nitrogen/index.ts index c9e2572a9..d379a7836 100644 --- a/fdm-calculator/src/balance/nitrogen/index.ts +++ b/fdm-calculator/src/balance/nitrogen/index.ts @@ -53,14 +53,14 @@ export async function calculateNitrogenBalance( return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, - b_buffer: fieldInput.field.b_buffer ?? false, + b_bufferstrip: fieldInput.field.b_bufferstrip ?? false, balance, } } catch (error) { return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, - b_buffer: fieldInput.field.b_buffer ?? false, + b_bufferstrip: fieldInput.field.b_bufferstrip ?? false, errorMessage: error instanceof Error ? error.message @@ -123,7 +123,7 @@ export function calculateNitrogenBalanceField( throw new Error("Timeframe start and end dates must be provided.") } - if (field.b_buffer) { + if (field.b_bufferstrip) { return { b_id: field.b_id, balance: 0, @@ -307,7 +307,7 @@ export function calculateNitrogenBalancesFieldToFarm( // Filter out fields that have errors for aggregation // Also filter out buffer strips as they should be ignored in the farm-level aggregation const successfulFieldBalances = fieldsWithBalanceResults.filter( - (result) => result.balance !== undefined && !result.b_buffer, + (result) => result.balance !== undefined && !result.b_bufferstrip, ) as (NitrogenBalanceFieldResultNumeric & { balance: NitrogenBalanceFieldNumeric })[] diff --git a/fdm-calculator/src/balance/nitrogen/types.d.ts b/fdm-calculator/src/balance/nitrogen/types.d.ts index b153fd9a5..9ee81c845 100644 --- a/fdm-calculator/src/balance/nitrogen/types.d.ts +++ b/fdm-calculator/src/balance/nitrogen/types.d.ts @@ -471,7 +471,7 @@ export type SoilAnalysisPicked = Pick< export type FieldInput = { field: Pick< Field, - "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_buffer" + "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_bufferstrip" > cultivations: Pick< Cultivation, @@ -673,7 +673,7 @@ export type NitrogenBalanceFieldNumeric = { export type NitrogenBalanceFieldResultNumeric = { b_id: string b_area: number - b_buffer: boolean + b_bufferstrip: boolean balance?: NitrogenBalanceFieldNumeric errorMessage?: string } diff --git a/fdm-calculator/src/balance/organic-matter/index.test.ts b/fdm-calculator/src/balance/organic-matter/index.test.ts index c2114695d..00762b87c 100644 --- a/fdm-calculator/src/balance/organic-matter/index.test.ts +++ b/fdm-calculator/src/balance/organic-matter/index.test.ts @@ -65,7 +65,7 @@ describe("Organic Matter Balance Calculation", () => { it("should return zero balance for buffer strips", () => { const result = calculateOrganicMatterBalanceField({ fieldInput: { - field: { ...mockField, b_buffer: true }, + field: { ...mockField, b_bufferstrip: true }, cultivations: mockCultivations, fertilizerApplications: mockFertilizerApplications, soilAnalyses: mockSoilAnalyses, @@ -149,7 +149,7 @@ describe("Organic Matter Balance Calculation", () => { { b_id: "field1", b_area: 10, - b_buffer: false, + b_bufferstrip: false, balance: { supply: { total: 500 }, degradation: { total: -200 }, @@ -159,7 +159,7 @@ describe("Organic Matter Balance Calculation", () => { { b_id: "buffer1", b_area: 100, - b_buffer: true, + b_bufferstrip: true, balance: { supply: { total: 0 }, degradation: { total: 0 }, diff --git a/fdm-calculator/src/balance/organic-matter/index.ts b/fdm-calculator/src/balance/organic-matter/index.ts index 0a704c0bf..7d1c0abf7 100644 --- a/fdm-calculator/src/balance/organic-matter/index.ts +++ b/fdm-calculator/src/balance/organic-matter/index.ts @@ -59,14 +59,14 @@ export async function calculateOrganicMatterBalance( return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, - b_buffer: fieldInput.field.b_buffer ?? false, + b_bufferstrip: fieldInput.field.b_bufferstrip ?? false, balance, } } catch (error) { return { b_id: fieldInput.field.b_id, b_area: fieldInput.field.b_area ?? 0, - b_buffer: fieldInput.field.b_buffer ?? false, + b_bufferstrip: fieldInput.field.b_bufferstrip ?? false, errorMessage: error instanceof Error ? error.message @@ -113,7 +113,7 @@ export function calculateOrganicMatterBalanceField( const { field, cultivations, fertilizerApplications, soilAnalyses } = fieldInput - if (field.b_buffer) { + if (field.b_bufferstrip) { return { b_id: field.b_id, balance: 0, @@ -217,7 +217,7 @@ export function calculateOrganicMatterBalancesFieldToFarm( // Filter out fields that have errors to ensure they are not included in the aggregation. // Also filter out buffer strips as they should be ignored in the farm-level aggregation const successfulFieldBalances = fieldsWithBalanceResults.filter( - (result) => result.balance !== undefined && !result.b_buffer, + (result) => result.balance !== undefined && !result.b_bufferstrip, ) as (OrganicMatterBalanceFieldResultNumeric & { balance: OrganicMatterBalanceFieldNumeric })[] diff --git a/fdm-calculator/src/balance/organic-matter/types.ts b/fdm-calculator/src/balance/organic-matter/types.ts index 022592a2a..075cb4f44 100644 --- a/fdm-calculator/src/balance/organic-matter/types.ts +++ b/fdm-calculator/src/balance/organic-matter/types.ts @@ -195,7 +195,7 @@ export type FieldInput = { /** The core details of the field. */ field: Pick< Field, - "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_buffer" + "b_id" | "b_centroid" | "b_area" | "b_start" | "b_end" | "b_bufferstrip" > /** The list of cultivations that took place on the field. */ cultivations: Pick< @@ -336,7 +336,7 @@ export type OrganicMatterBalanceFieldResultNumeric = { /** The area of the field in hectares. */ b_area: number /** Whether the field is a buffer strip */ - b_buffer: boolean + b_bufferstrip: boolean /** The detailed organic matter balance for the field. Undefined if an error occurred. */ balance?: OrganicMatterBalanceFieldNumeric /** An error message if the calculation for this field failed. */ diff --git a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts index 0c5cba91f..037f336ad 100644 --- a/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/dierlijke-mest-gebruiksnorm.test.ts @@ -119,7 +119,7 @@ describe("calculateNL2025DierlijkeMestGebruiksNorm", () => { field: { b_id: "1", b_centroid: [5.641351453912945, 51.97755938887036], - b_buffer: true, + b_bufferstrip: true, }, cultivations: [], soilAnalysis: { a_p_cc: 0, a_p_al: 0 }, 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 df97b9f60..f8625f980 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 @@ -152,7 +152,7 @@ export async function calculateNL2025DierlijkeMestGebruiksNorm( const field = input.field // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts index 1fe5cacc8..6d84e9869 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.test.ts @@ -47,7 +47,7 @@ describe("calculateNL2025FosfaatGebruiksNorm", () => { field: { b_id: "1", b_centroid: [5.0, 52.0], - b_buffer: true, + b_bufferstrip: true, }, cultivations: [ { diff --git a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts index 1ef3f89e6..f02400be7 100644 --- a/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/fosfaatgebruiksnorm.ts @@ -135,7 +135,7 @@ export async function calculateNL2025FosfaatGebruiksNorm( ): Promise { const field = input.field // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts index f40ff2525..fa530dbcf 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.test.ts @@ -9,7 +9,8 @@ import { import type { NL2025NormsInput, NL2025NormsInputForCultivation } from "./types" vi.mock("../../../../shared/geotiff", async (importActual) => { - const actual = await importActual() + const actual = + await importActual() return { ...actual, getGeoTiffValue: vi.fn(actual.getGeoTiffValue), @@ -96,7 +97,7 @@ describe(" calculateNL2025StikstofGebruiksNorm", () => { field: { b_id: "1", b_centroid: [5.6279889, 51.975571], - b_buffer: true, + b_bufferstrip: true, } as Field, cultivations: [ { diff --git a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts index 0c913b637..ff1bbcfdd 100644 --- a/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2025/value/stikstofgebruiksnorm.ts @@ -815,7 +815,7 @@ export async function calculateNL2025StikstofGebruiksNorm( const cultivations = input.cultivations // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", 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 61ba94a41..1d4ee0d78 100644 --- a/fdm-calculator/src/norms/nl/2025/value/types.d.ts +++ b/fdm-calculator/src/norms/nl/2025/value/types.d.ts @@ -18,7 +18,7 @@ export type NL2025NormsInput = { has_grazing_intention: boolean } /** The field record from fdm-core, including its ID and centroid for location-based checks. */ - field: Pick + field: Pick /** An array of all cultivations on the field with their required norm inputs. */ cultivations: NL2025NormsInputForCultivation[] /** The most recent soil analysis data available before the start of the cultivation. */ 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 index a92f827d8..8190bc5cd 100644 --- 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 @@ -6,7 +6,7 @@ describe("calculateNL2026DierlijkeMestGebruiksNorm", () => { it("should return the default norm value", async () => { const input = { field: { - b_buffer: false, + b_bufferstrip: false, }, } as NL2026NormsInput const result = await calculateNL2026DierlijkeMestGebruiksNorm(input) @@ -17,7 +17,7 @@ describe("calculateNL2026DierlijkeMestGebruiksNorm", () => { it("should return 0 for buffer strips", async () => { const input = { field: { - b_buffer: true, + b_bufferstrip: true, }, } as NL2026NormsInput const result = await calculateNL2026DierlijkeMestGebruiksNorm(input) 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 4e21718f8..a4208f438 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 @@ -25,7 +25,7 @@ export async function calculateNL2026DierlijkeMestGebruiksNorm( const field = input.field // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts index dbd4bbe26..a18aa03a1 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.test.ts @@ -47,7 +47,7 @@ describe("calculateNL2026FosfaatGebruiksNorm", () => { field: { b_id: "1", b_centroid: [5.0, 52.0], - b_buffer: true, + b_bufferstrip: true, }, cultivations: [ { diff --git a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts index 5d5d9f7cd..6fb600403 100644 --- a/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/fosfaatgebruiksnorm.ts @@ -125,7 +125,7 @@ export async function calculateNL2026FosfaatGebruiksNorm( ): Promise { const field = input.field // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", 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 5ec8bc371..ad159f7ee 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.test.ts @@ -54,7 +54,7 @@ describe("calculateNL2026StikstofGebruiksNorm", () => { field: { b_id: "1", b_centroid: [5.6279889, 51.975571], - b_buffer: true, + b_bufferstrip: true, } as Field, cultivations: [ { diff --git a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts index a7a421c66..dd3b76f33 100644 --- a/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts +++ b/fdm-calculator/src/norms/nl/2026/value/stikstofgebruiksnorm.ts @@ -651,7 +651,7 @@ export async function calculateNL2026StikstofGebruiksNorm( const cultivations = input.cultivations // Check for buffer strip - if (field.b_buffer) { + if (field.b_bufferstrip) { return { normValue: 0, normSource: "Bufferstrook: geen plaatsingsruimte", diff --git a/fdm-calculator/src/norms/nl/2026/value/types.d.ts b/fdm-calculator/src/norms/nl/2026/value/types.d.ts index a8bea2f21..3de218920 100644 --- a/fdm-calculator/src/norms/nl/2026/value/types.d.ts +++ b/fdm-calculator/src/norms/nl/2026/value/types.d.ts @@ -17,7 +17,7 @@ export type NL2026NormsInput = { has_grazing_intention: boolean } /** The field record from fdm-core, including its ID and centroid for location-based checks. */ - field: Pick + 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. */ diff --git a/fdm-calculator/src/nutrient-advice/index.test.ts b/fdm-calculator/src/nutrient-advice/index.test.ts index 18c0e3c6f..4e4925a6c 100644 --- a/fdm-calculator/src/nutrient-advice/index.test.ts +++ b/fdm-calculator/src/nutrient-advice/index.test.ts @@ -162,7 +162,7 @@ describe("requestNutrientAdvice", () => { b_centroid: [4.3, 52.4], currentSoilData: mockCurrentSoilData, nmiApiKey: "mock-api-key", - b_buffer: true, + b_bufferstrip: true, } const result = await requestNutrientAdvice(inputs) diff --git a/fdm-calculator/src/nutrient-advice/index.ts b/fdm-calculator/src/nutrient-advice/index.ts index b8708a584..b44f9f373 100644 --- a/fdm-calculator/src/nutrient-advice/index.ts +++ b/fdm-calculator/src/nutrient-advice/index.ts @@ -21,10 +21,10 @@ export async function requestNutrientAdvice({ b_centroid, currentSoilData, nmiApiKey, - b_buffer, + b_bufferstrip, }: NutrientAdviceInputs): Promise { try { - if (b_buffer) { + if (b_bufferstrip) { return { d_n_req: 0, d_n_norm: 0, diff --git a/fdm-calculator/src/nutrient-advice/types.d.ts b/fdm-calculator/src/nutrient-advice/types.d.ts index bc6ceb87a..cc426728f 100644 --- a/fdm-calculator/src/nutrient-advice/types.d.ts +++ b/fdm-calculator/src/nutrient-advice/types.d.ts @@ -85,5 +85,5 @@ export type NutrientAdviceInputs = { /** NMI API key for authentication */ nmiApiKey: string | undefined /** Indicates if the field is a buffer strip */ - b_buffer?: boolean + b_bufferstrip?: boolean } diff --git a/fdm-core/src/cultivation.d.ts b/fdm-core/src/cultivation.d.ts index 6b2a8fb0f..80a5dce68 100644 --- a/fdm-core/src/cultivation.d.ts +++ b/fdm-core/src/cultivation.d.ts @@ -38,7 +38,7 @@ export interface CultivationPlan { b_id: schema.fieldsTypeSelect["b_id"] b_area: number b_name: schema.fieldsTypeSelect["b_name"] - b_buffer: boolean + b_bufferstrip: boolean fertilizer_applications: Array<{ p_id_catalogue: schema.fertilizersCatalogueTypeSelect["p_id_catalogue"] p_name_nl: schema.fertilizersCatalogueTypeSelect["p_name_nl"] diff --git a/fdm-core/src/cultivation.ts b/fdm-core/src/cultivation.ts index 994a2dd51..d6d0cfb1b 100644 --- a/fdm-core/src/cultivation.ts +++ b/fdm-core/src/cultivation.ts @@ -785,7 +785,7 @@ export async function getCultivationPlan( b_name: schema.fields.b_name, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, b_perimeter: sql`ROUND((ST_Length(ST_ExteriorRing(b_geometry)::geography))::NUMERIC, 2)::FLOAT`, - b_buffer: schema.fields.b_buffer, + b_bufferstrip: schema.fields.b_bufferstrip, b_lu_start: schema.cultivationStarting.b_lu_start, b_lu_end: schema.cultivationEnding.b_lu_end, m_cropresidue: schema.cultivationEnding.m_cropresidue, @@ -957,7 +957,7 @@ export async function getCultivationPlan( b_id: curr.b_id, b_area: curr.b_area, b_name: curr.b_name, - b_buffer: curr.b_buffer, + b_bufferstrip: curr.b_bufferstrip, fertilizer_applications: [], harvests: [], } diff --git a/fdm-core/src/db/migrations/0021_exotic_mesmero.sql b/fdm-core/src/db/migrations/0021_exotic_mesmero.sql index b668c30a9..b580de7fb 100644 --- a/fdm-core/src/db/migrations/0021_exotic_mesmero.sql +++ b/fdm-core/src/db/migrations/0021_exotic_mesmero.sql @@ -1 +1 @@ -ALTER TABLE "fdm"."fields" ADD COLUMN "b_buffer" boolean DEFAULT false NOT NULL; \ No newline at end of file +ALTER TABLE "fdm"."fields" ADD COLUMN "b_bufferstrip" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/fdm-core/src/db/migrations/meta/0021_snapshot.json b/fdm-core/src/db/migrations/meta/0021_snapshot.json index e6c048546..aac2827eb 100644 --- a/fdm-core/src/db/migrations/meta/0021_snapshot.json +++ b/fdm-core/src/db/migrations/meta/0021_snapshot.json @@ -1549,8 +1549,8 @@ "primaryKey": false, "notNull": false }, - "b_buffer": { - "name": "b_buffer", + "b_bufferstrip": { + "name": "b_bufferstrip", "type": "boolean", "primaryKey": false, "notNull": true, diff --git a/fdm-core/src/db/schema.ts b/fdm-core/src/db/schema.ts index 210b4e77c..016c9ce8d 100644 --- a/fdm-core/src/db/schema.ts +++ b/fdm-core/src/db/schema.ts @@ -98,7 +98,7 @@ export const fields = fdmSchema.table( type: "Polygon", }), // PGLite does not support PostGIS yet; I expect to be supported in Q4 2024: https://github.com/electric-sql/pglite/issues/11 b_id_source: text(), - b_buffer: boolean().notNull().default(false), + b_bufferstrip: boolean().notNull().default(false), created: timestamp({ withTimezone: true }).notNull().defaultNow(), updated: timestamp({ withTimezone: true }), }, diff --git a/fdm-core/src/field.d.ts b/fdm-core/src/field.d.ts index 9e394c97c..b0207667c 100644 --- a/fdm-core/src/field.d.ts +++ b/fdm-core/src/field.d.ts @@ -12,5 +12,5 @@ export interface Field { b_start: schema.fieldAcquiringTypeSelect["b_start"] b_end: schema.fieldDiscardingTypeSelect["b_end"] b_acquiring_method: schema.fieldAcquiringTypeSelect["b_acquiring_method"] - b_buffer: boolean + b_bufferstrip: boolean } diff --git a/fdm-core/src/field.test.ts b/fdm-core/src/field.test.ts index f271cab95..75f5bbac9 100644 --- a/fdm-core/src/field.test.ts +++ b/fdm-core/src/field.test.ts @@ -106,7 +106,7 @@ describe("Farm Data Model", () => { expect(field.b_centroid[1]).toBeTypeOf("number") expect(field.b_area).toBeGreaterThan(0) expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) expect(field.b_start).toEqual(AcquireDate) expect(field.b_end).toEqual(discardingDate) expect(field.b_acquiring_method).toBe(AcquiringMethod) @@ -238,7 +238,7 @@ describe("Farm Data Model", () => { ) for (const field of fields) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) } }) @@ -379,7 +379,7 @@ describe("Farm Data Model", () => { expect(fields1.length).toBe(1) expect(fields1[0].b_name).toBe(field2Name) expect(fields1[0].b_perimeter).toBeGreaterThan(0) - expect(fields1[0].b_buffer).toBe(false) + expect(fields1[0].b_bufferstrip).toBe(false) // Test with a timeframe that includes both Field 1 and Field 2 const timeframe2 = { @@ -398,7 +398,7 @@ describe("Farm Data Model", () => { ) for (const field of fields2) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) } // Test with a timeframe that includes field 2 and field 3 @@ -419,7 +419,7 @@ describe("Farm Data Model", () => { ) for (const field of fields3) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) } //Test with only start date const fields4 = await getFields(fdm, principal_id, b_id_farm, { @@ -437,7 +437,7 @@ describe("Farm Data Model", () => { ) for (const field of fields4) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) } //Test with only end date const fields5 = await getFields(fdm, principal_id, b_id_farm, { @@ -450,7 +450,7 @@ describe("Farm Data Model", () => { ) for (const field of fields5) { expect(field.b_perimeter).toBeGreaterThan(0) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) } }) }) @@ -721,7 +721,7 @@ describe("Farm Data Model", () => { expect(field.b_name).toBe(fieldName) expect(field.b_perimeter).toBeGreaterThan(0) expect(field.b_perimeter).toBeGreaterThan(4000000) - expect(field.b_buffer).toBe(false) + expect(field.b_bufferstrip).toBe(false) }) }) @@ -962,28 +962,16 @@ describe("Farm Data Model", () => { describe("determineIfFieldIsBuffer", () => { it("should determine if a field is buffer by checking name", async () => { - const isBuffer = determineIfFieldIsBuffer( - 1.0, - 100.0, - "Bufferstrip", - ) + const isBuffer = determineIfFieldIsBuffer(1.0, 100.0, "Bufferstrip") expect(isBuffer).toBe(true) }) it("should determine if a field is buffer by checking shape", async () => { - const isBuffer = determineIfFieldIsBuffer( - 1.0, - 10000.0, - "Field", - ) + const isBuffer = determineIfFieldIsBuffer(1.0, 10000.0, "Field") expect(isBuffer).toBe(true) }) it("should determine if a field is productive (not buffer) by checking shape (area is large enough)", async () => { - const isBuffer = determineIfFieldIsBuffer( - 2.5, - 10000.0, - "Field", - ) + const isBuffer = determineIfFieldIsBuffer(2.5, 10000.0, "Field") expect(isBuffer).toBe(false) }) it("should determine if a field is buffer by checking shape and name", async () => { @@ -995,11 +983,7 @@ describe("Farm Data Model", () => { expect(isBuffer).toBe(true) }) it("should determine if a field is buffer by checking shape and name (productive)", async () => { - const isBuffer = determineIfFieldIsBuffer( - 10.0, - 100.0, - "Field", - ) + const isBuffer = determineIfFieldIsBuffer(10.0, 100.0, "Field") expect(isBuffer).toBe(false) }) }) diff --git a/fdm-core/src/field.ts b/fdm-core/src/field.ts index 33318965c..c63845294 100644 --- a/fdm-core/src/field.ts +++ b/fdm-core/src/field.ts @@ -53,7 +53,7 @@ export async function addField( b_start: schema.fieldAcquiringTypeInsert["b_start"], b_acquiring_method: schema.fieldAcquiringTypeInsert["b_acquiring_method"], b_end?: schema.fieldDiscardingTypeInsert["b_end"], - b_buffer?: schema.fieldsTypeInsert["b_buffer"], + b_bufferstrip?: schema.fieldsTypeInsert["b_bufferstrip"], ): Promise { try { await checkPermission( @@ -75,7 +75,7 @@ export async function addField( b_name: b_name, b_id_source: b_id_source, b_geometry: b_geometry, - b_buffer: b_buffer ?? false, + b_bufferstrip: b_bufferstrip ?? false, } await tx.insert(schema.fields).values(fieldData) @@ -114,7 +114,7 @@ export async function addField( await tx.insert(schema.fieldDiscarding).values(fieldDiscardingData) // If buffer status is not provided try to determine - if (b_buffer === undefined) { + if (b_bufferstrip === undefined) { const field = await tx .select({ b_id: schema.fields.b_id, @@ -136,7 +136,7 @@ export async function addField( await tx .update(schema.fields) - .set({ b_buffer: isBuffer }) + .set({ b_bufferstrip: isBuffer }) .where(eq(schema.fields.b_id, b_id)) } } @@ -152,7 +152,7 @@ export async function addField( b_start, b_acquiring_method, b_end, - b_buffer, + b_bufferstrip, }) } } @@ -194,7 +194,7 @@ export async function getField( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, - b_buffer: schema.fields.b_buffer, + b_bufferstrip: schema.fields.b_bufferstrip, b_centroid_x: sql`ST_X(ST_Centroid(b_geometry))`, b_centroid_y: sql`ST_Y(ST_Centroid(b_geometry))`, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, @@ -303,7 +303,7 @@ export async function getFields( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, - b_buffer: schema.fields.b_buffer, + b_bufferstrip: schema.fields.b_bufferstrip, b_centroid_x: sql`ST_X(ST_Centroid(b_geometry))`, b_centroid_y: sql`ST_Y(ST_Centroid(b_geometry))`, b_area: sql`ROUND((ST_Area(b_geometry::geography)/10000)::NUMERIC, 2)::FLOAT`, @@ -367,7 +367,7 @@ export async function updateField( b_start?: schema.fieldAcquiringTypeInsert["b_start"], b_acquiring_method?: schema.fieldAcquiringTypeInsert["b_acquiring_method"], b_end?: schema.fieldDiscardingTypeInsert["b_end"], - b_buffer?: schema.fieldsTypeInsert["b_buffer"], + b_bufferstrip?: schema.fieldsTypeInsert["b_bufferstrip"], ): Promise { return await fdm.transaction(async (tx: FdmType) => { try { @@ -392,8 +392,8 @@ export async function updateField( if (b_geometry !== undefined) { setFields.b_geometry = b_geometry } - if (b_buffer !== undefined) { - setFields.b_buffer = b_buffer + if (b_bufferstrip !== undefined) { + setFields.b_bufferstrip = b_bufferstrip } setFields.updated = updated @@ -436,7 +436,7 @@ export async function updateField( b_id_farm: schema.fieldAcquiring.b_id_farm, b_id_source: schema.fields.b_id_source, b_geometry: schema.fields.b_geometry, - b_buffer: schema.fields.b_buffer, + b_bufferstrip: schema.fields.b_bufferstrip, b_start: schema.fieldAcquiring.b_start, b_acquiring_method: schema.fieldAcquiring.b_acquiring_method, From 3bc1020865279ad16839a310b0d77a8556aa5ae0 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:28:49 +0100 Subject: [PATCH 04/28] refactor: remove not used code --- .../src/balance/organic-matter/index.test.ts | 33 ------------------- .../src/balance/shared/conversion.ts | 33 ------------------- 2 files changed, 66 deletions(-) diff --git a/fdm-calculator/src/balance/organic-matter/index.test.ts b/fdm-calculator/src/balance/organic-matter/index.test.ts index 00762b87c..e472219ce 100644 --- a/fdm-calculator/src/balance/organic-matter/index.test.ts +++ b/fdm-calculator/src/balance/organic-matter/index.test.ts @@ -1,6 +1,5 @@ import Decimal from "decimal.js" import { describe, expect, it, vi } from "vitest" -import { convertOrganicMatterBalanceToNumeric } from "../shared/conversion" import * as shared from "../shared/soil" import * as degradation from "./degradation" import { @@ -180,36 +179,4 @@ describe("Organic Matter Balance Calculation", () => { expect(farmBalance.balance).toBe(300) }) }) - - describe("convertOrganicMatterBalanceToNumeric", () => { - it("should convert all Decimal types to numbers", () => { - const farmBalanceDecimal = { - balance: new Decimal(233.333), - supply: new Decimal(466.666), - degradation: new Decimal(-233.333), - fields: [ - { - b_id: "field1", - b_area: 10, - balance: { - balance: new Decimal(300), - supply: {}, - degradation: {}, - }, - }, - ], - hasErrors: false, - fieldErrorMessages: [], - } as any - - const numericResult = - convertOrganicMatterBalanceToNumeric(farmBalanceDecimal) - - expect(typeof numericResult.balance).toBe("number") - expect(typeof numericResult.supply).toBe("number") - expect(typeof numericResult.degradation).toBe("number") - expect(typeof numericResult.fields[0].balance).toBe("object") - expect(numericResult.balance).toBe(233) // .round() - }) - }) }) diff --git a/fdm-calculator/src/balance/shared/conversion.ts b/fdm-calculator/src/balance/shared/conversion.ts index 9de35276a..2ae864f77 100644 --- a/fdm-calculator/src/balance/shared/conversion.ts +++ b/fdm-calculator/src/balance/shared/conversion.ts @@ -1,9 +1,4 @@ import Decimal from "decimal.js" -import type { - OrganicMatterBalance, - OrganicMatterBalanceFieldNumeric, - OrganicMatterBalanceNumeric, -} from "../organic-matter/types" // Helper function to convert Decimal to number recursively export function convertDecimalToNumberRecursive(data: unknown): unknown { @@ -33,31 +28,3 @@ export function convertDecimalToNumberRecursive(data: unknown): unknown { } return data } - -// Main conversion function for OrganicMatterBalance -export function convertOrganicMatterBalanceToNumeric( - balance: OrganicMatterBalance, -): OrganicMatterBalanceNumeric { - const numericBalance = convertDecimalToNumberRecursive( - balance, - ) as OrganicMatterBalanceNumeric - - numericBalance.fields = balance.fields.map((fieldResult) => { - if (fieldResult.balance) { - return { - b_id: fieldResult.b_id, - b_area: fieldResult.b_area, - balance: convertDecimalToNumberRecursive( - fieldResult.balance, - ) as OrganicMatterBalanceFieldNumeric, - } - } - return { - b_id: fieldResult.b_id, - b_area: fieldResult.b_area, - errorMessage: fieldResult.errorMessage, - } - }) - - return numericBalance -} From bcd3a3289c9a13ffc36ea108e502661496164bf7 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:45:43 +0100 Subject: [PATCH 05/28] feat: Set for existing fields the b_bufferstrip bases on the same logic as in determineIfFieldIsBuffer --- .changeset/loud-yaks-repeat.md | 5 + .../0022_calculate_bufferstrips.sql | 9 + .../src/db/migrations/meta/0022_snapshot.json | 3623 +++++++++++++++++ fdm-core/src/db/migrations/meta/_journal.json | 7 + 4 files changed, 3644 insertions(+) create mode 100644 .changeset/loud-yaks-repeat.md create mode 100644 fdm-core/src/db/migrations/0022_calculate_bufferstrips.sql create mode 100644 fdm-core/src/db/migrations/meta/0022_snapshot.json diff --git a/.changeset/loud-yaks-repeat.md b/.changeset/loud-yaks-repeat.md new file mode 100644 index 000000000..dce162c05 --- /dev/null +++ b/.changeset/loud-yaks-repeat.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-core": minor +--- + +Set for existing fields the b_bufferstrip bases on the same logic as in determineIfFieldIsBuffer diff --git a/fdm-core/src/db/migrations/0022_calculate_bufferstrips.sql b/fdm-core/src/db/migrations/0022_calculate_bufferstrips.sql new file mode 100644 index 000000000..048f15188 --- /dev/null +++ b/fdm-core/src/db/migrations/0022_calculate_bufferstrips.sql @@ -0,0 +1,9 @@ +UPDATE "fdm"."fields" +SET "b_bufferstrip" = COALESCE( + ( + ST_Length(ST_ExteriorRing(b_geometry)::geography) / NULLIF(SQRT(ST_Area(b_geometry::geography)), 0) >= 20 + AND ST_Area(b_geometry::geography) < 25000 + ), + FALSE +) +OR b_name ILIKE '%buffer%'; diff --git a/fdm-core/src/db/migrations/meta/0022_snapshot.json b/fdm-core/src/db/migrations/meta/0022_snapshot.json new file mode 100644 index 000000000..aac2827eb --- /dev/null +++ b/fdm-core/src/db/migrations/meta/0022_snapshot.json @@ -0,0 +1,3623 @@ +{ + "id": "68820e10-7b74-4632-9d86-500508549bb8", + "prevId": "1eb64507-01b4-41c6-8583-c114db99b60f", + "version": "7", + "dialect": "postgresql", + "tables": { + "fdm.cultivation_catalogue_selecting": { + "name": "cultivation_catalogue_selecting", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_source": { + "name": "b_lu_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk": { + "name": "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "cultivation_catalogue_selecting", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_ending": { + "name": "cultivation_ending", + "schema": "fdm", + "columns": { + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_end": { + "name": "b_lu_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "m_cropresidue": { + "name": "m_cropresidue", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_ending_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_ending_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_ending", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_harvesting": { + "name": "cultivation_harvesting", + "schema": "fdm", + "columns": { + "b_id_harvesting": { + "name": "b_id_harvesting", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_harvest_date": { + "name": "b_lu_harvest_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk": { + "name": "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk", + "tableFrom": "cultivation_harvesting", + "tableTo": "harvestables", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable"], + "columnsTo": ["b_id_harvestable"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cultivation_harvesting_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_harvesting_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_harvesting", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivation_starting": { + "name": "cultivation_starting", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_start": { + "name": "b_lu_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_sowing_amount": { + "name": "b_sowing_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_sowing_method": { + "name": "b_sowing_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cultivation_starting_b_id_fields_b_id_fk": { + "name": "cultivation_starting_b_id_fields_b_id_fk", + "tableFrom": "cultivation_starting", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cultivation_starting_b_lu_cultivations_b_lu_fk": { + "name": "cultivation_starting_b_lu_cultivations_b_lu_fk", + "tableFrom": "cultivation_starting", + "tableTo": "cultivations", + "schemaTo": "fdm", + "columnsFrom": ["b_lu"], + "columnsTo": ["b_lu"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivations": { + "name": "cultivations", + "schema": "fdm", + "columns": { + "b_lu": { + "name": "b_lu", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_catalogue": { + "name": "b_lu_catalogue", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_variety": { + "name": "b_lu_variety", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_lu_idx": { + "name": "b_lu_idx", + "columns": [ + { + "expression": "b_lu", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk": { + "name": "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk", + "tableFrom": "cultivations", + "tableTo": "cultivations_catalogue", + "schemaTo": "fdm", + "columnsFrom": ["b_lu_catalogue"], + "columnsTo": ["b_lu_catalogue"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.cultivations_catalogue": { + "name": "cultivations_catalogue", + "schema": "fdm", + "columns": { + "b_lu_catalogue": { + "name": "b_lu_catalogue", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_source": { + "name": "b_lu_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_name": { + "name": "b_lu_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_lu_name_en": { + "name": "b_lu_name_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_harvestable": { + "name": "b_lu_harvestable", + "type": "b_lu_harvestable", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": true + }, + "b_lu_harvestcat": { + "name": "b_lu_harvestcat", + "type": "b_lu_harvestcat", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_lu_hcat3": { + "name": "b_lu_hcat3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_hcat3_name": { + "name": "b_lu_hcat3_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_lu_croprotation": { + "name": "b_lu_croprotation", + "type": "b_lu_croprotation", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield": { + "name": "b_lu_yield", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_dm": { + "name": "b_lu_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_hi": { + "name": "b_lu_hi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_harvestable": { + "name": "b_lu_n_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_residue": { + "name": "b_lu_n_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_n_fixation": { + "name": "b_n_fixation", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_eom": { + "name": "b_lu_eom", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_eom_residues": { + "name": "b_lu_eom_residues", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_rest_oravib": { + "name": "b_lu_rest_oravib", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "b_lu_variety_options": { + "name": "b_lu_variety_options", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "b_lu_start_default": { + "name": "b_lu_start_default", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_date_harvest_default": { + "name": "b_date_harvest_default", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_lu_catalogue_idx": { + "name": "b_lu_catalogue_idx", + "columns": [ + { + "expression": "b_lu_catalogue", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "b_lu_start_default_format": { + "name": "b_lu_start_default_format", + "value": "b_lu_start_default IS NULL OR b_lu_start_default ~ '^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$'" + }, + "b_date_harvest_default_format": { + "name": "b_date_harvest_default_format", + "value": "b_date_harvest_default IS NULL OR b_date_harvest_default ~ '^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$'" + } + }, + "isRLSEnabled": false + }, + "fdm.derogation_applying": { + "name": "derogation_applying", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_derogation": { + "name": "b_id_derogation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "derogation_one_per_farm_per": { + "name": "derogation_one_per_farm_per", + "columns": [ + { + "expression": "b_id_derogation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "derogation_applying_b_id_farm_farms_b_id_farm_fk": { + "name": "derogation_applying_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "derogation_applying", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "derogation_applying_b_id_derogation_derogations_b_id_derogation_fk": { + "name": "derogation_applying_b_id_derogation_derogations_b_id_derogation_fk", + "tableFrom": "derogation_applying", + "tableTo": "derogations", + "schemaTo": "fdm", + "columnsFrom": ["b_id_derogation"], + "columnsTo": ["b_id_derogation"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.derogations": { + "name": "derogations", + "schema": "fdm", + "columns": { + "b_id_derogation": { + "name": "b_id_derogation", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_derogation_year": { + "name": "b_derogation_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.farms": { + "name": "farms", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_name_farm": { + "name": "b_name_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_businessid_farm": { + "name": "b_businessid_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_address_farm": { + "name": "b_address_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_postalcode_farm": { + "name": "b_postalcode_farm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_farm_idx": { + "name": "b_id_farm_idx", + "columns": [ + { + "expression": "b_id_farm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_acquiring": { + "name": "fertilizer_acquiring", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_acquiring_amount": { + "name": "p_acquiring_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_acquiring_date": { + "name": "p_acquiring_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk": { + "name": "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "fertilizer_acquiring", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_acquiring_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_acquiring_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_acquiring", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_applying": { + "name": "fertilizer_applying", + "schema": "fdm", + "columns": { + "p_app_id": { + "name": "p_app_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_app_amount": { + "name": "p_app_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_app_method": { + "name": "p_app_method", + "type": "p_app_method", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "p_app_date": { + "name": "p_app_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_app_idx": { + "name": "p_app_idx", + "columns": [ + { + "expression": "p_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fertilizer_applying_b_id_fields_b_id_fk": { + "name": "fertilizer_applying_b_id_fields_b_id_fk", + "tableFrom": "fertilizer_applying", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_applying_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_applying_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_applying", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_catalogue_enabling": { + "name": "fertilizer_catalogue_enabling", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_source": { + "name": "p_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk": { + "name": "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "fertilizer_catalogue_enabling", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizer_picking": { + "name": "fertilizer_picking", + "schema": "fdm", + "columns": { + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_id_catalogue": { + "name": "p_id_catalogue", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_picking_date": { + "name": "p_picking_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fertilizer_picking_p_id_fertilizers_p_id_fk": { + "name": "fertilizer_picking_p_id_fertilizers_p_id_fk", + "tableFrom": "fertilizer_picking", + "tableTo": "fertilizers", + "schemaTo": "fdm", + "columnsFrom": ["p_id"], + "columnsTo": ["p_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk": { + "name": "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk", + "tableFrom": "fertilizer_picking", + "tableTo": "fertilizers_catalogue", + "schemaTo": "fdm", + "columnsFrom": ["p_id_catalogue"], + "columnsTo": ["p_id_catalogue"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizers": { + "name": "fertilizers", + "schema": "fdm", + "columns": { + "p_id": { + "name": "p_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_id_idx": { + "name": "p_id_idx", + "columns": [ + { + "expression": "p_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fertilizers_catalogue": { + "name": "fertilizers_catalogue", + "schema": "fdm", + "columns": { + "p_id_catalogue": { + "name": "p_id_catalogue", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "p_source": { + "name": "p_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_name_nl": { + "name": "p_name_nl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p_name_en": { + "name": "p_name_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "p_description": { + "name": "p_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "p_app_method_options": { + "name": "p_app_method_options", + "type": "p_app_method[]", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "p_dm": { + "name": "p_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_density": { + "name": "p_density", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_om": { + "name": "p_om", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_a": { + "name": "p_a", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_hc": { + "name": "p_hc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_eom": { + "name": "p_eom", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_eoc": { + "name": "p_eoc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_rt": { + "name": "p_c_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_of": { + "name": "p_c_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_if": { + "name": "p_c_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_c_fr": { + "name": "p_c_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cn_of": { + "name": "p_cn_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_rt": { + "name": "p_n_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_if": { + "name": "p_n_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_of": { + "name": "p_n_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_n_wc": { + "name": "p_n_wc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_no3_rt": { + "name": "p_no3_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_nh4_rt": { + "name": "p_nh4_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_p_rt": { + "name": "p_p_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_k_rt": { + "name": "p_k_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mg_rt": { + "name": "p_mg_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ca_rt": { + "name": "p_ca_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ne": { + "name": "p_ne", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_s_rt": { + "name": "p_s_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_s_wc": { + "name": "p_s_wc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cu_rt": { + "name": "p_cu_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_zn_rt": { + "name": "p_zn_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_na_rt": { + "name": "p_na_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_si_rt": { + "name": "p_si_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_b_rt": { + "name": "p_b_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mn_rt": { + "name": "p_mn_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ni_rt": { + "name": "p_ni_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_fe_rt": { + "name": "p_fe_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_mo_rt": { + "name": "p_mo_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_co_rt": { + "name": "p_co_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_as_rt": { + "name": "p_as_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cd_rt": { + "name": "p_cd_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cr_rt": { + "name": "p_cr_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cr_vi": { + "name": "p_cr_vi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_pb_rt": { + "name": "p_pb_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_hg_rt": { + "name": "p_hg_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_cl_rt": { + "name": "p_cl_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_ef_nh3": { + "name": "p_ef_nh3", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "p_type_manure": { + "name": "p_type_manure", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_mineral": { + "name": "p_type_mineral", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_compost": { + "name": "p_type_compost", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "p_type_rvo": { + "name": "p_type_rvo", + "type": "p_type_rvo", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "p_id_catalogue_idx": { + "name": "p_id_catalogue_idx", + "columns": [ + { + "expression": "p_id_catalogue", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.field_acquiring": { + "name": "field_acquiring", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_start": { + "name": "b_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_acquiring_method": { + "name": "b_acquiring_method", + "type": "b_acquiring_method", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "field_acquiring_b_id_fields_b_id_fk": { + "name": "field_acquiring_b_id_fields_b_id_fk", + "tableFrom": "field_acquiring", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "field_acquiring_b_id_farm_farms_b_id_farm_fk": { + "name": "field_acquiring_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "field_acquiring", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.field_discarding": { + "name": "field_discarding", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_end": { + "name": "b_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "field_discarding_b_id_fields_b_id_fk": { + "name": "field_discarding_b_id_fields_b_id_fk", + "tableFrom": "field_discarding", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.fields": { + "name": "fields", + "schema": "fdm", + "columns": { + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_name": { + "name": "b_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_geometry": { + "name": "b_geometry", + "type": "geometry(Polygon,4326)", + "primaryKey": false, + "notNull": false + }, + "b_id_source": { + "name": "b_id_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_bufferstrip": { + "name": "b_bufferstrip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_idx": { + "name": "b_id_idx", + "columns": [ + { + "expression": "b_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "b_geom_idx": { + "name": "b_geom_idx", + "columns": [ + { + "expression": "b_geometry", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestable_analyses": { + "name": "harvestable_analyses", + "schema": "fdm", + "columns": { + "b_id_harvestable_analysis": { + "name": "b_id_harvestable_analysis", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_lu_yield": { + "name": "b_lu_yield", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield_fresh": { + "name": "b_lu_yield_fresh", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_yield_bruto": { + "name": "b_lu_yield_bruto", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_tarra": { + "name": "b_lu_tarra", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_dm": { + "name": "b_lu_dm", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_moist": { + "name": "b_lu_moist", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_uww": { + "name": "b_lu_uww", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_cp": { + "name": "b_lu_cp", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_harvestable": { + "name": "b_lu_n_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_n_residue": { + "name": "b_lu_n_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_p_harvestable": { + "name": "b_lu_p_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_p_residue": { + "name": "b_lu_p_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_k_harvestable": { + "name": "b_lu_k_harvestable", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_lu_k_residue": { + "name": "b_lu_k_residue", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_harvestable_analyses_idx": { + "name": "b_id_harvestable_analyses_idx", + "columns": [ + { + "expression": "b_id_harvestable_analysis", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestable_sampling": { + "name": "harvestable_sampling", + "schema": "fdm", + "columns": { + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_harvestable_analysis": { + "name": "b_id_harvestable_analysis", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_sampling_date": { + "name": "b_sampling_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk": { + "name": "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk", + "tableFrom": "harvestable_sampling", + "tableTo": "harvestables", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable"], + "columnsTo": ["b_id_harvestable"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk": { + "name": "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk", + "tableFrom": "harvestable_sampling", + "tableTo": "harvestable_analyses", + "schemaTo": "fdm", + "columnsFrom": ["b_id_harvestable_analysis"], + "columnsTo": ["b_id_harvestable_analysis"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.harvestables": { + "name": "harvestables", + "schema": "fdm", + "columns": { + "b_id_harvestable": { + "name": "b_id_harvestable", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "b_id_harvestable_idx": { + "name": "b_id_harvestable_idx", + "columns": [ + { + "expression": "b_id_harvestable", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.intending_grazing": { + "name": "intending_grazing", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_grazing_intention": { + "name": "b_grazing_intention", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "b_grazing_intention_year": { + "name": "b_grazing_intention_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "intending_grazing_b_id_farm_farms_b_id_farm_fk": { + "name": "intending_grazing_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "intending_grazing", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "intending_grazing_b_id_farm_b_grazing_intention_year_pk": { + "name": "intending_grazing_b_id_farm_b_grazing_intention_year_pk", + "columns": ["b_id_farm", "b_grazing_intention_year"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.organic_certifications": { + "name": "organic_certifications", + "schema": "fdm", + "columns": { + "b_id_organic": { + "name": "b_id_organic", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_organic_traces": { + "name": "b_organic_traces", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_organic_skal": { + "name": "b_organic_skal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "b_organic_issued": { + "name": "b_organic_issued", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_organic_expires": { + "name": "b_organic_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.organic_certifications_holding": { + "name": "organic_certifications_holding", + "schema": "fdm", + "columns": { + "b_id_farm": { + "name": "b_id_farm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "b_id_organic": { + "name": "b_id_organic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organic_one_farm_per_cert": { + "name": "organic_one_farm_per_cert", + "columns": [ + { + "expression": "b_id_organic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organic_certifications_holding_b_id_farm_farms_b_id_farm_fk": { + "name": "organic_certifications_holding_b_id_farm_farms_b_id_farm_fk", + "tableFrom": "organic_certifications_holding", + "tableTo": "farms", + "schemaTo": "fdm", + "columnsFrom": ["b_id_farm"], + "columnsTo": ["b_id_farm"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organic_certifications_holding_b_id_organic_organic_certifications_b_id_organic_fk": { + "name": "organic_certifications_holding_b_id_organic_organic_certifications_b_id_organic_fk", + "tableFrom": "organic_certifications_holding", + "tableTo": "organic_certifications", + "schemaTo": "fdm", + "columnsFrom": ["b_id_organic"], + "columnsTo": ["b_id_organic"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.soil_analysis": { + "name": "soil_analysis", + "schema": "fdm", + "columns": { + "a_id": { + "name": "a_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "a_date": { + "name": "a_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "a_source": { + "name": "a_source", + "type": "a_source", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false, + "default": "'other'" + }, + "a_al_ox": { + "name": "a_al_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_c_of": { + "name": "a_c_of", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ca_co": { + "name": "a_ca_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ca_co_po": { + "name": "a_ca_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_caco3_if": { + "name": "a_caco3_if", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cec_co": { + "name": "a_cec_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_clay_mi": { + "name": "a_clay_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cn_fr": { + "name": "a_cn_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_com_fr": { + "name": "a_com_fr", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_cu_cc": { + "name": "a_cu_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_density_sa": { + "name": "a_density_sa", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_fe_ox": { + "name": "a_fe_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_cc": { + "name": "a_k_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_co": { + "name": "a_k_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_k_co_po": { + "name": "a_k_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_cc": { + "name": "a_mg_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_co": { + "name": "a_mg_co", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_mg_co_po": { + "name": "a_mg_co_po", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_n_pmn": { + "name": "a_n_pmn", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_n_rt": { + "name": "a_n_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_nh4_cc": { + "name": "a_nh4_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_nmin_cc": { + "name": "a_nmin_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_no3_cc": { + "name": "a_no3_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_al": { + "name": "a_p_al", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_cc": { + "name": "a_p_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_ox": { + "name": "a_p_ox", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_rt": { + "name": "a_p_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_sg": { + "name": "a_p_sg", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_p_wa": { + "name": "a_p_wa", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_ph_cc": { + "name": "a_ph_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_s_rt": { + "name": "a_s_rt", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_sand_mi": { + "name": "a_sand_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_silt_mi": { + "name": "a_silt_mi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_som_loi": { + "name": "a_som_loi", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "a_zn_cc": { + "name": "a_zn_cc", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_gwl_class": { + "name": "b_gwl_class", + "type": "b_gwl_class", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "b_soiltype_agr": { + "name": "b_soiltype_agr", + "type": "b_soiltype_agr", + "typeSchema": "fdm", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm.soil_sampling": { + "name": "soil_sampling", + "schema": "fdm", + "columns": { + "b_id_sampling": { + "name": "b_id_sampling", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "b_id": { + "name": "b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "a_id": { + "name": "a_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "a_depth_upper": { + "name": "a_depth_upper", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "a_depth_lower": { + "name": "a_depth_lower", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "b_sampling_date": { + "name": "b_sampling_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "b_sampling_geometry": { + "name": "b_sampling_geometry", + "type": "geometry(MultiPoint,4326)", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "soil_sampling_b_id_fields_b_id_fk": { + "name": "soil_sampling_b_id_fields_b_id_fk", + "tableFrom": "soil_sampling", + "tableTo": "fields", + "schemaTo": "fdm", + "columnsFrom": ["b_id"], + "columnsTo": ["b_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "soil_sampling_a_id_soil_analysis_a_id_fk": { + "name": "soil_sampling_a_id_soil_analysis_a_id_fk", + "tableFrom": "soil_sampling", + "tableTo": "soil_analysis", + "schemaTo": "fdm", + "columnsFrom": ["a_id"], + "columnsTo": ["a_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.account": { + "name": "account", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.invitation": { + "name": "invitation", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "schemaTo": "fdm-authn", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.member": { + "name": "member", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "schemaTo": "fdm-authn", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.organization": { + "name": "organization", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.rate_limit": { + "name": "rate_limit", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.session": { + "name": "session", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "schemaTo": "fdm-authn", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.user": { + "name": "user", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surname": { + "name": "surname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lang": { + "name": "lang", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'nl-NL'" + }, + "farm_active": { + "name": "farm_active", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authn.verification": { + "name": "verification", + "schema": "fdm-authn", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authz.audit": { + "name": "audit", + "schema": "fdm-authz", + "columns": { + "audit_id": { + "name": "audit_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "audit_timestamp": { + "name": "audit_timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "audit_origin": { + "name": "audit_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_resource": { + "name": "target_resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_resource_id": { + "name": "target_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granting_resource": { + "name": "granting_resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granting_resource_id": { + "name": "granting_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-authz.role": { + "name": "role", + "schema": "fdm-authz", + "columns": { + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted": { + "name": "deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "role_idx": { + "name": "role_idx", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-calculator.calculation_cache": { + "name": "calculation_cache", + "schema": "fdm-calculator", + "columns": { + "calculation_hash": { + "name": "calculation_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calculation_function": { + "name": "calculation_function", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "calculator_version": { + "name": "calculator_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "fdm-calculator.calculation_errors": { + "name": "calculation_errors", + "schema": "fdm-calculator", + "columns": { + "calculation_error_id": { + "name": "calculation_error_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "calculation_function": { + "name": "calculation_function", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "calculator_version": { + "name": "calculator_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stack_trace": { + "name": "stack_trace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "fdm.b_acquiring_method": { + "name": "b_acquiring_method", + "schema": "fdm", + "values": [ + "nl_01", + "nl_02", + "nl_03", + "nl_04", + "nl_07", + "nl_09", + "nl_10", + "nl_11", + "nl_12", + "nl_13", + "nl_61", + "nl_63", + "unknown" + ] + }, + "fdm.p_app_method": { + "name": "p_app_method", + "schema": "fdm", + "values": [ + "slotted coulter", + "incorporation", + "incorporation 2 tracks", + "injection", + "shallow injection", + "spraying", + "broadcasting", + "spoke wheel", + "pocket placement", + "narrowband" + ] + }, + "fdm.b_gwl_class": { + "name": "b_gwl_class", + "schema": "fdm", + "values": [ + "I", + "Ia", + "Ic", + "II", + "IIa", + "IIb", + "IIc", + "III", + "IIIa", + "IIIb", + "IV", + "IVu", + "IVc", + "V", + "Va", + "Vao", + "Vad", + "Vb", + "Vbo", + "Vbd", + "sV", + "sVb", + "VI", + "VIo", + "VId", + "VII", + "VIIo", + "VIId", + "VIII", + "VIIIo", + "VIIId" + ] + }, + "fdm.b_lu_harvestcat": { + "name": "b_lu_harvestcat", + "schema": "fdm", + "values": [ + "HC010", + "HC020", + "HC031", + "HC040", + "HC041", + "HC042", + "HC050" + ] + }, + "fdm.b_lu_harvestable": { + "name": "b_lu_harvestable", + "schema": "fdm", + "values": ["none", "once", "multiple"] + }, + "fdm.b_lu_croprotation": { + "name": "b_lu_croprotation", + "schema": "fdm", + "values": [ + "other", + "clover", + "nature", + "potato", + "grass", + "rapeseed", + "starch", + "maize", + "cereal", + "sugarbeet", + "alfalfa", + "catchcrop" + ] + }, + "fdm.a_source": { + "name": "a_source", + "schema": "fdm", + "values": [ + "nl-rva-l122", + "nl-rva-l136", + "nl-rva-l264", + "nl-rva-l320", + "nl-rva-l335", + "nl-rva-l610", + "nl-rva-l648", + "nl-rva-l697", + "nl-other-nmi", + "other" + ] + }, + "fdm.b_soiltype_agr": { + "name": "b_soiltype_agr", + "schema": "fdm", + "values": [ + "moerige_klei", + "rivierklei", + "dekzand", + "zeeklei", + "dalgrond", + "veen", + "loess", + "duinzand", + "maasklei" + ] + }, + "fdm.p_type_rvo": { + "name": "p_type_rvo", + "schema": "fdm", + "values": [ + "10", + "11", + "12", + "13", + "14", + "17", + "18", + "19", + "23", + "30", + "31", + "32", + "33", + "35", + "39", + "40", + "41", + "42", + "43", + "46", + "50", + "56", + "60", + "61", + "75", + "76", + "80", + "81", + "90", + "91", + "92", + "25", + "26", + "27", + "95", + "96", + "97", + "98", + "99", + "100", + "101", + "102", + "103", + "104", + "105", + "106", + "107", + "108", + "109", + "110", + "111", + "112", + "113", + "114", + "115", + "116", + "117", + "120" + ] + } + }, + "schemas": { + "fdm": "fdm", + "fdm-authn": "fdm-authn", + "fdm-authz": "fdm-authz", + "fdm-calculator": "fdm-calculator" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/fdm-core/src/db/migrations/meta/_journal.json b/fdm-core/src/db/migrations/meta/_journal.json index ca8688f23..bb7adc0c0 100644 --- a/fdm-core/src/db/migrations/meta/_journal.json +++ b/fdm-core/src/db/migrations/meta/_journal.json @@ -155,6 +155,13 @@ "when": 1768485087752, "tag": "0021_exotic_mesmero", "breakpoints": true + }, + { + "idx": 22, + "version": "7", + "when": 1768485087753, + "tag": "0022_calculate_bufferstrips", + "breakpoints": true } ] } From 64b1d9cd0201fc5c21399c103afc62b6c0fbe2db Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:15:45 +0100 Subject: [PATCH 06/28] fix: forgotten names --- fdm-calculator/src/balance/nitrogen/input.test.ts | 12 ++++-------- fdm-core/src/cultivation.ts | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/fdm-calculator/src/balance/nitrogen/input.test.ts b/fdm-calculator/src/balance/nitrogen/input.test.ts index 50877f65d..a70169571 100644 --- a/fdm-calculator/src/balance/nitrogen/input.test.ts +++ b/fdm-calculator/src/balance/nitrogen/input.test.ts @@ -92,7 +92,7 @@ describe("collectInputForNitrogenBalance", () => { b_start: new Date("2023-01-01"), b_end: new Date("2023-12-31"), b_acquiring_method: "purchase", - b_isproductive: true, + b_bufferstrip: false, }, { b_id: "field-2", @@ -106,7 +106,7 @@ describe("collectInputForNitrogenBalance", () => { b_start: new Date("2023-01-01"), b_end: new Date("2023-12-31"), b_acquiring_method: "purchase", - b_isproductive: true, + b_bufferstrip: false, }, ] const mockCultivationsData: Cultivation[] = [ @@ -287,11 +287,7 @@ describe("collectInputForNitrogenBalance", () => { ) expect( mockedCalculateAllFieldsNitrogenSupplyByDeposition, - ).toHaveBeenCalledWith( - expect.anything(), - timeframe, - expect.any(String), - ) + ).toHaveBeenCalledWith(expect.anything(), timeframe, expect.any(String)) }) it("should throw an error if getFields fails", async () => { @@ -322,7 +318,7 @@ describe("collectInputForNitrogenBalance", () => { b_start: new Date("2023-01-01"), b_end: new Date("2023-12-31"), b_acquiring_method: "purchase", - b_isproductive: true, + b_bufferstrip: false, }, ] mockedGetFields.mockResolvedValue(mockFieldsData) diff --git a/fdm-core/src/cultivation.ts b/fdm-core/src/cultivation.ts index d6d0cfb1b..8be5642d0 100644 --- a/fdm-core/src/cultivation.ts +++ b/fdm-core/src/cultivation.ts @@ -705,7 +705,7 @@ export async function getCultivations( * b_id: string; // Unique ID of the field * b_name: string; // Name of the field * b_area: number; // Area of the field - * b_isproductive: boolean; // Whether the field is productive + * b_bufferstrip: boolean; // Whether the field is a bufferstrip * fertilizer_applications: [ * { * p_id_catalogue: string; // Fertilizer catalogue ID From 683900597cebf1b7fb330caf0188bef597032486 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:36:40 +0100 Subject: [PATCH 07/28] feat: enable to set if field is a bufferstrip and adjust calculated outputs for it --- .changeset/pretty-baboons-ask.md | 5 +++ .../blocks/balance/buffer-strip-info.tsx | 30 +++++++++++++++ .../blocks/balance/buffer-strip-warning.tsx | 37 +++++++++++++++++++ .../blocks/cultivation/list-plan.tsx | 2 +- .../fertilizer-applications/metrics.tsx | 30 ++++++++++----- .../components/blocks/rotation/columns.tsx | 2 +- .../app/components/blocks/rotation/table.tsx | 2 +- fdm-app/app/integrations/calculator.ts | 3 ++ fdm-app/app/lib/form.ts | 11 +++++- ..._farm.$calendar.balance.nitrogen.$b_id.tsx | 5 +++ ...farm.$calendar.balance.nitrogen._index.tsx | 14 +++---- ....$b_id_farm.$calendar.balance.nitrogen.tsx | 22 ++++++----- ...$calendar.balance.organic-matter.$b_id.tsx | 5 +++ ...calendar.balance.organic-matter._index.tsx | 8 ++-- ..._farm.$calendar.balance.organic-matter.tsx | 22 ++++++----- ...calendar.field.$b_id.fertilizer._index.tsx | 4 +- ...id_farm.$calendar.field.$b_id.overview.tsx | 27 +++++++++++++- ...farm.$b_id_farm.$calendar.field._index.tsx | 4 +- ...farm.$b_id_farm.$calendar.norms._index.tsx | 2 +- ...d_farm.$calendar.nutrient_advice.$b_id.tsx | 1 + ...m.$b_id_farm.$calendar.rotation._index.tsx | 4 +- ..._id_farm.$calendar.fields.$b_id._index.tsx | 34 +++++++++++++++++ ...arm.create.$b_id_farm.$calendar.fields.tsx | 2 +- 23 files changed, 221 insertions(+), 55 deletions(-) create mode 100644 .changeset/pretty-baboons-ask.md create mode 100644 fdm-app/app/components/blocks/balance/buffer-strip-info.tsx create mode 100644 fdm-app/app/components/blocks/balance/buffer-strip-warning.tsx diff --git a/.changeset/pretty-baboons-ask.md b/.changeset/pretty-baboons-ask.md new file mode 100644 index 000000000..b2c340ccd --- /dev/null +++ b/.changeset/pretty-baboons-ask.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Users can now indicate if a field is a buffer strip. Fields marked as buffer strips are excluded from nitrogen and organic matter balances at the farm level, and their nutrient advice and norms are automatically adjusted to zero. The UI now displays informative messages when calculations are skipped for these fields. diff --git a/fdm-app/app/components/blocks/balance/buffer-strip-info.tsx b/fdm-app/app/components/blocks/balance/buffer-strip-info.tsx new file mode 100644 index 000000000..f7fd0bdc7 --- /dev/null +++ b/fdm-app/app/components/blocks/balance/buffer-strip-info.tsx @@ -0,0 +1,30 @@ +import { PanelsRightBottom } from "lucide-react" +import { Button } from "~/components/ui/button" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "~/components/ui/tooltip" + +export function BufferStripInfo() { + return ( + + + + + + +

Bufferstroken zijn uitgesloten van de balansberekening

+
+
+
+ ) +} diff --git a/fdm-app/app/components/blocks/balance/buffer-strip-warning.tsx b/fdm-app/app/components/blocks/balance/buffer-strip-warning.tsx new file mode 100644 index 000000000..75bcd82e5 --- /dev/null +++ b/fdm-app/app/components/blocks/balance/buffer-strip-warning.tsx @@ -0,0 +1,37 @@ +import { NavLink } from "react-router" +import { useCalendarStore } from "~/store/calendar" +import { Button } from "~/components/ui/button" +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card" + +export function BufferStripWarning({ b_id }: { b_id: string }) { + const calendar = useCalendarStore((state) => state.calendar) + + return ( +
+ + + Bufferstrook: uitgesloten van balans + + +
+

+ Dit perceel is gemarkeerd als bufferstrook en wordt + daarom niet meegenomen in de balansberekening. +

+
+
+ + + + + +
+
+ ) +} diff --git a/fdm-app/app/components/blocks/cultivation/list-plan.tsx b/fdm-app/app/components/blocks/cultivation/list-plan.tsx index 80bb99a9f..765104c3f 100644 --- a/fdm-app/app/components/blocks/cultivation/list-plan.tsx +++ b/fdm-app/app/components/blocks/cultivation/list-plan.tsx @@ -7,7 +7,7 @@ interface CultivationField { b_id: string b_name: string b_area: number - b_isproductive: boolean + b_bufferstrip: boolean } interface CultivationPlanItem { diff --git a/fdm-app/app/components/blocks/fertilizer-applications/metrics.tsx b/fdm-app/app/components/blocks/fertilizer-applications/metrics.tsx index 6b94226ee..d3d381617 100644 --- a/fdm-app/app/components/blocks/fertilizer-applications/metrics.tsx +++ b/fdm-app/app/components/blocks/fertilizer-applications/metrics.tsx @@ -61,6 +61,7 @@ interface FertilizerApplicationMetricsData { dose: Dose b_id: string b_id_farm: string + b_bufferstrip: boolean calendar: string cultivations: Cultivation[] activeCultivation: Cultivation | undefined @@ -148,7 +149,7 @@ export function FertilizerApplicationMetricsCard({ - + {isSubmitting ? ( ) : ( @@ -166,9 +167,9 @@ export function FertilizerApplicationMetricsCard({ {(resolvedNorms) => { if (!resolvedNorms) { return ( -
+ Geen gebruiksnormen beschikbaar voor dit jaar. -
+ ) } return ( @@ -357,7 +358,7 @@ export function FertilizerApplicationMetricsCard({ - + {isSubmitting ? ( ) : ( @@ -374,14 +375,25 @@ export function FertilizerApplicationMetricsCard({ resolve={nitrogenBalance} > {(resolvedNitrogenBalance) => { + if ( + fertilizerApplicationMetricsData.b_bufferstrip + ) { + return ( + + Geen stikstofbalans + beschikbaar voor + bufferstrook. + + ) + } const balance = resolvedNitrogenBalance?.balance if (!balance) { return ( -
+ Geen balans beschikbaar -
+ ) } return ( @@ -483,7 +495,7 @@ export function FertilizerApplicationMetricsCard({ )} - + {isSubmitting ? ( ) : ( @@ -505,10 +517,10 @@ export function FertilizerApplicationMetricsCard({ {(resolvedNutrientAdvice) => { if (!resolvedNutrientAdvice) { return ( -
+ Geen advies beschikbaar -
+ ) } return ( diff --git a/fdm-app/app/components/blocks/rotation/columns.tsx b/fdm-app/app/components/blocks/rotation/columns.tsx index a19f3ac3a..1febb00e7 100644 --- a/fdm-app/app/components/blocks/rotation/columns.tsx +++ b/fdm-app/app/components/blocks/rotation/columns.tsx @@ -31,7 +31,7 @@ export type RotationExtended = { b_id: string b_name: string b_area: number - b_isproductive: boolean + b_bufferstrip: boolean a_som_loi: number b_soiltype_agr: string b_lu_harvest_date: Date[] diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index ba1e7a55f..ed9837751 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -157,7 +157,7 @@ export function DataTable({ ) => { if ( showProductiveOnly && - !row.original.fields.some((field) => field.b_isproductive) + row.original.fields.some((field) => field.b_bufferstrip) ) { return false } diff --git a/fdm-app/app/integrations/calculator.ts b/fdm-app/app/integrations/calculator.ts index 085ff78d5..48128e9fb 100644 --- a/fdm-app/app/integrations/calculator.ts +++ b/fdm-app/app/integrations/calculator.ts @@ -164,6 +164,8 @@ export async function getNutrientAdviceForField({ const currentSoilData = await getCurrentSoilData(fdm, principal_id, b_id) + const field = await getField(fdm, principal_id, b_id) + const cultivations = await getCultivations( fdm, principal_id, @@ -185,6 +187,7 @@ export async function getNutrientAdviceForField({ b_centroid: b_centroid, currentSoilData: currentSoilData, nmiApiKey: nmiApiKey, + b_bufferstrip: field.b_bufferstrip, }) return nutrientAdvice diff --git a/fdm-app/app/lib/form.ts b/fdm-app/app/lib/form.ts index 431b567e1..abb6cd8bf 100644 --- a/fdm-app/app/lib/form.ts +++ b/fdm-app/app/lib/form.ts @@ -46,8 +46,17 @@ export async function extractFormValuesFromRequest( formObject[key] = value.replace(/['"]+/g, "").trim() } + const cleanedValue = formObject[key] + + // Parse boolean values + if (cleanedValue === "true" || cleanedValue === "on") { + formObject[key] = true + } else if (cleanedValue === "false") { + formObject[key] = false + } + // Parse null values at formData - if (value === "null") { + if (value === "null" || cleanedValue === "null") { formObject[key] = null } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx index 5d50f9165..0e3580281 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx @@ -24,6 +24,7 @@ import { import { NitrogenBalanceChart } from "~/components/blocks/balance/nitrogen-chart" import NitrogenBalanceDetails from "~/components/blocks/balance/nitrogen-details" import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" +import { BufferStripWarning } from "~/components/blocks/balance/buffer-strip-warning" import { Button } from "~/components/ui/button" import { Card, @@ -192,6 +193,10 @@ function NitrogenBalance({ const page = location.pathname const calendar = useCalendarStore((state) => state.calendar) + if (field.b_bufferstrip) { + return + } + if (fieldResult.errorMessage) { return (
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx index 4327fb680..f065f5095 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx @@ -1,6 +1,4 @@ -import { - type NitrogenBalanceFieldResultNumeric, -} from "@svenvw/fdm-calculator" +import { type NitrogenBalanceFieldResultNumeric } from "@svenvw/fdm-calculator" import { getFarm, getFields } from "@svenvw/fdm-core" import { ArrowDown, @@ -22,7 +20,7 @@ import { } from "react-router" import { NitrogenBalanceChart } from "~/components/blocks/balance/nitrogen-chart" import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" -import { FieldFilterToggle } from "~/components/custom/field-filter-toggle" +import { BufferStripInfo } from "~/components/blocks/balance/buffer-strip-info" import { Card, CardContent, @@ -41,7 +39,6 @@ import { getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError, reportError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import { useFieldFilterStore } from "~/store/field-filter" // Meta export const meta: MetaFunction = () => { @@ -153,7 +150,6 @@ function FarmBalanceNitrogenOverview({ asyncData, }: Awaited>) { const { nitrogenBalanceResult } = use(asyncData) - const { showProductiveOnly } = useFieldFilterStore() const resolvedNitrogenBalanceResult = nitrogenBalanceResult @@ -195,13 +191,13 @@ function FarmBalanceNitrogenOverview({ } const { hasErrors } = resolvedNitrogenBalanceResult + console.log(hasErrors) const fieldsMap = new Map(fields.map((f) => [f.b_id, f])) const filteredFields = resolvedNitrogenBalanceResult.fields.filter( (fieldResult: NitrogenBalanceFieldResultNumeric) => { - if (!showProductiveOnly) return true const fieldData = fieldsMap.get(fieldResult.b_id) - return fieldData ? fieldData.b_isproductive === true : false + return fieldData ? !fieldData.b_bufferstrip : false }, ) @@ -335,7 +331,7 @@ function FarmBalanceNitrogenOverview({

Percelen

- +
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx index 665f13e18..6131409fe 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx @@ -93,16 +93,18 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_id_farm, timeframe, ) - const fieldOptions = fields.map((field) => { - if (!field?.b_id || !field?.b_name) { - throw new Error("Invalid field data structure") - } - return { - b_id: field.b_id, - b_name: field.b_name, - b_area: Math.round(field.b_area * 10) / 10, - } - }) + const fieldOptions = fields + .filter((field) => !field.b_bufferstrip) + .map((field) => { + if (!field?.b_id || !field?.b_name) { + throw new Error("Invalid field data structure") + } + return { + b_id: field.b_id, + b_name: field.b_name, + b_area: Math.round(field.b_area * 10) / 10, + } + }) // Return user information from loader return { diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx index 291cf5417..ccf2c0e58 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx @@ -18,6 +18,7 @@ import { import { OrganicMatterBalanceChart } from "~/components/blocks/balance/organic-matter-chart" import OrganicMatterBalanceDetails from "~/components/blocks/balance/organic-matter-details" import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" +import { BufferStripWarning } from "~/components/blocks/balance/buffer-strip-warning" import { Button } from "~/components/ui/button" import { Card, @@ -140,6 +141,10 @@ function OrganicMatterBalance({ const page = location.pathname const calendar = useCalendarStore((state) => state.calendar) + if (field.b_bufferstrip) { + return + } + if (fieldResult.errorMessage) { return (
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter._index.tsx index 1dc1b5430..12dde9303 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter._index.tsx @@ -18,7 +18,7 @@ import { } from "react-router" import { OrganicMatterBalanceChart } from "~/components/blocks/balance/organic-matter-chart" import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" // Can be reused -import { FieldFilterToggle } from "~/components/custom/field-filter-toggle" +import { BufferStripInfo } from "~/components/blocks/balance/buffer-strip-info" import { Card, CardContent, @@ -129,7 +129,6 @@ function FarmBalanceOrganicMatterOverview({ asyncData, }: Awaited>) { const { organicMatterBalanceResult } = use(asyncData) - const { showProductiveOnly } = useFieldFilterStore() if (organicMatterBalanceResult.errorMessage) { return ( @@ -172,9 +171,8 @@ function FarmBalanceOrganicMatterOverview({ const fieldsMap = new Map(fields.map((f) => [f.b_id, f])) const filteredFields = organicMatterBalanceResult.fields.filter( (fieldResult: OrganicMatterBalanceFieldResultNumeric) => { - if (!showProductiveOnly) return true const fieldData = fieldsMap.get(fieldResult.b_id) - return fieldData ? fieldData.b_isproductive === true : false + return fieldData ? !fieldData.b_bufferstrip : false }, ) @@ -252,7 +250,7 @@ function FarmBalanceOrganicMatterOverview({

Percelen

- +
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.tsx index c43f2b3ef..34e0ac1ca 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.tsx @@ -93,16 +93,18 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_id_farm, timeframe, ) - const fieldOptions = fields.map((field) => { - if (!field?.b_id || !field?.b_name) { - throw new Error("Invalid field data structure") - } - return { - b_id: field.b_id, - b_name: field.b_name, - b_area: Math.round(field.b_area * 10) / 10, - } - }) + const fieldOptions = fields + .filter((field) => !field.b_bufferstrip) + .map((field) => { + if (!field?.b_id || !field?.b_name) { + throw new Error("Invalid field data structure") + } + return { + b_id: field.b_id, + b_name: field.b_name, + b_area: Math.round(field.b_area * 10) / 10, + } + }) // Return user information from loader return { diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx index cf448eaea..da55cf58b 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx @@ -177,6 +177,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_centroid: b_centroid, currentSoilData: currentSoilData, nmiApiKey: nmiApiKey, + b_bufferstrip: field.b_bufferstrip, }) } @@ -190,7 +191,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { calendar, }) : Promise.resolve(null), - nitrogenBalance: getNitrogenBalanceforField({ + nitrogenBalance: getNitrogenBalanceForField({ fdm, principal_id, b_id_farm, @@ -201,6 +202,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { dose: dose.dose, b_id: b_id, b_id_farm: b_id_farm, + b_bufferstrip: field.b_bufferstrip, calendar: calendar, cultivations, activeCultivation, diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx index 058397f3f..6b329bb9a 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx @@ -21,6 +21,7 @@ import { z } from "zod" import { DatePicker } from "~/components/custom/date-picker-v2" import { Spinner } from "~/components/ui/spinner" import { Button } from "~/components/ui/button" +import { Switch } from "~/components/ui/switch" import { Field, FieldError, FieldLabel } from "~/components/ui/field" import { Input } from "~/components/ui/input" import { @@ -121,6 +122,7 @@ export default function FarmFieldsOverviewBlock() { b_acquiring_method: loaderData.field.b_acquiring_method, b_start: loaderData.field.b_start ?? new Date(), b_end: loaderData.field.b_end, + b_bufferstrip: loaderData.field.b_bufferstrip ?? false, }, }) @@ -130,6 +132,7 @@ export default function FarmFieldsOverviewBlock() { b_acquiring_method: loaderData.field.b_acquiring_method, b_start: loaderData.field.b_start ?? new Date(), b_end: loaderData.field.b_end, + b_bufferstrip: loaderData.field.b_bufferstrip ?? false, }) }, [loaderData, form.reset]) @@ -176,7 +179,7 @@ export default function FarmFieldsOverviewBlock() { render={({ field, fieldState }) => ( Is perceel in eigendom of pacht? @@ -207,6 +210,26 @@ export default function FarmFieldsOverviewBlock() { )} /> + ( +
+
+ + Bufferstrook + +

+ Is dit perceel een bufferstrook?{" "} +

+
+ +
+ )} + /> { diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx index 3bf3a2bbd..7976d5d44 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx @@ -174,7 +174,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { a_som_loi: a_som_loi, b_soiltype_agr: b_soiltype_agr, b_area: Math.round(field.b_area * 10) / 10, - b_isproductive: field.b_isproductive ?? true, + b_bufferstrip: field.b_bufferstrip, has_write_permission: has_write_permission, } }), @@ -223,7 +223,7 @@ export default function FarmFieldIndex() { if (!showProductiveOnly) { return true } - return field.b_isproductive === true + return field.b_bufferstrip === false }) const currentFarmName = 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 299a36943..e719cd560 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 @@ -409,7 +409,7 @@ function Norms(loaderData: Awaited>) { const filteredFieldNorms = fieldNorms.filter((fieldNorm) => { if (!showProductiveOnly) return true const fieldData = fieldsMap.get(fieldNorm.b_id) - return fieldData ? fieldData.b_isproductive === true : false + return fieldData ? fieldData.b_bufferstrip === false : true }) return ( diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx index 301e8219a..e87d098ce 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx @@ -147,6 +147,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_centroid: field.b_centroid, currentSoilData: resolvedCurrentSoilData, nmiApiKey: nmiApiKey, + b_bufferstrip: field.b_bufferstrip, }) return { diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx index 8a9355b43..70191f21a 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx @@ -214,7 +214,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { a_som_loi: a_som_loi, b_soiltype_agr: b_soiltype_agr, b_area: Math.round(field.b_area * 10) / 10, - b_isproductive: field.b_isproductive ?? true, + b_bufferstrip: field.b_bufferstrip, } }), ) @@ -297,7 +297,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_id: field.b_id, b_name: field.b_name, b_area: field.b_area, - b_isproductive: field.b_isproductive, + b_bufferstrip: field.b_bufferstrip, a_som_loi: field.a_som_loi ?? 0, b_soiltype_agr: field.b_soiltype_agr ?? "", b_lu_harvest_date: field.harvests diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx index a6b421fa1..c8344c156 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx @@ -34,6 +34,7 @@ import { SoilDataCards } from "~/components/blocks/soil/cards" import { Combobox } from "~/components/custom/combobox" import { Spinner } from "~/components/ui/spinner" import { Button } from "~/components/ui/button" +import { Checkbox } from "~/components/ui/checkbox" import { Card, CardContent, @@ -87,6 +88,7 @@ const FormSchema = z.object({ b_lu_catalogue: z.string({ required_error: "Hoofdgewas is verplicht", }), + b_bufferstrip: z.boolean().optional(), }) /** @@ -207,6 +209,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { currentSoilData: currentSoilData, soilParameterDescription: soilParameterDescription, b_area: field.b_area, + b_bufferstrip: field.b_bufferstrip, featureCollection: featureCollection, cultivationOptions: cultivationOptions, mapStyle: mapStyle, @@ -236,6 +239,7 @@ export default function Index() { b_name: loaderData.b_name ?? "", b_area: Math.round(loaderData.b_area * 10) / 10, b_lu_catalogue: loaderData.b_lu_catalogue ?? "", + b_bufferstrip: loaderData.b_bufferstrip ?? false, }, }) @@ -244,6 +248,7 @@ export default function Index() { b_name: loaderData.b_name ?? "", b_area: Math.round(loaderData.b_area * 10) / 10, b_lu_catalogue: loaderData.b_lu_catalogue ?? "", + b_bufferstrip: loaderData.b_bufferstrip ?? false, }) }, [loaderData, form.reset]) @@ -348,6 +353,34 @@ export default function Index() { )} /> + ( + + + + +
+ + Bufferstrook + + + Is dit perceel + een + bufferstrook? + +
+
+ )} + />
@@ -494,6 +527,7 @@ export async function action({ request, params }: ActionFunctionArgs) { undefined, undefined, undefined, + formValues.b_bufferstrip, ) const cultivations = await getCultivations( diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx index 6ac284f30..a37fa684c 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx @@ -107,7 +107,7 @@ export default function Index() { () => fields .filter((field) => - showProductiveOnly ? field.b_isproductive : true, + showProductiveOnly ? field.b_bufferstrip === false : true, ) .slice() .sort((a, b) => b.b_area - a.b_area) // Sort by area in descending order From 35e799ef2e5035611228a781626a84ee08f8f3cd Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:39:09 +0100 Subject: [PATCH 08/28] fix: error checking for balance --- fdm-calculator/src/balance/nitrogen/index.ts | 3 ++- fdm-calculator/src/balance/organic-matter/index.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fdm-calculator/src/balance/nitrogen/index.ts b/fdm-calculator/src/balance/nitrogen/index.ts index d379a7836..c007f28b8 100644 --- a/fdm-calculator/src/balance/nitrogen/index.ts +++ b/fdm-calculator/src/balance/nitrogen/index.ts @@ -527,7 +527,8 @@ export function calculateNitrogenBalancesFieldToFarm( fields: fieldsWithBalanceResults, hasErrors: hasErrors || - fieldsWithBalanceResults.length !== successfulFieldBalances.length, + fieldsWithBalanceResults.filter((result) => !result.b_bufferstrip) + .length !== successfulFieldBalances.length, fieldErrorMessages: fieldErrorMessages, } diff --git a/fdm-calculator/src/balance/organic-matter/index.ts b/fdm-calculator/src/balance/organic-matter/index.ts index 7d1c0abf7..465905913 100644 --- a/fdm-calculator/src/balance/organic-matter/index.ts +++ b/fdm-calculator/src/balance/organic-matter/index.ts @@ -256,7 +256,10 @@ export function calculateOrganicMatterBalancesFieldToFarm( supply: avgFarmSupply, degradation: avgFarmDegradation, fields: fieldsWithBalanceResults, - hasErrors, + hasErrors: + hasErrors || + fieldsWithBalanceResults.filter((result) => !result.b_bufferstrip) + .length !== successfulFieldBalances.length, fieldErrorMessages, }) as OrganicMatterBalanceNumeric } From 8006efcc82595aee05ef60dd5c5e664f2cb046d4 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:13:54 +0100 Subject: [PATCH 09/28] refactor: improve form of field overvied and improve mobile and small screen design for the field pages --- .changeset/mobile-ux-improvements.md | 10 + .../components/blocks/farm/farm-content.tsx | 6 +- .../app/components/blocks/farm/farm-title.tsx | 16 +- .../blocks/fertilizer-applications/form.tsx | 36 +- .../fertilizer-applications/metrics.tsx | 762 +++++++++--------- .../app/components/blocks/header/action.tsx | 4 +- fdm-app/app/components/blocks/header/base.tsx | 22 +- fdm-app/app/components/blocks/header/farm.tsx | 22 +- .../app/components/blocks/header/field.tsx | 22 +- .../app/components/custom/sidebar-page.tsx | 4 +- ...farm.$calendar.field.$b_id.cultivation.tsx | 2 +- ...calendar.field.$b_id.fertilizer._index.tsx | 56 +- ...id_farm.$calendar.field.$b_id.overview.tsx | 282 ++++--- ...farm.$calendar.field.fertilizer._index.tsx | 16 +- 14 files changed, 668 insertions(+), 592 deletions(-) create mode 100644 .changeset/mobile-ux-improvements.md diff --git a/.changeset/mobile-ux-improvements.md b/.changeset/mobile-ux-improvements.md new file mode 100644 index 000000000..a2d4b98bc --- /dev/null +++ b/.changeset/mobile-ux-improvements.md @@ -0,0 +1,10 @@ +--- +"fdm-app": patch +--- + +Significant UI/UX and mobile responsiveness improvements across the application: +- Field Overview: Refactored to use a responsive Card layout and simplified labels for better mobile fit. +- Header: Optimized for small screens with flexible height, truncated labels, and streamlined breadcrumbs. +- Sidebar: Reduced width and internal spacing on medium screens to reclaim horizontal space for content. +- Fertilizer Dashboard: Redesigned with a stacked layout for better readability on laptops and added robust text truncation. +- Layout: Improved global padding and adjusted breakpoints (xl/2xl) to ensure a polished look across mobile, tablet, and desktop. diff --git a/fdm-app/app/components/blocks/farm/farm-content.tsx b/fdm-app/app/components/blocks/farm/farm-content.tsx index 4efb2dde1..235073ea4 100644 --- a/fdm-app/app/components/blocks/farm/farm-content.tsx +++ b/fdm-app/app/components/blocks/farm/farm-content.tsx @@ -12,10 +12,10 @@ interface FarmContentProps { export function FarmContent({ sidebarItems, children }: FarmContentProps) { return ( -
-
+
+
{sidebarItems && ( -