diff --git a/book_source/03_topical_pages/02_pecan_standards.Rmd b/book_source/03_topical_pages/02_pecan_standards.Rmd index 83732f89d7..f30b0a5cd4 100644 --- a/book_source/03_topical_pages/02_pecan_standards.Rmd +++ b/book_source/03_topical_pages/02_pecan_standards.Rmd @@ -103,7 +103,7 @@ Externally imposed changes in system state, such as planting and harvest or till Event types are defined by the PEcAn events spec, which is currently provided as a JSON schema file bundled into the PEcAn.data.land package (TODO consider exposing this in a more linkable format?). Valid events files are stored as JSON, with each events file containing an array of sites. Each site contains an id, a schema version, and an array of events, usually recorded in date order. Each event has a date, an event type, and one or more values from the required and optional properties that are documented below for each event type. Any event is also allowed to contain arbitrary additional properties not mentioned in the spec; PEcAn will pass these on to models with no further validation. -The currently supported event types are: planting, harvest, irrigation, fertilization, and tillage. Each of these is described briefly below. You will note that all of them are focused on agronomic management of croplands; future work will extend this framework to include disturbance of unmanaged systems such as fire, inundation or insect outbreaks. +The currently supported event types are: planting, harvest, irrigation, fertilization, tillage, and phenology (controlled by separate `leafon` and `leafoff` event types). Each of these is described briefly below. You will note that all of them are focused on agronomic management of croplands; future work will extend this framework to include disturbance of unmanaged systems such as fire, inundation or insect outbreaks. #### planting @@ -176,6 +176,28 @@ Optional properties: * `intensity_category`: string giving additional information on the type or amount of tillage performed. * `depth_m`: Depth of soil affected by the event. +#### phenology (`leafon` / `leafoff`) + +Prescribed dates of onset of leaf growth (`leafon`) and senescence (`leafoff`) phenological events. As with other events, the precise definition of these events varies significantly by model. Note also that the inclusion of leaf on / leaf off events *does not* guarantee that these will replace the internal model representation of these events; rather, models must be explicitly configured to turn off their internal representations of leaf phenology and to use prescribed events instead. + +These event types only take a date --- there are (currently) no additional properties. + +This event type is available in schema versions >= 0.1.2. + +Here is an example: + +```json +[ + { + "pecan_events_version": "0.1.2", + "site_id": "EX1", + "events": [ + {"event_type": "leafon", "date": "2025-04-05"}, + {"event_type": "leafoff", "date": "2025-10-08"} + ] + } +] +``` ## Output Standards diff --git a/modules/data.land/NEWS.md b/modules/data.land/NEWS.md index 033ead5547..a690c3281e 100644 --- a/modules/data.land/NEWS.md +++ b/modules/data.land/NEWS.md @@ -20,12 +20,11 @@ ## Changed * Package `traits`, used by `match_pft()` and `match_species_id()` only when no database connection is provided, is now suggested rather than required. -* Updated `validate_events_json()` to use events schema v0.1.1 by default. The previous default v0.1.0 is still available by setting `schema_version="0.1.0"`. +* Updated `validate_events_json()` to use events schema v0.1.2 by default. Older versions are still accessible by setting, e.g., `schema_version="0.1.1"`. * Packages `doSNOW`, `dplR`, `httr`, `MCMCpack`, `mvtnorm`, `neonUtilities`, `neonstore`, `PEcAn.benchmark`, `PEcAn.visualization`, `rjags`, `sirt`, and `sp` are now suggested rather than required. They are only needed for specific optional functionality. (#3599) -* Updated `validate_events_json()` to use events schema v0.1.1 by default. The previous default v0.1.0 is still available by setting `schema_version="0.1.0"`. # PEcAn.data.land 1.9.0 diff --git a/modules/data.land/R/validate_events.R b/modules/data.land/R/validate_events.R index b96aa6ab31..ee5bdee6c4 100644 --- a/modules/data.land/R/validate_events.R +++ b/modules/data.land/R/validate_events.R @@ -26,7 +26,7 @@ #' # package = "PEcAn.data.land")) #' #' @export -validate_events_json <- function(events_json, schema_version = "0.1.1", verbose = TRUE, max_errs = 50) { +validate_events_json <- function(events_json, schema_version = "0.1.2", verbose = TRUE, max_errs = 50) { if (!file.exists(events_json)) { PEcAn.logger::logger.error(glue::glue("events_json file does not exist: {events_json}")) return(FALSE) diff --git a/modules/data.land/inst/events_fixtures/events_site1.json b/modules/data.land/inst/events_fixtures/events_site1.json index d2b583e447..f7319936b1 100644 --- a/modules/data.land/inst/events_fixtures/events_site1.json +++ b/modules/data.land/inst/events_fixtures/events_site1.json @@ -1,6 +1,6 @@ [ { - "pecan_events_version": "0.1.1", + "pecan_events_version": "0.1.2", "site_id": "EX1", "events": [ { diff --git a/modules/data.land/inst/events_fixtures/events_site1_site2.json b/modules/data.land/inst/events_fixtures/events_site1_site2.json index fb2cc15124..217cbdac08 100644 --- a/modules/data.land/inst/events_fixtures/events_site1_site2.json +++ b/modules/data.land/inst/events_fixtures/events_site1_site2.json @@ -1,6 +1,6 @@ [ { - "pecan_events_version": "0.1.1", + "pecan_events_version": "0.1.2", "site_id": "S1", "events": [ { @@ -20,7 +20,7 @@ ] }, { - "pecan_events_version": "0.1.1", + "pecan_events_version": "0.1.2", "site_id": "S2", "events": [ { diff --git a/modules/data.land/inst/events_schema_v0.1.2.json b/modules/data.land/inst/events_schema_v0.1.2.json new file mode 100644 index 0000000000..e41596abef --- /dev/null +++ b/modules/data.land/inst/events_schema_v0.1.2.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://pecanproject.org/schema/events-mvp-0-1-1.json", + "oneOf": [ + { "$ref": "#/$defs/site" }, + { "type": "array", "items": { "$ref": "#/$defs/site" } } + ], + "$defs": { + "site": { + "type": "object", + "required": ["pecan_events_version", "site_id", "events"], + "properties": { + "pecan_events_version": { "type": "string", "const": "0.1.2" }, + "site_id": { "type": "string", "minLength": 1 }, + "ensemble_id": { "type": ["string", "null"], "minLength": 1 }, + "geometry_uri": { "type": ["string", "null"], "format": "uri" }, + "provenance": { "type": "object", "additionalProperties": true }, + "events": { + "type": "array", + "items": { + "type": "object", + "required": ["event_type", "date"], + "properties": { + "event_type": { + "type": "string", + "enum": ["planting", "harvest", "irrigation", "fertilization", "tillage", "leafon", "leafoff"] + }, + "date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, + "fraction_area": { "type": "number", "minimum": 0, "maximum": 1, "default": 1.0 }, + "source": { "type": "string" }, + + "leaf_c_kg_m2": { "type": "number", "minimum": 0 }, + "wood_c_kg_m2": { "type": "number", "minimum": 0 }, + "fine_root_c_kg_m2": { "type": "number", "minimum": 0 }, + "coarse_root_c_kg_m2": { "type": "number", "minimum": 0 }, + "cultivar": { "type": "string" }, + "crop_code": { "type": "string" }, + "crop_display": { "type": "string" }, + + "frac_above_removed_0to1": { "type": "number", "minimum": 0, "maximum": 1 }, + "frac_below_removed_0to1": { "type": "number", "minimum": 0, "maximum": 1 }, + "frac_above_to_litter_0to1": { "type": "number", "minimum": 0, "maximum": 1 }, + "frac_below_to_litter_0to1": { "type": "number", "minimum": 0, "maximum": 1 }, + + "amount_mm": { "type": "number", "minimum": 0 }, + "method": { "type": "string", "enum": ["soil", "canopy", "flood"] }, + "immed_evap_frac_0to1": { "type": "number", "minimum": 0, "maximum": 1 }, + + "org_c_kg_m2": { "type": "number", "minimum": 0 }, + "org_n_kg_m2": { "type": "number", "minimum": 0 }, + "nh4_n_kg_m2": { "type": "number", "minimum": 0 }, + "no3_n_kg_m2": { "type": "number", "minimum": 0 }, + + "tillage_eff_0to1": { "type": "number", "minimum": 0 }, + "intensity_category": { "type": "string" }, + "depth_m": { "type": "number", "minimum": 0 } + }, + "allOf": [ + { "if": { "properties": { "event_type": { "const": "planting" } } }, + "then": { "required": ["crop_code", "leaf_c_kg_m2"] } }, + { "if": { "properties": { "event_type": { "const": "harvest" } } }, + "then": { "required": ["frac_above_removed_0to1"] } }, + { "if": { "properties": { "event_type": { "const": "irrigation" } } }, + "then": { "required": ["amount_mm", "method"] } }, + { "if": { "properties": { "event_type": { "const": "fertilization" } } }, + "then": { "anyOf": [ + { "required": ["org_c_kg_m2"] }, + { "required": ["nh4_n_kg_m2"] }, + { "required": ["no3_n_kg_m2"] } + ] } }, + { "if": { "properties": { "event_type": { "const": "tillage" } } }, + "then": { "required": ["tillage_eff_0to1"] } } + ], + "additionalProperties": true + } + } + }, + "additionalProperties": false + } + } +} diff --git a/modules/data.land/man/validate_events_json.Rd b/modules/data.land/man/validate_events_json.Rd index 18c16706f1..27d2b5d08d 100644 --- a/modules/data.land/man/validate_events_json.Rd +++ b/modules/data.land/man/validate_events_json.Rd @@ -6,7 +6,7 @@ \usage{ validate_events_json( events_json, - schema_version = "0.1.1", + schema_version = "0.1.2", verbose = TRUE, max_errs = 50 ) diff --git a/workflows/sipnet-restart-workflow/01-prepare-events.R b/workflows/sipnet-restart-workflow/01-prepare-events.R index 7992c51224..17a0bbd09c 100644 --- a/workflows/sipnet-restart-workflow/01-prepare-events.R +++ b/workflows/sipnet-restart-workflow/01-prepare-events.R @@ -124,7 +124,7 @@ irrigation_n_list <- purrr::map(irrigation_events_list, make_event_list) make_all_events <- function(...) { dplyr::bind_rows(...) |> dplyr::summarize(events = list(purrr::list_c(.data$events)), .by = "site_id") |> - dplyr::mutate(pecan_events_version = "0.1.1", .before = "site_id") + dplyr::mutate(pecan_events_version = "0.1.2", .before = "site_id") } # NOTE: This only works if we each event type has either 1 ensemble or the same