Skip to content

Commit 886cd83

Browse files
authored
Merge pull request #4088 from sarahkanee/yp-planting-events
Add planting events for young woody (YP) parcels
2 parents 5a625ed + 4a7e245 commit 886cd83

5 files changed

Lines changed: 109 additions & 23 deletions

File tree

modules/data.remote/inst/ccmmf/events/R/matched_input.R

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ load_landiq_season2_identity <- function(year, landiq_crops, cropcode_csv) {
145145
} else {
146146
landiq[, SPECOND := NA_character_]
147147
}
148+
if ("COUNTY" %in% names(landiq)) {
149+
landiq[, COUNTY := trimws(as.character(COUNTY))]
150+
} else {
151+
landiq[, COUNTY := NA_character_]
152+
}
148153

149154
landiq <- merge(landiq, ag_pairs, by = c("CLASS", "SUBCLASS"))
150155
data.table::setorder(landiq, parcel_id)
@@ -154,6 +159,28 @@ load_landiq_season2_identity <- function(year, landiq_crops, cropcode_csv) {
154159
CLASS,
155160
SUBCLASS,
156161
PFT,
157-
SPECOND
162+
SPECOND,
163+
COUNTY
158164
)]
159165
}
166+
167+
# Season-2 identity year -> year+1 (shared by harvest clearing and YP planting).
168+
load_landiq_season2_lookahead <- function(year, paths) {
169+
yr <- as.integer(year)
170+
prior <- load_landiq_season2_identity(yr, paths$landiq_crops, paths$cropcode_csv)
171+
curr <- load_landiq_season2_identity(yr + 1L, paths$landiq_crops, paths$cropcode_csv)
172+
if (is.null(prior) || is.null(curr)) {
173+
return(NULL)
174+
}
175+
data.table::setnames(
176+
prior,
177+
c("CLASS", "SUBCLASS", "PFT", "SPECOND", "COUNTY"),
178+
c("prior_CLASS", "prior_SUBCLASS", "prior_PFT", "prior_SPECOND", "prior_COUNTY")
179+
)
180+
data.table::setnames(
181+
curr,
182+
c("CLASS", "SUBCLASS", "PFT", "SPECOND", "COUNTY"),
183+
c("curr_CLASS", "curr_SUBCLASS", "curr_PFT", "curr_SPECOND", "curr_COUNTY")
184+
)
185+
merge(prior, curr, by = "parcel_id", all.x = TRUE)
186+
}

modules/data.remote/inst/ccmmf/events/R/planting_events.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ build_planting_events <- function(year, out_dir, matched_dir) {
5757
dt[, coarse_root_n_kg_m2 := as.numeric(N_COARSEROOT)]
5858
if ("PFT" %in% names(dt)) {
5959
pft_l <- tolower(trimws(as.character(dt$PFT)))
60-
dt <- dt[!pft_l %in% c("hay", "woody", "other")]
60+
dt <- dt[!pft_l %in% c("hay", "other")]
6161
}
6262
dt <- keep_event_columns(dt, .planting_event_cols)
6363
if (nrow(dt)) {

modules/data.remote/inst/ccmmf/traits/apply_harvest.R

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -167,28 +167,15 @@ build_woody_destructive_from_transition <- function(year, matched, pool_env, lk,
167167
yr, " -> ", next_yr, " (CLASS-level)"
168168
)
169169

170-
prior <- load_landiq_season2_identity(yr, paths$landiq_crops, paths$cropcode_csv)
171-
curr <- load_landiq_season2_identity(next_yr, paths$landiq_crops, paths$cropcode_csv)
172-
if (is.null(prior) || is.null(curr)) {
170+
trans <- load_landiq_season2_lookahead(yr, paths)
171+
if (is.null(trans)) {
173172
message(
174173
" Skip woody destructive: need season-2 LandIQ for both ",
175174
yr, " and ", next_yr, " (re-run prior year after new LandIQ year exists)."
176175
)
177176
return(NULL)
178177
}
179178

180-
data.table::setnames(
181-
prior,
182-
c("CLASS", "SUBCLASS", "PFT", "SPECOND"),
183-
c("prior_CLASS", "prior_SUBCLASS", "prior_PFT", "prior_SPECOND")
184-
)
185-
data.table::setnames(
186-
curr,
187-
c("CLASS", "SUBCLASS", "PFT", "SPECOND"),
188-
c("curr_CLASS", "curr_SUBCLASS", "curr_PFT", "curr_SPECOND")
189-
)
190-
191-
trans <- merge(prior, curr, by = "parcel_id", all.x = TRUE)
192179
prior_mature <- tolower(trimws(as.character(trans$prior_PFT))) == "woody" &
193180
!(toupper(trimws(as.character(trans$prior_CLASS))) == "YP" |
194181
toupper(trimws(as.character(trans$prior_SPECOND))) == "Y")

modules/data.remote/inst/ccmmf/traits/apply_planting.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ matched <- load_matched_for_events(
4848
year_arg, paths$matched_dir, run_planting = TRUE
4949
)
5050

51-
lai_dt <- build_planting_lai_table(matched, pool$pool_env)
51+
lai_dt <- build_planting_lai_table(matched, pool$pool_env, year_arg, paths)
5252
message("[lai] ", nrow(lai_dt), " rows in memory (not written)")
5353
if (nrow(lai_dt)) {
5454
print(lai_dt[, .N, by = .(PFT, lai_source)])

modules/data.remote/inst/ccmmf/traits/planting_apply.R

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,65 @@
11
# Shared EVI -> LAI and LAI -> C/N tables for planting.
22
# CLI: apply_planting.R
33
# make_events_statewide planting reads assigned_year=Y_planting.parquet
4-
# (does not recompute). Hay and woody are not planted here.
4+
# (does not recompute). Hay and mature woody are not planted here; YP is.
5+
6+
# Young perennial (CLASS=YP) has no crop subclass, so planting traits must come
7+
# from an orchard/vineyard code (D, C, or V).
8+
resolve_yp_planting_codes <- function(matched, year, paths) {
9+
idx <- which(toupper(trimws(as.character(matched$landiq_CLASS))) == "YP")
10+
if (!length(idx)) return(matched)
11+
12+
# Load existing transition CSVs (does not rebuild them).
13+
gf <- trimws(Sys.getenv(
14+
"LANDIQ_GAPFILL_ROOT", file.path(Sys.getenv("CCMMF_CODE"), "landiq-gapfill")
15+
))
16+
source(file.path(gf, "scripts", "R", "county_transition.R"), local = TRUE)
17+
state_csv <- file.path(dirname(paths$cropcode_csv), "state_transition_matrix.csv")
18+
ag <- rownames(read.csv(state_csv, row.names = 1, check.names = FALSE))
19+
mats <- load_county_transition_matrices(path_county_transition_dir(), ag)
20+
fb <- load_transition_matrix_csv(state_csv, ag)
21+
22+
ov <- c("D", "C", "V")
23+
# Argmax of YP -> D/C/V from the county matrix (or statewide fallback).
24+
prior_one <- function(county) {
25+
st <- county_matrix_stem(county)
26+
A <- if (!is.na(st) && nzchar(st) && st %in% names(mats)) mats[[st]] else fb
27+
p <- as.numeric(A["YP", ov])
28+
names(p) <- ov
29+
p[!is.finite(p)] <- 0
30+
if (!any(p > 0)) "D" else names(p)[which.max(p)]
31+
}
32+
33+
cls <- character(length(idx))
34+
sub <- rep("**", length(idx))
35+
county <- rep(NA_character_, length(idx))
36+
37+
# 1) Look-ahead: season-2 identity year -> year+1 (same helper as harvest clearing).
38+
tr <- load_landiq_season2_lookahead(year, paths)
39+
if (!is.null(tr)) {
40+
j <- match(as.character(matched$parcel_id[idx]), as.character(tr$parcel_id))
41+
hit <- !is.na(j)
42+
county[hit] <- as.character(tr$prior_COUNTY[j[hit]])
43+
cc <- toupper(trimws(as.character(tr$curr_CLASS[j])))
44+
# Only accept a mature orchard/vineyard destination.
45+
ok <- hit & cc %in% ov
46+
cls[ok] <- cc[ok]
47+
s <- as.character(tr$curr_SUBCLASS[j[ok]])
48+
s[is.na(s) | trimws(s) %in% c("", "**")] <- "**"
49+
sub[ok] <- s
50+
}
51+
52+
# 2) Still YP / no usable next class -> county (or state) transition prior.
53+
need <- !nzchar(cls)
54+
cls[need] <- vapply(county[need], prior_one, character(1))
55+
message(
56+
"[planting] YP trait codes: ", length(idx),
57+
" (look-ahead=", sum(!need), ", county prior=", sum(need), ")"
58+
)
59+
# Overwrite YP with the resolved D/C/V code used by the planting lookup.
60+
matched[idx, `:=`(landiq_CLASS = cls, landiq_SUBCLASS = sub)]
61+
matched
62+
}
563

664
planting_keep_dated_crop_rows <- function(matched) {
765
if (!"planting_date_str" %in% names(matched)) {
@@ -11,9 +69,19 @@ planting_keep_dated_crop_rows <- function(matched) {
1169
keep <- !is.na(d) & nzchar(d) & d != "NA"
1270
matched <- matched[keep]
1371
pft_l <- tolower(trimws(as.character(matched$landiq_PFT)))
14-
# Annual planting only (row, rice). Hay and woody are not planted each year;
15-
# they get phenology + harvest instead. YP OGI stays on the overlay.
16-
matched <- matched[!pft_l %in% c("other", "woody", "hay")]
72+
specond <- if ("landiq_SPECOND" %in% names(matched)) {
73+
as.character(matched$landiq_SPECOND)
74+
} else {
75+
rep(NA_character_, nrow(matched))
76+
}
77+
# Plant annuals plus young woody. CLASS=YP needs trait resolve below;
78+
# SPECOND=Y already carries a D/C/V class and uses those traits as-is.
79+
# Mature woody and hay stay phenology + harvest only.
80+
young <- pft_l == "woody" & (
81+
toupper(trimws(as.character(matched$landiq_CLASS))) == "YP" |
82+
toupper(trimws(specond)) == "Y"
83+
)
84+
matched <- matched[pft_l %in% c("row", "rice") | young]
1785
matched
1886
}
1987

@@ -92,8 +160,12 @@ lookup_planting_lai_fallback <- function(class, pft, fb) {
92160
}
93161

94162
# Overlay rows -> LAI via compute_lai_from_mslsp (or CLASS/PFT fallback).
95-
build_planting_lai_table <- function(matched, pool_env) {
163+
# Keep filter first (while CLASS is still YP), then rewrite YP to D/C/V.
164+
build_planting_lai_table <- function(matched, pool_env, year = NULL, paths = NULL) {
96165
matched <- planting_keep_dated_crop_rows(matched)
166+
if (!is.null(year) && !is.null(paths)) {
167+
matched <- resolve_yp_planting_codes(matched, year, paths)
168+
}
97169
message("[lai] Crop rows with planting date: ", nrow(matched))
98170
lai_fb <- planting_lai_fallbacks(matched, pool_env)
99171
message(

0 commit comments

Comments
 (0)