pyholos 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyholos might be problematic. Click here for more details.

Files changed (55) hide show
  1. pyholos/__init__.py +0 -0
  2. pyholos/common.py +141 -0
  3. pyholos/common2.py +157 -0
  4. pyholos/components/__init__.py +0 -0
  5. pyholos/components/animals/__init__.py +0 -0
  6. pyholos/components/animals/beef.py +766 -0
  7. pyholos/components/animals/common.py +2301 -0
  8. pyholos/components/animals/dairy.py +341 -0
  9. pyholos/components/animals/sheep.py +412 -0
  10. pyholos/components/common.py +170 -0
  11. pyholos/components/land_management/__init__.py +0 -0
  12. pyholos/components/land_management/carbon/__init__.py +0 -0
  13. pyholos/components/land_management/carbon/climate.py +863 -0
  14. pyholos/components/land_management/carbon/management.py +21 -0
  15. pyholos/components/land_management/carbon/relative_biomass_information.py +410 -0
  16. pyholos/components/land_management/carbon/tillage.py +88 -0
  17. pyholos/components/land_management/common.py +220 -0
  18. pyholos/components/land_management/crop.py +1233 -0
  19. pyholos/components/land_management/field_system.py +458 -0
  20. pyholos/components/land_management/utils.py +66 -0
  21. pyholos/config.py +49 -0
  22. pyholos/core_constants.py +20 -0
  23. pyholos/defaults.py +116 -0
  24. pyholos/farm/__init__.py +0 -0
  25. pyholos/farm/enums.py +54 -0
  26. pyholos/farm/farm.py +101 -0
  27. pyholos/farm/farm_inputs.py +633 -0
  28. pyholos/farm/farm_settings.py +542 -0
  29. pyholos/launching.py +86 -0
  30. pyholos/postprocessing/__init__.py +0 -0
  31. pyholos/postprocessing/plots.py +164 -0
  32. pyholos/postprocessing/postprocessing.py +38 -0
  33. pyholos/resources/holos/Table_16_Livestock_Coefficients_BeefAndDairy_Cattle_Provider.csv +21 -0
  34. pyholos/resources/holos/Table_21_Average_Milk_Production_For_Dairy_Cows_By_Province.csv +23 -0
  35. pyholos/resources/holos/Table_22_Livestock_Coefficients_For_Sheep.csv +28 -0
  36. pyholos/resources/holos/Table_29_Percentage_Total_Manure_Produced_In_Systems.csv +56 -0
  37. pyholos/resources/holos/Table_30_Default_Bedding_Material_Composition_Provider.csv +28 -0
  38. pyholos/resources/holos/Table_50_Fuel_Energy_Requirement_Estimates_By_Region.csv +81 -0
  39. pyholos/resources/holos/Table_51_Herbicide_Energy_Requirement_Estimates_By_Region.csv +81 -0
  40. pyholos/resources/holos/Table_61_Fractions_of_dairy_cattle_N_volatilized.csv +25 -0
  41. pyholos/resources/holos/Table_62_Fractions_of_swine_N_volatilized.csv +25 -0
  42. pyholos/resources/holos/Table_6_Manure_Types_And_Default_Composition.csv +126 -0
  43. pyholos/resources/holos/Table_7_Relative_Biomass_Information.csv +112 -0
  44. pyholos/resources/holos/Table_9_Default_Values_For_Nitrogen_Lignin_In_Crops.csv +72 -0
  45. pyholos/resources/holos/Table_Tillage_Factor.csv +13 -0
  46. pyholos/resources/holos/feeds.csv +223 -0
  47. pyholos/resources/holos/main_tables.py +493 -0
  48. pyholos/resources/holos/small_area_yields.csv +167159 -0
  49. pyholos/resources/soil_landscapes_of_canada_v3r2.zip +0 -0
  50. pyholos/soil.py +439 -0
  51. pyholos/utils.py +83 -0
  52. pyholos-0.0.1.dist-info/METADATA +16 -0
  53. pyholos-0.0.1.dist-info/RECORD +55 -0
  54. pyholos-0.0.1.dist-info/WHEEL +4 -0
  55. pyholos-0.0.1.dist-info/licenses/LICENSE +677 -0
@@ -0,0 +1,633 @@
1
+ from datetime import date
2
+ from typing import ClassVar, Generator, Union
3
+ from uuid import UUID, uuid4
4
+
5
+ from pydantic import (BaseModel, Field, NonNegativeFloat, NonNegativeInt,
6
+ PositiveFloat, PositiveInt, confloat, conint, conlist,
7
+ field_validator)
8
+
9
+ from pyholos.components.animals import beef, dairy, sheep
10
+ from pyholos.components.animals.common import (BeddingMaterialType, Diet,
11
+ DietAdditiveType, HousingType,
12
+ ManureAnimalSourceTypes,
13
+ ManureLocationSourceType,
14
+ ManureStateType, Milk,
15
+ ProductionStage,
16
+ get_manure_emission_factors)
17
+ from pyholos.components.land_management.carbon.relative_biomass_information import (
18
+ RelativeBiomassInformationData, get_relative_biomass_information_data,
19
+ parse_table_7)
20
+ from pyholos.components.land_management.common import (FertilizerBlends,
21
+ HarvestMethod,
22
+ IrrigationType,
23
+ ManureApplicationTypes,
24
+ TillageType)
25
+ from pyholos.components.land_management.crop import CropType
26
+ from pyholos.components.land_management.field_system import CropViewItem
27
+ from pyholos.core_constants import CoreConstants
28
+ from pyholos.common2 import CanadianProvince
29
+ from pyholos.soil import SoilFunctionalCategory, SoilTexture
30
+ from pyholos.utils import concat_lists
31
+
32
+ AnimalComponent = Union[
33
+ beef.Bulls, beef.ReplacementHeifers, beef.Cows, beef.Calves,
34
+ beef.FinishingHeifers, beef.FinishingSteers,
35
+ beef.BackgrounderHeifer, beef.BackgrounderSteer,
36
+ dairy.DairyHeifers, dairy.DairyLactatingCow, dairy.DairyCalves, dairy.DairyDryCow,
37
+ sheep.SheepFeedlot, sheep.Rams, sheep.Ewes, sheep.Lambs
38
+ ]
39
+ type ManagementPeriods = list[BeefManagementPeriod | DairyManagementPeriod | SheepManagementPeriod]
40
+
41
+ TypeWaterData = confloat(strict=True, ge=0, allow_inf_nan=False)
42
+ TypeTemperatureData = confloat(strict=True, allow_inf_nan=False)
43
+
44
+
45
+ class WeatherData(BaseModel):
46
+ """A class that holds daily values for precipitation (mm), potential_evapotranspiration (mm) and temperature (°C)
47
+ for one year.
48
+ """
49
+ spec_daily_data: ClassVar = dict(min_length=365, max_length=366)
50
+
51
+ year: int = Field(gt=CoreConstants.MinimumYear)
52
+ precipitation: conlist(item_type=TypeWaterData, **spec_daily_data)
53
+ potential_evapotranspiration: conlist(item_type=TypeWaterData, **spec_daily_data)
54
+ temperature: conlist(item_type=TypeTemperatureData, **spec_daily_data)
55
+
56
+
57
+ class WeatherSummary(BaseModel):
58
+ spec_monthly_data: ClassVar = dict(min_length=12, max_length=12)
59
+
60
+ year: int = Field(gt=1970)
61
+ mean_annual_precipitation: TypeWaterData
62
+ mean_annual_temperature: TypeTemperatureData
63
+ mean_annual_evapotranspiration: TypeWaterData
64
+ growing_season_precipitation: TypeWaterData
65
+ growing_season_evapotranspiration: TypeWaterData
66
+ monthly_precipitation: conlist(item_type=TypeWaterData, **spec_monthly_data)
67
+ monthly_potential_evapotranspiration: conlist(item_type=TypeWaterData, **spec_monthly_data)
68
+ monthly_temperature: conlist(item_type=TypeTemperatureData, **spec_monthly_data)
69
+
70
+
71
+ class BeefManagementPeriod(BaseModel):
72
+ name: str = Field(min_length=1)
73
+ start_date: date
74
+ days: conint(gt=0)
75
+ group_pairing_number: conint(ge=0)
76
+ number_of_animals: conint(ge=0)
77
+ production_stage: ProductionStage
78
+ number_of_young_animals: conint(ge=0)
79
+ is_milk_fed_only: bool
80
+ diet: Diet
81
+ housing_type: HousingType
82
+ manure_handling_system: ManureStateType
83
+ weather_summary: WeatherSummary
84
+ start_weight: confloat(ge=0, allow_inf_nan=False) = None
85
+ end_weight: confloat(ge=0, allow_inf_nan=False) = None
86
+ diet_additive_type: DietAdditiveType = DietAdditiveType.NONE
87
+ bedding_material_type: BeddingMaterialType = BeddingMaterialType.straw
88
+
89
+
90
+ class DairyManagementPeriod(BaseModel):
91
+ name: str = Field(min_length=1)
92
+ start_date: date
93
+ days: conint(gt=0)
94
+ group_pairing_number: conint(ge=0)
95
+ number_of_animals: conint(ge=0)
96
+ production_stage: ProductionStage
97
+ number_of_young_animals: conint(ge=0)
98
+ milk_data: Milk
99
+ diet: Diet
100
+ housing_type: HousingType
101
+ manure_handling_system: ManureStateType
102
+ weather_summary: WeatherSummary
103
+ start_weight: confloat(ge=0, allow_inf_nan=False) = None
104
+ end_weight: confloat(ge=0, allow_inf_nan=False) = None
105
+ diet_additive_type: DietAdditiveType = DietAdditiveType.NONE
106
+ bedding_material_type: BeddingMaterialType = BeddingMaterialType.straw
107
+
108
+
109
+ class SheepManagementPeriod(BaseModel):
110
+ name: str = Field(min_length=1)
111
+ start_date: date
112
+ days: conint(gt=0)
113
+ group_pairing_number: conint(ge=0)
114
+ number_of_animals: conint(ge=0)
115
+ production_stage: ProductionStage
116
+ number_of_young_animals: conint(ge=0)
117
+ diet: Diet
118
+ housing_type: HousingType
119
+ manure_handling_system: ManureStateType
120
+ weather_summary: WeatherSummary
121
+ start_weight: confloat(ge=0, allow_inf_nan=False) = None
122
+ end_weight: confloat(ge=0, allow_inf_nan=False) = None
123
+ diet_additive_type: DietAdditiveType = DietAdditiveType.NONE
124
+ bedding_material_type: BeddingMaterialType = BeddingMaterialType.straw
125
+
126
+
127
+ class AnimalInputBase(BaseModel):
128
+ def __iter__(self):
129
+ for k, v in self.__dict__.items():
130
+ if v is not None:
131
+ yield k, v
132
+
133
+ def _filter_inputs(
134
+ self,
135
+ animal_groups: list[list[str]]
136
+ ) -> list[list[str]]:
137
+ res = []
138
+ for component_type_animals in animal_groups:
139
+ animal_data = [s for s in component_type_animals if getattr(self, s) is not None]
140
+ if len(animal_data) > 0:
141
+ res.append(animal_data)
142
+ return res
143
+
144
+ def filter_inputs(self) -> list[list[str]]:
145
+ pass
146
+
147
+ @staticmethod
148
+ def map_component(**kwargs):
149
+ pass
150
+
151
+ @staticmethod
152
+ def _create_component(**kwargs):
153
+ pass
154
+
155
+ def create_components(
156
+ self,
157
+ province: CanadianProvince,
158
+ soil_texture: SoilTexture,
159
+ ) -> list[list[AnimalComponent]]:
160
+ res = []
161
+ for non_empty_entry in self.filter_inputs():
162
+ animal_components = []
163
+ for animal_type in non_empty_entry:
164
+ management_periods = getattr(self, animal_type)
165
+ component_type = self.map_component(component_name=animal_type)
166
+ animal_components.append(
167
+ [self._create_component(
168
+ province=province,
169
+ soil_texture=soil_texture,
170
+ component_class=component_type,
171
+ management_period=management_period)
172
+ for management_period in management_periods])
173
+
174
+ res.append(concat_lists(*animal_components))
175
+ return res
176
+
177
+
178
+ class BeefCattleInput(AnimalInputBase):
179
+ Bulls: ManagementPeriods = None
180
+ ReplacementHeifers: ManagementPeriods = None
181
+ Cows: ManagementPeriods = None
182
+ Calves: ManagementPeriods = None
183
+ FinishingHeifers: ManagementPeriods = None
184
+ FinishingSteers: ManagementPeriods = None
185
+ BackgrounderHeifer: ManagementPeriods = None
186
+ BackgrounderSteer: ManagementPeriods = None
187
+
188
+ component_types: ClassVar = Union[
189
+ beef.Bulls,
190
+ beef.ReplacementHeifers,
191
+ beef.Cows,
192
+ beef.Calves,
193
+ beef.FinishingHeifers,
194
+ beef.FinishingSteers,
195
+ beef.BackgrounderHeifer,
196
+ beef.BackgrounderSteer]
197
+
198
+ def filter_inputs(self) -> list[list[str]]:
199
+ return self._filter_inputs(animal_groups=[
200
+ ["Bulls", "ReplacementHeifers", "Cows", "Calves"],
201
+ ["FinishingHeifers", "FinishingSteers"],
202
+ ["BackgrounderHeifer", "BackgrounderSteer"]
203
+ ])
204
+
205
+ @staticmethod
206
+ def map_component(
207
+ component_name: str
208
+ ) -> component_types:
209
+
210
+ match component_name:
211
+ case 'Bulls':
212
+ res = beef.Bulls
213
+ case 'ReplacementHeifers':
214
+ res = beef.ReplacementHeifers
215
+ case 'Cows':
216
+ res = beef.Cows
217
+ case 'Calves':
218
+ res = beef.Calves
219
+ case 'FinishingHeifers':
220
+ res = beef.FinishingHeifers
221
+ case 'FinishingSteers':
222
+ res = beef.FinishingSteers
223
+ case 'BackgrounderHeifer':
224
+ res = beef.BackgrounderHeifer
225
+ case 'BackgrounderSteer':
226
+ res = beef.BackgrounderSteer
227
+ case _:
228
+ raise ValueError(f'Unrecognized component name "({component_name})."')
229
+
230
+ return res
231
+
232
+ @staticmethod
233
+ def _create_component(
234
+ province: CanadianProvince,
235
+ soil_texture: SoilTexture,
236
+ component_class: [component_types],
237
+ management_period: BeefManagementPeriod
238
+ ) -> component_types:
239
+ return component_class(
240
+ management_period_name=management_period.name,
241
+ management_period_start_date=management_period.start_date,
242
+ management_period_days=management_period.days,
243
+ group_pairing_number=management_period.group_pairing_number,
244
+ production_stage=management_period.production_stage,
245
+ number_of_animals=management_period.number_of_animals,
246
+ number_of_young_animals=management_period.number_of_young_animals,
247
+ is_milk_fed_only=management_period.is_milk_fed_only,
248
+ milk_data=Milk(),
249
+ diet=management_period.diet,
250
+ housing_type=management_period.housing_type,
251
+ manure_handling_system=management_period.manure_handling_system,
252
+ manure_emission_factors=get_manure_emission_factors(
253
+ animal_type=component_class.animal_type,
254
+ year=management_period.weather_summary.year,
255
+ manure_state_type=management_period.manure_handling_system,
256
+ mean_annual_precipitation=management_period.weather_summary.mean_annual_precipitation,
257
+ mean_annual_temperature=management_period.weather_summary.mean_annual_temperature,
258
+ mean_annual_evapotranspiration=management_period.weather_summary.mean_annual_evapotranspiration,
259
+ growing_season_precipitation=management_period.weather_summary.growing_season_precipitation,
260
+ growing_season_evapotranspiration=management_period.weather_summary.growing_season_evapotranspiration,
261
+ province=province,
262
+ soil_texture=soil_texture),
263
+ start_weight=management_period.start_weight,
264
+ end_weight=management_period.end_weight,
265
+ diet_additive_type=management_period.diet_additive_type,
266
+ bedding_material_type=management_period.bedding_material_type
267
+ )
268
+
269
+
270
+ class DairyCattleInput(AnimalInputBase):
271
+ Heifers: ManagementPeriods = None
272
+ LactatingCow: ManagementPeriods = None
273
+ Calves: ManagementPeriods = None
274
+ DryCow: ManagementPeriods = None
275
+
276
+ component_types: ClassVar = Union[
277
+ dairy.DairyHeifers,
278
+ dairy.DairyLactatingCow,
279
+ dairy.DairyCalves,
280
+ dairy.DairyDryCow]
281
+
282
+ @staticmethod
283
+ def map_component(
284
+ component_name: str
285
+ ) -> component_types:
286
+
287
+ match component_name:
288
+ case 'Heifers':
289
+ res = dairy.DairyHeifers
290
+ case 'LactatingCow':
291
+ res = dairy.DairyLactatingCow
292
+ case 'Calves':
293
+ res = dairy.DairyCalves
294
+ case 'DryCow':
295
+ res = dairy.DairyDryCow
296
+ case _:
297
+ raise ValueError(f'Unrecognized component name "({component_name})."')
298
+
299
+ return res
300
+
301
+ def filter_inputs(self) -> list[list[str]]:
302
+ return self._filter_inputs(animal_groups=[
303
+ ["Heifers", "LactatingCow", "Calves", "DryCow"]
304
+ ])
305
+
306
+ @staticmethod
307
+ def _create_component(
308
+ province: CanadianProvince,
309
+ soil_texture: SoilTexture,
310
+ component_class: [component_types],
311
+ management_period: DairyManagementPeriod
312
+ ) -> component_types:
313
+ return component_class(
314
+ management_period_name=management_period.name,
315
+ management_period_start_date=management_period.start_date,
316
+ management_period_days=management_period.days,
317
+ group_pairing_number=management_period.group_pairing_number,
318
+ number_of_animals=management_period.number_of_animals,
319
+ production_stage=management_period.production_stage,
320
+ number_of_young_animals=management_period.number_of_young_animals,
321
+ milk_data=Milk(),
322
+ diet=management_period.diet,
323
+ housing_type=management_period.housing_type,
324
+ manure_handling_system=management_period.manure_handling_system,
325
+ diet_additive_type=management_period.diet_additive_type,
326
+ bedding_material_type=management_period.bedding_material_type,
327
+
328
+ manure_emission_factors=get_manure_emission_factors(
329
+ manure_state_type=management_period.manure_handling_system,
330
+ mean_annual_precipitation=management_period.weather_summary.mean_annual_precipitation,
331
+ mean_annual_temperature=management_period.weather_summary.mean_annual_temperature,
332
+ mean_annual_evapotranspiration=management_period.weather_summary.mean_annual_evapotranspiration,
333
+ growing_season_precipitation=management_period.weather_summary.growing_season_precipitation,
334
+ growing_season_evapotranspiration=management_period.weather_summary.growing_season_evapotranspiration,
335
+ animal_type=component_class.animal_group.type,
336
+ province=province,
337
+ year=management_period.weather_summary.year,
338
+ soil_texture=soil_texture)
339
+ )
340
+
341
+
342
+ class SheepFlockInput(AnimalInputBase):
343
+ SheepFeedlot: ManagementPeriods = None
344
+ Rams: ManagementPeriods = None
345
+ Ewes: ManagementPeriods = None
346
+ Lambs: ManagementPeriods = None
347
+
348
+ component_types: ClassVar = Union[
349
+ sheep.SheepFeedlot,
350
+ sheep.Rams,
351
+ sheep.Ewes,
352
+ sheep.Lambs]
353
+
354
+ @staticmethod
355
+ def map_component(
356
+ component_name: str
357
+ ) -> component_types:
358
+
359
+ match component_name:
360
+ case 'SheepFeedlot':
361
+ res = sheep.SheepFeedlot
362
+ case 'Rams':
363
+ res = sheep.Rams
364
+ case 'Ewes':
365
+ res = sheep.Ewes
366
+ case 'Lambs':
367
+ res = sheep.Lambs
368
+ case _:
369
+ raise ValueError(f'Unrecognized component name "({component_name})."')
370
+
371
+ return res
372
+
373
+ def filter_inputs(self) -> list[list[str]]:
374
+ return self._filter_inputs(animal_groups=[
375
+ ["SheepFeedlot"],
376
+ ["Rams"],
377
+ ["Ewes", "Lambs"],
378
+ ])
379
+
380
+ @staticmethod
381
+ def _create_component(
382
+ province: CanadianProvince,
383
+ soil_texture: SoilTexture,
384
+ component_class: [component_types],
385
+ management_period: SheepManagementPeriod
386
+ ) -> component_types:
387
+ return component_class(
388
+ management_period_name=management_period.name,
389
+ management_period_start_date=management_period.start_date,
390
+ management_period_days=management_period.days,
391
+ group_pairing_number=management_period.group_pairing_number,
392
+ number_of_animals=management_period.number_of_animals,
393
+ production_stage=management_period.production_stage,
394
+ number_of_young_animals=management_period.number_of_young_animals,
395
+ diet=management_period.diet,
396
+ housing_type=management_period.housing_type,
397
+ manure_handling_system=management_period.manure_handling_system,
398
+ diet_additive_type=management_period.diet_additive_type,
399
+ bedding_material_type=management_period.bedding_material_type,
400
+
401
+ manure_emission_factors=get_manure_emission_factors(
402
+ manure_state_type=management_period.manure_handling_system,
403
+ mean_annual_precipitation=management_period.weather_summary.mean_annual_precipitation,
404
+ mean_annual_temperature=management_period.weather_summary.mean_annual_temperature,
405
+ mean_annual_evapotranspiration=management_period.weather_summary.mean_annual_evapotranspiration,
406
+ growing_season_precipitation=management_period.weather_summary.growing_season_precipitation,
407
+ growing_season_evapotranspiration=management_period.weather_summary.growing_season_evapotranspiration,
408
+ animal_type=component_class.animal_type,
409
+ province=province,
410
+ year=management_period.weather_summary.year,
411
+ soil_texture=soil_texture)
412
+ )
413
+
414
+
415
+ class FieldAnnualData(BaseModel):
416
+ name: str = Field(min_length=1)
417
+ field_area: PositiveFloat
418
+ weather_data: WeatherData
419
+ crop_type: CropType
420
+ crop_yield: NonNegativeFloat
421
+ crop_year: PositiveInt
422
+ under_sown_crops_used: bool
423
+ tillage_type: TillageType
424
+ harvest_method: HarvestMethod
425
+ nitrogen_fertilizer_rate: NonNegativeFloat = Field(default=0)
426
+ fertilizer_blend: FertilizerBlends
427
+ irrigation_type: IrrigationType = IrrigationType.RainFed
428
+ amount_of_irrigation: NonNegativeFloat = 0
429
+ number_of_pesticide_passes: NonNegativeInt = 0
430
+ amount_of_manure_applied: NonNegativeFloat = 0
431
+ manure_application_type: ManureApplicationTypes = ManureApplicationTypes.NotSelected
432
+ manure_animal_source_type: ManureAnimalSourceTypes = ManureAnimalSourceTypes.NotSelected
433
+ manure_state_type: ManureStateType = ManureStateType.not_selected
434
+ manure_location_source_type: ManureLocationSourceType = ManureLocationSourceType.NotSelected
435
+
436
+ year_in_perennial_stand: int = None
437
+ field_system_component_guid: UUID = None
438
+ current_year: int = None
439
+ relative_biomass_information_data: RelativeBiomassInformationData = None
440
+ province: CanadianProvince = None
441
+ clay_content: float = None
442
+ sand_content: float = None
443
+ organic_carbon_percentage: float = None
444
+ soil_top_layer_thickness: float = None
445
+ soil_functional_category: SoilFunctionalCategory = None
446
+ evapotranspiration: list[float] = None
447
+ precipitation: list[float] = None
448
+ temperature: list[float] = None
449
+
450
+ @field_validator('weather_data', mode='after')
451
+ @classmethod
452
+ def revalidate_weather_data(cls, value) -> WeatherData:
453
+ WeatherData(**value.model_dump())
454
+ return value
455
+
456
+
457
+ class FieldsInput(BaseModel):
458
+ fields: FieldAnnualData | list[FieldAnnualData] = None
459
+ table_7: ClassVar = parse_table_7()
460
+
461
+ @property
462
+ def fields_data(self) -> Generator[list[FieldAnnualData]] | Generator:
463
+ if self.fields is None:
464
+ return iter(())
465
+ else:
466
+ if not isinstance(self.fields, list):
467
+ self.fields = [self.fields]
468
+ for v in self.fields:
469
+ yield [v]
470
+
471
+ @staticmethod
472
+ def calc_year_in_perennial_stand(
473
+ crops: list[CropType]
474
+ ) -> list[int]:
475
+ v = 0
476
+ res = []
477
+ for i, crop in enumerate(crops):
478
+ v = (v + 1) if crop.is_perennial() else 0
479
+ res.append(v)
480
+ return res
481
+
482
+ @staticmethod
483
+ def calc_perennial_stand_lengths(
484
+ years_in_perennial_stand: list[int]
485
+ ) -> list[int]:
486
+ res = [max(1, years_in_perennial_stand[-1])]
487
+ for v in reversed(years_in_perennial_stand[:-1]):
488
+ res.append(max(v, res[-1]) if v != 0 else 1)
489
+ return list(reversed(res))
490
+
491
+ @staticmethod
492
+ def set_perennial_stand_id(
493
+ crops: list[CropType],
494
+ ) -> list[UUID]:
495
+ id_for_annual = UUID("00000000-0000-0000-0000-000000000000")
496
+ perennial_stand_id = None
497
+
498
+ crop_prev = None
499
+ res = []
500
+ for crop in crops:
501
+ if not crop.is_perennial():
502
+ perennial_stand_id = id_for_annual
503
+ else:
504
+ if crop != crop_prev:
505
+ perennial_stand_id = uuid4()
506
+ crop_prev = crop
507
+
508
+ res.append(perennial_stand_id)
509
+
510
+ return res
511
+
512
+ def _create_one_year_component(
513
+ self,
514
+ province: CanadianProvince,
515
+ clay_content: float,
516
+ sand_content: float,
517
+ organic_carbon_percentage: float,
518
+ soil_top_layer_thickness: float,
519
+ soil_functional_category: SoilFunctionalCategory,
520
+ perennial_stand_id: UUID,
521
+ field_system_component_guid: UUID,
522
+ perennial_stand_length: int,
523
+ field_one_year_data: FieldAnnualData,
524
+ year_in_perennial_stand: int,
525
+ ) -> CropViewItem:
526
+ weather_data = field_one_year_data.weather_data
527
+
528
+ return CropViewItem(
529
+ name=field_one_year_data.name,
530
+ field_area=field_one_year_data.field_area,
531
+ current_year=weather_data.year,
532
+ crop_year=field_one_year_data.crop_year,
533
+ year_in_perennial_stand=year_in_perennial_stand,
534
+ crop_type=field_one_year_data.crop_type,
535
+ tillage_type=field_one_year_data.tillage_type,
536
+ perennial_stand_id=perennial_stand_id,
537
+ perennial_stand_length=perennial_stand_length,
538
+ relative_biomass_information_data=get_relative_biomass_information_data(
539
+ table_7=self.table_7,
540
+ crop_type=field_one_year_data.crop_type,
541
+ irrigation_type=field_one_year_data.irrigation_type,
542
+ irrigation_amount=sum(weather_data.precipitation),
543
+ province=province),
544
+ crop_yield=field_one_year_data.crop_yield,
545
+ harvest_method=field_one_year_data.harvest_method,
546
+ nitrogen_fertilizer_rate=field_one_year_data.nitrogen_fertilizer_rate,
547
+ under_sown_crops_used=field_one_year_data.under_sown_crops_used,
548
+ field_system_component_guid=field_system_component_guid,
549
+ province=province,
550
+ clay_content=clay_content,
551
+ sand_content=sand_content,
552
+ organic_carbon_percentage=organic_carbon_percentage,
553
+ soil_top_layer_thickness=soil_top_layer_thickness,
554
+ soil_functional_category=soil_functional_category,
555
+ fertilizer_blend=field_one_year_data.fertilizer_blend,
556
+ evapotranspiration=weather_data.potential_evapotranspiration,
557
+ precipitation=weather_data.precipitation,
558
+ temperature=weather_data.temperature,
559
+
560
+ amount_of_irrigation=field_one_year_data.amount_of_irrigation,
561
+ number_of_pesticide_passes=field_one_year_data.number_of_pesticide_passes,
562
+ amount_of_manure_applied=field_one_year_data.amount_of_manure_applied,
563
+ manure_application_type=field_one_year_data.manure_application_type,
564
+ manure_animal_source_type=field_one_year_data.manure_animal_source_type,
565
+ manure_state_type=field_one_year_data.manure_state_type,
566
+ manure_location_source_type=field_one_year_data.manure_location_source_type
567
+ )
568
+
569
+ def _create_field_component(
570
+ self,
571
+ field_data: list[FieldAnnualData],
572
+ province: CanadianProvince,
573
+ clay_content: float,
574
+ sand_content: float,
575
+ organic_carbon_percentage: float,
576
+ soil_top_layer_thickness: float,
577
+ soil_functional_category: SoilFunctionalCategory,
578
+ ) -> list[CropViewItem]:
579
+
580
+ field_system_component_guid = uuid4()
581
+ crops = [v.crop_type for v in field_data]
582
+ years_in_perennial_stand = self.calc_year_in_perennial_stand(crops=crops)
583
+ ids_perennial = self.set_perennial_stand_id(crops=crops)
584
+ perennial_stand_lengths = self.calc_perennial_stand_lengths(years_in_perennial_stand=years_in_perennial_stand)
585
+
586
+ res = []
587
+ for annual_data, year_in_perennial_stand, perennial_stand_length, id_perennial in zip(
588
+ field_data,
589
+ years_in_perennial_stand,
590
+ perennial_stand_lengths,
591
+ ids_perennial
592
+ ):
593
+ one_year_component = self._create_one_year_component(
594
+ province=province,
595
+ clay_content=clay_content,
596
+ sand_content=sand_content,
597
+ organic_carbon_percentage=organic_carbon_percentage,
598
+ soil_top_layer_thickness=soil_top_layer_thickness,
599
+ soil_functional_category=soil_functional_category,
600
+ perennial_stand_id=id_perennial,
601
+ field_system_component_guid=field_system_component_guid,
602
+ perennial_stand_length=perennial_stand_length,
603
+ field_one_year_data=annual_data,
604
+ year_in_perennial_stand=year_in_perennial_stand,
605
+ )
606
+
607
+ res.append(one_year_component)
608
+
609
+ return res
610
+
611
+ def create_components(
612
+ self,
613
+ province: CanadianProvince,
614
+ clay_content: float,
615
+ sand_content: float,
616
+ organic_carbon_percentage: float,
617
+ soil_top_layer_thickness: float,
618
+ soil_functional_category: SoilFunctionalCategory,
619
+ ) -> list[list[CropViewItem]]:
620
+ res = []
621
+ for field_data in self.fields_data:
622
+ field_component = self._create_field_component(
623
+ field_data=field_data,
624
+ province=province,
625
+ clay_content=clay_content,
626
+ sand_content=sand_content,
627
+ organic_carbon_percentage=organic_carbon_percentage,
628
+ soil_top_layer_thickness=soil_top_layer_thickness,
629
+ soil_functional_category=soil_functional_category
630
+ )
631
+ res.append(field_component)
632
+
633
+ return res