Skip to content

[Crop and Soil][Manure] Reflecting dailyspread in CS#2623

Open
matthew7838 wants to merge 187 commits into
devfrom
cs-dailyspread
Open

[Crop and Soil][Manure] Reflecting dailyspread in CS#2623
matthew7838 wants to merge 187 commits into
devfrom
cs-dailyspread

Conversation

@matthew7838

@matthew7838 matthew7838 commented Oct 21, 2025

Copy link
Copy Markdown
Collaborator

Context

Issue(s) closed by this pull request: closes #2494

What

This branch adds a new daily_spread block to a field’s manure schedule and wires it into the simulation as a daily manure request. When the field is created, FieldManager still builds the normal scheduled manure events, but it also pulls out the raw daily_spread settings and stores them on the Field. Then, each day, the field checks whether daily spreading is enabled and, if so, creates a synthetic ManureEvent for that day.

That synthetic event uses the daily_spread nutrient settings to set the request amount. In the current code, only nitrogen_spread_amount and phosphorus_spread_amount are actually used, each capped by max_nitrogen and max_phosphorus. The event is tagged as is_daily_spread, and when it is converted into a NutrientRequest, that tag becomes use_daily_spread_source=True. That flag is what changes the downstream logic.

In ManureManager, requests marked as daily spread do not use the normal shared manure pool first. Instead, the manager splits storages into DailySpread versus regular storages, tries to fulfill the request only from the DailySpread processors, and removes nutrients from their available_for_field_application stream. The DailySpread storage itself was changed so that after it processes manure for the day, it keeps that manure in a same-day available bucket rather than passing it along like normal stored manure.

So the overall logic is: input daily_spread settings on the field, turn them into a daily manure event, convert that into a nutrient request marked as daily-spread-sourced, and fulfill that request from DailySpread storage only. One important gap is that the schema includes potassium_spread_amount and max_potassium, but the current request flow never uses them, because the event and nutrient-request objects only carry nitrogen and phosphorus.
Screenshot 2026-06-18 at 6 34 21 PM

Why

How

Test plan

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1678
Mypy errors on dev branch: 1678
No difference in error counts

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

github-actions Bot commented Nov 6, 2025

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1710
Mypy errors on dev branch: 1710
No difference in error counts

@github-actions

github-actions Bot commented Nov 6, 2025

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1710
Mypy errors on dev branch: 1710
No difference in error counts

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1709
Mypy errors on dev branch: 1709
No difference in error counts

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1677
Mypy errors on dev branch: 1677
No difference in error counts

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1684
Mypy errors on dev branch: 1684
No difference in error counts

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: %

Mypy errors on cs-dailyspread branch: 1685
Mypy errors on dev branch: 1684
1 more errors on cs-dailyspread branch

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Please update the changelog. This PR cannot be merged until changelog.md is updated.
🚨 Flake8 linting errors were found. Please fix the linting issues.
🚨 Some tests have failed.

matthew7838 and others added 4 commits February 24, 2026 21:11
# Conflicts:
#	RUFAS/biophysical/field/field/field.py
#	RUFAS/biophysical/manure/manure_manager.py
#	RUFAS/biophysical/manure/storage/daily_spread.py
#	RUFAS/data_structures/events.py
#	RUFAS/data_structures/manure_to_crop_soil_connection.py
#	input/metadata/properties/default.json
#	tests/test_biophysical/test_manure/test_manure_manager/test_manure_manager.py
#	tests/test_biophysical/test_manure/test_storage/test_daily_spread.py
@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1140
Mypy errors on dev branch: 1144
4 fewer errors on cs-dailyspread branch

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unauthorized changes detected in protected files. Please remove these changes if they are not intended.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1140
Mypy errors on dev branch: 1144
4 fewer errors on cs-dailyspread branch

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unauthorized changes detected in protected files. Please remove these changes if they are not intended.

@ew3361zh ew3361zh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close to approving, just needed to run a few tests to see what happens when you try to run daily spread with no manure module (e.g. field_only simulation type).

Great work and thanks for doing a walk through with us in the dev team wt.

I do think a diagram of the logic would be helpful here in terms of how manure requests are fulfilled based on whether daily spread is happening and also what the nutrient request fulfillment looks like from the manure module when there are BOTH daily spread requests and "normal" manure schedule request events.

Comment on lines +3005 to +3014
"is_daily_spreading": {
"type": "bool",
"description": "Whether this field uses daily spread manure applications.",
"default": false
},
"spread_all_available_manure": {
"type": "bool",
"description": "If true, spread all manure available in DailySpread processors that day, ignoring nutrient targets, caps, manure type, and supplemental manure settings.",
"default": false
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the hierarchy that is_daily_spreading being false will override all the other values here and render them irrelevant?
Just want to be sure because it seems like spread_all_available_manure could potentially be of interest to non-daily-spread manure applications.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that spread-all itself is a subtype of daily-spread. Spread all means spread all daily spread processors; therefore, I think the case you mentioned isn't valid.

Comment thread input/metadata/properties/default.json Outdated
Comment thread RUFAS/biophysical/field/field/field.py Outdated
Comment thread RUFAS/biophysical/field/manager/field_manager.py Outdated
Comment on lines +939 to +962
if request.use_daily_spread_source:
manure_source = "daily_spread"
daily_spread_storages, _ = self._split_storages_by_daily_spread()
request_result, is_nutrient_request_fulfilled = self._handle_nutrient_request_for_storages(
request=request, storages=daily_spread_storages
)
if request_result is not None:
self._remove_nutrients_from_storage(
request_result,
request.manure_type,
include_daily_spread=True,
update_nutrient_manager_pool=False,
)
else:
manure_source = "stored_manure"
request_result, is_nutrient_request_fulfilled = self._manure_nutrient_manager.handle_nutrient_request(
request
)
if request_result is not None:
self._remove_nutrients_from_storage(
request_result,
request.manure_type,
include_daily_spread=False,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight preference for this to reduce duplication and it's a little clearer to me but not a required change for me.

Suggested change
if request.use_daily_spread_source:
manure_source = "daily_spread"
daily_spread_storages, _ = self._split_storages_by_daily_spread()
request_result, is_nutrient_request_fulfilled = self._handle_nutrient_request_for_storages(
request=request, storages=daily_spread_storages
)
if request_result is not None:
self._remove_nutrients_from_storage(
request_result,
request.manure_type,
include_daily_spread=True,
update_nutrient_manager_pool=False,
)
else:
manure_source = "stored_manure"
request_result, is_nutrient_request_fulfilled = self._manure_nutrient_manager.handle_nutrient_request(
request
)
if request_result is not None:
self._remove_nutrients_from_storage(
request_result,
request.manure_type,
include_daily_spread=False,
)
if request.use_daily_spread_source:
manure_source = "daily_spread"
daily_spread_storages, _ = self._split_storages_by_daily_spread()
request_result, is_nutrient_request_fulfilled = self._handle_nutrient_request_for_storages(
request=request,
storages=daily_spread_storages,
)
include_daily_spread = True
update_nutrient_manager_pool = False
else:
manure_source = "stored_manure"
request_result, is_nutrient_request_fulfilled = self._manure_nutrient_manager.handle_nutrient_request(
request
)
include_daily_spread = False
update_nutrient_manager_pool = True
if request_result is not None:
self._remove_nutrients_from_storage(
request_result,
request.manure_type,
include_daily_spread=include_daily_spread,
update_nutrient_manager_pool=update_nutrient_manager_pool,
)

Comment on lines 1063 to 1072
@@ -984,17 +1071,114 @@ def _remove_nutrients_from_storage(self, results: NutrientRequestResults, manure
"bedding_non_degradable_volatile_solids",
]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for ways to reduce the size of this function and I wonder if we could move these to be a class/file-level constant?

Comment on lines +1025 to +1030
storage_manure_type = STORAGE_CLASS_TO_TYPE.get(type(storage))
if storage_manure_type != manure_type:
continue
source_stream = (
storage.available_for_field_application if isinstance(storage, DailySpread) else storage.stored_manure
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extract to a helper function since it's the same code here and in the other loop?

non_daily_storages.append(processor)
return daily_spread_storages, non_daily_storages

def _handle_nutrient_request_for_storages(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is just for daily_spread related requests (as it seems like it may be from the one place it's used), should we add that either into the function name or the docstrings for clarity?

Comment on lines +1119 to +1130
for storage in storages:
storage_manure_type = STORAGE_CLASS_TO_TYPE.get(type(storage))
if storage_manure_type != request.manure_type:
continue
source_stream = (
storage.available_for_field_application if isinstance(storage, DailySpread) else storage.stored_manure
)
nitrogen += source_stream.nitrogen
phosphorus += source_stream.phosphorus
potassium += source_stream.potassium
total_manure_mass += source_stream.mass
dry_matter += source_stream.total_solids

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This same logic is repeated above, I think it could be extracted into a helper function.

Comment thread RUFAS/data_structures/manure_to_crop_soil_connection.py Outdated

@ew3361zh ew3361zh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested a daily spread manure schedule with a simulation type that had no manure module and it didn't crash which I think is good (field-only). I can't speak to the scientific outcomes for manure splitting and whether it's retrieving manure and dividing it between the existing manure pools as intended so will leave that to the SMEs but code-wise this seems to be working as designed and no further feedback from me.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1134
Mypy errors on dev branch: 1138
4 fewer errors on cs-dailyspread branch

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unauthorized changes detected in protected files. Please remove these changes if they are not intended.

@github-actions

Copy link
Copy Markdown
Contributor

Current Coverage: 99%

Mypy errors on cs-dailyspread branch: 1134
Mypy errors on dev branch: 1138
4 fewer errors on cs-dailyspread branch

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unauthorized changes detected in protected files. Please remove these changes if they are not intended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Crop & soil] Consider how daily spread should be reflected in C&S

4 participants