823 lines
22 KiB
Markdown
823 lines
22 KiB
Markdown
|
|
# راهنمای کامل `crop_simulation/services.py`
|
||
|
|
|
||
|
|
این فایل توضیح میدهد که سرویسهای شبیهسازی در `crop_simulation/services.py` چه کاری انجام میدهند، ورودی و خروجی هر بخش چیست، و چگونه با تنظیمات موجود در `irrigation/apps.py` و `fertilization/apps.py` ارتباط میگیرند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نمای کلی
|
||
|
|
|
||
|
|
فایل `crop_simulation/services.py` هسته اجرای سناریوهای شبیهسازی محصول در پروژه است. این فایل سه مسئولیت اصلی دارد:
|
||
|
|
|
||
|
|
1. نرمالسازی ورودیها برای موتور شبیهسازی
|
||
|
|
2. اجرای مدل PCSE/WOFOST
|
||
|
|
3. ذخیره و مدیریت سناریوها و runها در دیتابیس
|
||
|
|
|
||
|
|
در عمل این فایل بین دادههای خام مزرعه/هواشناسی/مدیریتی و خروجی نهایی شبیهسازی قرار میگیرد.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ساختار کلی فایل
|
||
|
|
|
||
|
|
این فایل را میتوان به ۴ بخش تقسیم کرد:
|
||
|
|
|
||
|
|
1. توابع کمکی برای تبدیل ورودیها
|
||
|
|
2. کلاس `PcseSimulationManager`
|
||
|
|
3. کلاس `CropSimulationService`
|
||
|
|
4. wrapperهای سطح ماژول برای استفاده سادهتر
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش اول: ثابتها و Exception
|
||
|
|
|
||
|
|
### `DEFAULT_OUTPUT_VARS`
|
||
|
|
لیست متغیرهایی که از خروجی روزانه مدل میخواهیم:
|
||
|
|
|
||
|
|
- `DVS`
|
||
|
|
- `LAI`
|
||
|
|
- `TAGP`
|
||
|
|
- `TWSO`
|
||
|
|
- `SM`
|
||
|
|
|
||
|
|
### `DEFAULT_SUMMARY_VARS`
|
||
|
|
متغیرهای خلاصه:
|
||
|
|
|
||
|
|
- `TAGP`
|
||
|
|
- `TWSO`
|
||
|
|
- `CTRAT`
|
||
|
|
- `RD`
|
||
|
|
|
||
|
|
### `DEFAULT_TERMINAL_VARS`
|
||
|
|
متغیرهای انتهایی:
|
||
|
|
|
||
|
|
- `TAGP`
|
||
|
|
- `TWSO`
|
||
|
|
- `LAI`
|
||
|
|
- `DVS`
|
||
|
|
|
||
|
|
### `CropSimulationError`
|
||
|
|
خطای اختصاصی این ماژول است. هر جا داده ورودی یا اجرای مدل مشکل داشته باشد، معمولا این exception یا exceptionهای مشتقشده از آن دیده میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش دوم: توابع کمکی داخلی
|
||
|
|
|
||
|
|
این توابع public API نیستند، اما پایه رفتار کل سرویس را تشکیل میدهند.
|
||
|
|
|
||
|
|
### `_json_ready(value)`
|
||
|
|
دادههای Python را برای ذخیره در JSON آماده میکند.
|
||
|
|
|
||
|
|
کارهایی که انجام میدهد:
|
||
|
|
|
||
|
|
- `dict`، `list` و `tuple` را recursive تبدیل میکند
|
||
|
|
- `date` و `datetime` را به `isoformat()` تبدیل میکند
|
||
|
|
|
||
|
|
موارد استفاده:
|
||
|
|
|
||
|
|
- قبل از ذخیره `input_payload`
|
||
|
|
- قبل از ذخیره `result_payload`
|
||
|
|
- قبل از ذخیره payload هر `SimulationRun`
|
||
|
|
|
||
|
|
### `_coerce_date(value)`
|
||
|
|
ورودی را به `date` تبدیل میکند.
|
||
|
|
|
||
|
|
ورودی قابل قبول:
|
||
|
|
|
||
|
|
- `date`
|
||
|
|
- `datetime`
|
||
|
|
- رشته ISO مثل `2026-04-01`
|
||
|
|
|
||
|
|
اگر نوع پشتیبانی نشود، `CropSimulationError` میدهد.
|
||
|
|
|
||
|
|
### `_normalize_weather_records(weather)`
|
||
|
|
ورودی آبوهوا را به فرمت استاندارد موردنیاز PCSE تبدیل میکند.
|
||
|
|
|
||
|
|
ورودی قابل قبول:
|
||
|
|
|
||
|
|
- یک `dict`
|
||
|
|
- یک `list[dict]`
|
||
|
|
- یک آبجکت با کلید `records`
|
||
|
|
|
||
|
|
خروجی همیشه لیستی از رکوردهای نرمالشده با کلیدهای زیر است:
|
||
|
|
|
||
|
|
- `DAY`
|
||
|
|
- `LAT`
|
||
|
|
- `LON`
|
||
|
|
- `ELEV`
|
||
|
|
- `IRRAD`
|
||
|
|
- `TMIN`
|
||
|
|
- `TMAX`
|
||
|
|
- `VAP`
|
||
|
|
- `WIND`
|
||
|
|
- `RAIN`
|
||
|
|
- `E0`
|
||
|
|
- `ES0`
|
||
|
|
- `ET0`
|
||
|
|
|
||
|
|
اگر رکوردها خالی باشند، خطا میدهد.
|
||
|
|
|
||
|
|
### `_normalize_agromanagement(agromanagement)`
|
||
|
|
ورودی agromanagement را به یک `list[dict]` تبدیل میکند.
|
||
|
|
|
||
|
|
ورودی قابل قبول:
|
||
|
|
|
||
|
|
- دیکشنری با کلید `AgroManagement`
|
||
|
|
- لیست
|
||
|
|
- یک دیکشنری تکی
|
||
|
|
|
||
|
|
اگر خالی باشد، خطا میدهد.
|
||
|
|
|
||
|
|
### `_deep_copy_json_like(value)`
|
||
|
|
نسخه deep copy ساده از objectهای JSON-like میسازد.
|
||
|
|
|
||
|
|
برای جلوگیری از mutation روی ورودی اصلی استفاده میشود.
|
||
|
|
|
||
|
|
### `_parse_recommendation_events(...)`
|
||
|
|
دادههای توصیه آبیاری یا کودهی را به فرمت event قابل الحاق به `TimedEvents` تبدیل میکند.
|
||
|
|
|
||
|
|
این تابع از چند شکل ورودی پشتیبانی میکند:
|
||
|
|
|
||
|
|
- `events`
|
||
|
|
- `schedule`
|
||
|
|
- `applications`
|
||
|
|
- `plan`
|
||
|
|
|
||
|
|
نمونه ورودی آبیاری:
|
||
|
|
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"events": [
|
||
|
|
{"date": "2026-04-25", "amount": 2.5, "efficiency": 0.8}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
نمونه خروجی:
|
||
|
|
|
||
|
|
```python
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"event_signal": "irrigate",
|
||
|
|
"name": "irrigate recommendation",
|
||
|
|
"comment": "",
|
||
|
|
"events_table": [
|
||
|
|
{
|
||
|
|
date(2026, 4, 25): {"amount": 2.5, "efficiency": 0.8}
|
||
|
|
}
|
||
|
|
],
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
### `_merge_management_recommendations(...)`
|
||
|
|
مهمترین تابع glue برای اتصال recommendationها به شبیهسازی است.
|
||
|
|
|
||
|
|
کار این تابع:
|
||
|
|
|
||
|
|
1. agromanagement را normalize میکند
|
||
|
|
2. توصیه آبیاری را به eventهای `irrigate` تبدیل میکند
|
||
|
|
3. توصیه کودهی را به eventهای `apply_n` تبدیل میکند
|
||
|
|
4. همه آنها را داخل اولین campaign معتبر در `TimedEvents` merge میکند
|
||
|
|
|
||
|
|
این تابع همان نقطهای است که recommendationهای اپ آبیاری/کودهی به سناریوی شبیهسازی تزریق میشوند.
|
||
|
|
|
||
|
|
### `_normalize_pcse_output_records(records)`
|
||
|
|
خروجیهای مدل PCSE را به لیست تبدیل میکند تا کدهای بعدی همیشه با ساختار یکنواخت کار کنند.
|
||
|
|
|
||
|
|
### `_pick_first_not_none(*values)`
|
||
|
|
اولین مقدار non-null را برمیگرداند.
|
||
|
|
|
||
|
|
برای ساخت metricهای نهایی مثل `yield_estimate` استفاده میشود.
|
||
|
|
|
||
|
|
### `_extract_total_n(agromanagement)`
|
||
|
|
جمع کل `N_amount` را از eventهای کودهی استخراج میکند.
|
||
|
|
|
||
|
|
در نسخه فعلی این تابع برای محاسبات جانبی آماده است و نقطه مناسبی برای توسعه تحلیل استراتژیهای تغذیه است.
|
||
|
|
|
||
|
|
### `_load_pcse_bindings()`
|
||
|
|
کلاسها و ماژولهای لازم از package `pcse` را load میکند:
|
||
|
|
|
||
|
|
- `ParameterProvider`
|
||
|
|
- `WeatherDataProvider`
|
||
|
|
- `WeatherDataContainer`
|
||
|
|
- `pcse.models`
|
||
|
|
|
||
|
|
اگر `pcse` نصب نباشد، `None` برمیگرداند.
|
||
|
|
|
||
|
|
### `_resolve_model_class(bindings, model_name)`
|
||
|
|
کلاس مدل PCSE را با نامی مثل `Wofost81_NWLP_CWB_CNB` پیدا میکند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## `PreparedSimulationInput`
|
||
|
|
|
||
|
|
این dataclass ورودیهای نرمالشده برای اجرای مدل را نگه میدارد:
|
||
|
|
|
||
|
|
- `weather`
|
||
|
|
- `soil`
|
||
|
|
- `crop`
|
||
|
|
- `site`
|
||
|
|
- `agromanagement`
|
||
|
|
|
||
|
|
این ساختار باعث میشود manager با یک payload استاندارد کار کند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش سوم: `PcseSimulationManager`
|
||
|
|
|
||
|
|
این کلاس فقط مسئول اجرای موتور شبیهسازی است و وارد منطق ذخیره سناریوها نمیشود.
|
||
|
|
|
||
|
|
### `__init__(model_name="Wofost81_NWLP_CWB_CNB")`
|
||
|
|
مدل PCSE مورد استفاده را مشخص میکند.
|
||
|
|
|
||
|
|
مدل پیشفرض:
|
||
|
|
|
||
|
|
```python
|
||
|
|
Wofost81_NWLP_CWB_CNB
|
||
|
|
```
|
||
|
|
|
||
|
|
### `run_simulation(...)`
|
||
|
|
ورودی خام میگیرد، normalize میکند، dependencyهای PCSE را load میکند، و شبیهسازی را اجرا میکند.
|
||
|
|
|
||
|
|
پارامترها:
|
||
|
|
|
||
|
|
- `weather`
|
||
|
|
- `soil`
|
||
|
|
- `crop_parameters`
|
||
|
|
- `agromanagement`
|
||
|
|
- `site_parameters`
|
||
|
|
|
||
|
|
خروجی:
|
||
|
|
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"engine": "pcse",
|
||
|
|
"model_name": "Wofost81_NWLP_CWB_CNB",
|
||
|
|
"metrics": {...},
|
||
|
|
"daily_output": [...],
|
||
|
|
"summary_output": [...],
|
||
|
|
"terminal_output": [...]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### `_run_with_pcse(prepared, bindings)`
|
||
|
|
اجرای واقعی مدل را انجام میدهد.
|
||
|
|
|
||
|
|
جریان داخلی:
|
||
|
|
|
||
|
|
1. ساخت weather provider سفارشی از روی dictها
|
||
|
|
2. ساخت `ParameterProvider`
|
||
|
|
3. ساخت instance مدل PCSE
|
||
|
|
4. اجرای `run_till_terminate()` یا `run()`
|
||
|
|
5. گرفتن خروجیها
|
||
|
|
6. تبدیل خروجی به فرم نهایی
|
||
|
|
|
||
|
|
### `_build_result(...)`
|
||
|
|
metricهای کلیدی را از خروجیهای terminal/summary/daily استخراج میکند:
|
||
|
|
|
||
|
|
- `yield_estimate`
|
||
|
|
- `biomass`
|
||
|
|
- `max_lai`
|
||
|
|
|
||
|
|
اولویت انتخاب metricها:
|
||
|
|
|
||
|
|
1. terminal
|
||
|
|
2. summary
|
||
|
|
3. آخرین رکورد daily
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش چهارم: `CropSimulationService`
|
||
|
|
|
||
|
|
این کلاس service layer سطح بالاتر است. علاوه بر اجرای مدل، سناریوها و runها را در دیتابیس ذخیره میکند.
|
||
|
|
|
||
|
|
مدلهای مرتبط:
|
||
|
|
|
||
|
|
- `SimulationScenario`
|
||
|
|
- `SimulationRun`
|
||
|
|
|
||
|
|
### `__init__(manager=None)`
|
||
|
|
اگر manager داده نشود، از `PcseSimulationManager()` پیشفرض استفاده میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## متدهای public اصلی
|
||
|
|
|
||
|
|
### 1) `run_single_simulation(...)`
|
||
|
|
برای اجرای یک سناریوی تکی.
|
||
|
|
|
||
|
|
پارامترها:
|
||
|
|
|
||
|
|
- `weather`
|
||
|
|
- `soil`
|
||
|
|
- `crop_parameters`
|
||
|
|
- `agromanagement`
|
||
|
|
- `site_parameters`
|
||
|
|
- `irrigation_recommendation`
|
||
|
|
- `fertilization_recommendation`
|
||
|
|
- `name`
|
||
|
|
|
||
|
|
کارها:
|
||
|
|
|
||
|
|
1. merge کردن recommendationها داخل management
|
||
|
|
2. ساخت `SimulationScenario` با نوع `SINGLE`
|
||
|
|
3. ساخت `SimulationRun`
|
||
|
|
4. اجرای سناریو
|
||
|
|
|
||
|
|
مهم:
|
||
|
|
اگر recommendationهای آبیاری/کودهی بدهید، این متد آنها را به eventهای مدل تبدیل میکند.
|
||
|
|
|
||
|
|
نمونه:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from crop_simulation.services import run_single_simulation
|
||
|
|
|
||
|
|
result = run_single_simulation(
|
||
|
|
weather=weather_payload,
|
||
|
|
soil={"SMFCF": 0.34, "SMW": 0.12, "RDMSOL": 120.0},
|
||
|
|
crop_parameters={"crop_name": "wheat", "TSUM1": 800},
|
||
|
|
agromanagement=agromanagement_payload,
|
||
|
|
site_parameters={"WAV": 40.0},
|
||
|
|
irrigation_recommendation={
|
||
|
|
"events": [
|
||
|
|
{"date": "2026-04-25", "amount": 2.5, "efficiency": 0.8}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2) `compare_crops(...)`
|
||
|
|
برای مقایسه دو محصول.
|
||
|
|
|
||
|
|
ورودیهای اضافه:
|
||
|
|
|
||
|
|
- `crop_a`
|
||
|
|
- `crop_b`
|
||
|
|
|
||
|
|
خروجی:
|
||
|
|
|
||
|
|
- سناریو با نوع `CROP_COMPARISON`
|
||
|
|
- دو run
|
||
|
|
- comparison شامل best run و yield gap
|
||
|
|
|
||
|
|
### 3) `recommend_best_crop(...)`
|
||
|
|
برای مقایسه چند محصول و انتخاب بهترین گزینه.
|
||
|
|
|
||
|
|
ورودی مهم:
|
||
|
|
|
||
|
|
- `crops: list[dict]`
|
||
|
|
|
||
|
|
شرط:
|
||
|
|
|
||
|
|
- حداقل دو crop باید وجود داشته باشد
|
||
|
|
|
||
|
|
خروجی سادهشده:
|
||
|
|
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"scenario_id": ...,
|
||
|
|
"scenario_type": "crop_comparison",
|
||
|
|
"recommended_crop": {
|
||
|
|
"run_key": "...",
|
||
|
|
"label": "...",
|
||
|
|
"expected_yield_estimate": ...
|
||
|
|
},
|
||
|
|
"candidates": [...],
|
||
|
|
"raw_result": {...}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4) `compare_fertilization_strategies(...)`
|
||
|
|
برای مقایسه چند strategy کودهی روی یک crop ثابت.
|
||
|
|
|
||
|
|
ورودی ویژه:
|
||
|
|
|
||
|
|
```python
|
||
|
|
strategies = [
|
||
|
|
{
|
||
|
|
"label": "base",
|
||
|
|
"agromanagement": [...]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"label": "high_n",
|
||
|
|
"agromanagement": [...]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
این متد برای هر strategy یک run میسازد و بهترین استراتژی را بر اساس `yield_estimate` انتخاب میکند.
|
||
|
|
|
||
|
|
### 5) `get_scenario_result(scenario_id)`
|
||
|
|
نتیجه ذخیرهشده یک سناریو را از دیتابیس برمیگرداند.
|
||
|
|
|
||
|
|
خروجی شامل:
|
||
|
|
|
||
|
|
- اطلاعات scenario
|
||
|
|
- اطلاعات همه runها
|
||
|
|
- status
|
||
|
|
- input payload
|
||
|
|
- result payload
|
||
|
|
- error message
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## متدهای داخلی مهم در `CropSimulationService`
|
||
|
|
|
||
|
|
### `_execute_scenario(...)`
|
||
|
|
قلب اجرای سناریو است.
|
||
|
|
|
||
|
|
جریان:
|
||
|
|
|
||
|
|
1. status سناریو را `RUNNING` میکند
|
||
|
|
2. تکتک runها را اجرا میکند
|
||
|
|
3. خروجی هر run را ذخیره میکند
|
||
|
|
4. اگر exception رخ دهد:
|
||
|
|
- همان run را `FAILURE` میکند
|
||
|
|
- سناریو را `FAILURE` میکند
|
||
|
|
- خطا را ذخیره میکند
|
||
|
|
5. اگر همه چیز موفق باشد:
|
||
|
|
- `scenario_result` میسازد
|
||
|
|
- سناریو را `SUCCESS` میکند
|
||
|
|
|
||
|
|
### `_build_scenario_result(scenario, results)`
|
||
|
|
خروجی سطح سناریو را میسازد.
|
||
|
|
|
||
|
|
رفتار بر اساس نوع سناریو:
|
||
|
|
|
||
|
|
- `SINGLE`:
|
||
|
|
- فقط `result` برمیگرداند
|
||
|
|
- `CROP_COMPARISON`:
|
||
|
|
- comparison میسازد
|
||
|
|
- بهترین run را مشخص میکند
|
||
|
|
- `yield_gap` میسازد
|
||
|
|
- `FERTILIZATION_COMPARISON`:
|
||
|
|
- recommendation برای بهترین strategy میسازد
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## wrapperهای سطح ماژول
|
||
|
|
|
||
|
|
در انتهای فایل این wrapperها وجود دارند:
|
||
|
|
|
||
|
|
- `run_single_simulation(**kwargs)`
|
||
|
|
- `compare_crops(**kwargs)`
|
||
|
|
- `recommend_best_crop(**kwargs)`
|
||
|
|
- `compare_fertilization_strategies(**kwargs)`
|
||
|
|
|
||
|
|
همه آنها با `@transaction.atomic` تزئین شدهاند.
|
||
|
|
|
||
|
|
یعنی اگر بخواهید ساده از بیرون صدا بزنید، لازم نیست خودتان instance بسازید:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from crop_simulation.services import recommend_best_crop
|
||
|
|
|
||
|
|
result = recommend_best_crop(
|
||
|
|
weather=weather_payload,
|
||
|
|
soil=soil_payload,
|
||
|
|
crops=[crop_a, crop_b, crop_c],
|
||
|
|
agromanagement=agromanagement_payload,
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نحوه ارتباط با مدلهای دیتابیس
|
||
|
|
|
||
|
|
### `SimulationScenario`
|
||
|
|
نماینده یک سناریوی کلی است.
|
||
|
|
|
||
|
|
مثالها:
|
||
|
|
|
||
|
|
- single run
|
||
|
|
- crop comparison
|
||
|
|
- fertilization comparison
|
||
|
|
|
||
|
|
### `SimulationRun`
|
||
|
|
نماینده هر اجرای منفرد داخل یک سناریو است.
|
||
|
|
|
||
|
|
مثلا در `compare_crops`:
|
||
|
|
|
||
|
|
- یک `SimulationScenario`
|
||
|
|
- دو `SimulationRun`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ارتباط `crop_simulation/services.py` با `crop_simulation/apps.py`
|
||
|
|
|
||
|
|
فایل `crop_simulation/apps.py` این متد را expose میکند:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def get_recommendation_optimizer(self):
|
||
|
|
return self.recommendation_optimizer
|
||
|
|
```
|
||
|
|
|
||
|
|
این optimizer در فایل `crop_simulation/recommendation_optimizer.py` ساخته میشود و برای recommendationهای آبیاری و کودهی استفاده میشود.
|
||
|
|
|
||
|
|
نکته مهم:
|
||
|
|
|
||
|
|
- `services.py` موتور اجرای سناریوهاست
|
||
|
|
- `recommendation_optimizer.py` روی همین موتور سناریوهای candidate میسازد
|
||
|
|
- `apps.py` فقط نقطه دسترسی مرکزی به optimizer است
|
||
|
|
|
||
|
|
یعنی:
|
||
|
|
|
||
|
|
```python
|
||
|
|
optimizer = apps.get_app_config("crop_simulation").get_recommendation_optimizer()
|
||
|
|
```
|
||
|
|
|
||
|
|
و بعد optimizer در داخل خودش از `CropSimulationService` استفاده میکند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ارتباط با `irrigation/apps.py`
|
||
|
|
|
||
|
|
فایل `irrigation/apps.py` خودش شبیهسازی اجرا نمیکند؛ بلکه تنظیمات default برای optimizer آبیاری را نگه میدارد.
|
||
|
|
|
||
|
|
### فیلدهای مهم
|
||
|
|
|
||
|
|
#### `tone_file`
|
||
|
|
مسیر tone مربوط به LLM:
|
||
|
|
|
||
|
|
```python
|
||
|
|
config/tones/irrigation_tone.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `optimizer_defaults`
|
||
|
|
این property تنظیمات پایه بهینهسازی آبیاری را برمیگرداند:
|
||
|
|
|
||
|
|
- `validity_days`
|
||
|
|
- `minimum_event_mm`
|
||
|
|
- `significant_rain_threshold_mm`
|
||
|
|
- `stage_targets`
|
||
|
|
- `strategy_profiles`
|
||
|
|
|
||
|
|
### `stage_targets`
|
||
|
|
هدف رطوبت یا رفتار پایه برای stageهای مختلف:
|
||
|
|
|
||
|
|
- `initial`
|
||
|
|
- `vegetative`
|
||
|
|
- `flowering`
|
||
|
|
- `fruiting`
|
||
|
|
|
||
|
|
### `strategy_profiles`
|
||
|
|
سه سناریوی پایه برای optimizer:
|
||
|
|
|
||
|
|
- `conservative`
|
||
|
|
- `balanced`
|
||
|
|
- `protective`
|
||
|
|
|
||
|
|
هر سناریو مشخص میکند:
|
||
|
|
|
||
|
|
- ضریب آب (`multiplier`)
|
||
|
|
- ضریب تعداد دفعات (`frequency_factor`)
|
||
|
|
- تعداد event پایه (`event_count`)
|
||
|
|
|
||
|
|
### نحوه استفاده در کد
|
||
|
|
|
||
|
|
در optimizer آبیاری معمولا به شکل زیر خوانده میشود:
|
||
|
|
|
||
|
|
```python
|
||
|
|
defaults = apps.get_app_config("irrigation").get_optimizer_defaults()
|
||
|
|
```
|
||
|
|
|
||
|
|
سپس این defaults به سناریوهای recommendation تبدیل میشوند و در صورت نیاز به `run_single_simulation()` پاس داده میشوند.
|
||
|
|
|
||
|
|
### نقش آن در ارتباط با `services.py`
|
||
|
|
|
||
|
|
ارتباط غیرمستقیم است:
|
||
|
|
|
||
|
|
1. `irrigation/apps.py` تنظیمات baseline را میدهد
|
||
|
|
2. optimizer با این تنظیمات candidate strategy میسازد
|
||
|
|
3. strategyها به recommendation event تبدیل میشوند
|
||
|
|
4. `crop_simulation/services.py` آنها را داخل agromanagement merge و اجرا میکند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ارتباط با `fertilization/apps.py`
|
||
|
|
|
||
|
|
این فایل مشابه irrigation است اما برای منطق کودهی.
|
||
|
|
|
||
|
|
### `tone_file`
|
||
|
|
|
||
|
|
```python
|
||
|
|
config/tones/fertilization_tone.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
### `optimizer_defaults`
|
||
|
|
این تنظیمات را میدهد:
|
||
|
|
|
||
|
|
- `validity_days`
|
||
|
|
- `rain_delay_threshold_mm`
|
||
|
|
- `stage_targets`
|
||
|
|
- `strategy_profiles`
|
||
|
|
|
||
|
|
### `stage_targets`
|
||
|
|
برای هر stage اطلاعات زیر مشخص میشود:
|
||
|
|
|
||
|
|
- `n`
|
||
|
|
- `p`
|
||
|
|
- `k`
|
||
|
|
- `formula`
|
||
|
|
- `application_method`
|
||
|
|
- `timing`
|
||
|
|
|
||
|
|
### `strategy_profiles`
|
||
|
|
سناریوهای پایه:
|
||
|
|
|
||
|
|
- `maintenance`
|
||
|
|
- `balanced`
|
||
|
|
- `corrective`
|
||
|
|
|
||
|
|
هرکدام مشخص میکنند:
|
||
|
|
|
||
|
|
- ضریب مصرف (`multiplier`)
|
||
|
|
- focus تغذیهای
|
||
|
|
- روش مصرف
|
||
|
|
- override فرمول در صورت نیاز
|
||
|
|
|
||
|
|
### نحوه استفاده در کد
|
||
|
|
|
||
|
|
```python
|
||
|
|
defaults = apps.get_app_config("fertilization").get_optimizer_defaults()
|
||
|
|
```
|
||
|
|
|
||
|
|
سپس optimizer با این defaults چند strategy میسازد. اگر لازم باشد این strategyها به `compare_fertilization_strategies()` یا `run_single_simulation()` داده میشوند.
|
||
|
|
|
||
|
|
### ارتباط آن با `services.py`
|
||
|
|
|
||
|
|
ارتباط باز هم غیرمستقیم است:
|
||
|
|
|
||
|
|
1. `fertilization/apps.py` پروفایل stage و strategy را میدهد
|
||
|
|
2. optimizer از روی آن strategy تولید میکند
|
||
|
|
3. strategy به eventهای `apply_n` تبدیل میشود
|
||
|
|
4. `services.py` این eventها را داخل agromanagement merge میکند
|
||
|
|
5. سناریو اجرا و مقایسه میشود
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## الگوی ارتباط کامل بین سه بخش
|
||
|
|
|
||
|
|
### سناریوی آبیاری
|
||
|
|
|
||
|
|
```text
|
||
|
|
irrigation/apps.py
|
||
|
|
-> optimizer_defaults
|
||
|
|
-> recommendation optimizer
|
||
|
|
-> irrigation recommendation events
|
||
|
|
-> crop_simulation/services.py:_merge_management_recommendations()
|
||
|
|
-> run_single_simulation()
|
||
|
|
-> PCSE run
|
||
|
|
-> scenario/run result
|
||
|
|
```
|
||
|
|
|
||
|
|
### سناریوی کودهی
|
||
|
|
|
||
|
|
```text
|
||
|
|
fertilization/apps.py
|
||
|
|
-> optimizer_defaults
|
||
|
|
-> recommendation optimizer
|
||
|
|
-> fertilization recommendation events
|
||
|
|
-> crop_simulation/services.py:_merge_management_recommendations()
|
||
|
|
-> compare_fertilization_strategies() / run_single_simulation()
|
||
|
|
-> PCSE run
|
||
|
|
-> best strategy result
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نمونه استفاده واقعی
|
||
|
|
|
||
|
|
### اجرای یک شبیهسازی ساده
|
||
|
|
|
||
|
|
```python
|
||
|
|
from crop_simulation.services import run_single_simulation
|
||
|
|
|
||
|
|
result = run_single_simulation(
|
||
|
|
weather=[
|
||
|
|
{
|
||
|
|
"DAY": "2026-04-01",
|
||
|
|
"LAT": 35.7,
|
||
|
|
"LON": 51.4,
|
||
|
|
"ELEV": 1200,
|
||
|
|
"IRRAD": 16000000,
|
||
|
|
"TMIN": 11,
|
||
|
|
"TMAX": 22,
|
||
|
|
"VAP": 12,
|
||
|
|
"WIND": 2.4,
|
||
|
|
"RAIN": 0.8,
|
||
|
|
"E0": 0.35,
|
||
|
|
"ES0": 0.3,
|
||
|
|
"ET0": 0.32,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
soil={"SMFCF": 0.34, "SMW": 0.12, "RDMSOL": 120.0},
|
||
|
|
crop_parameters={"crop_name": "wheat", "TSUM1": 800, "YIELD_SCALE": 1.0},
|
||
|
|
agromanagement=[
|
||
|
|
{
|
||
|
|
"2026-04-01": {
|
||
|
|
"CropCalendar": {
|
||
|
|
"crop_name": "wheat",
|
||
|
|
"variety_name": "winter-wheat",
|
||
|
|
"crop_start_date": "2026-04-05",
|
||
|
|
"crop_start_type": "sowing",
|
||
|
|
"crop_end_date": "2026-09-01",
|
||
|
|
"crop_end_type": "harvest",
|
||
|
|
"max_duration": 180,
|
||
|
|
},
|
||
|
|
"TimedEvents": [],
|
||
|
|
"StateEvents": [],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
],
|
||
|
|
site_parameters={"WAV": 40.0},
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### مقایسه دو محصول
|
||
|
|
|
||
|
|
```python
|
||
|
|
from crop_simulation.services import compare_crops
|
||
|
|
|
||
|
|
result = compare_crops(
|
||
|
|
weather=weather_payload,
|
||
|
|
soil=soil_payload,
|
||
|
|
crop_a={"crop_name": "wheat", "TSUM1": 800},
|
||
|
|
crop_b={"crop_name": "maize", "TSUM1": 900},
|
||
|
|
agromanagement=agromanagement_payload,
|
||
|
|
site_parameters={"WAV": 40.0},
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### مقایسه strategyهای کودهی
|
||
|
|
|
||
|
|
```python
|
||
|
|
from crop_simulation.services import compare_fertilization_strategies
|
||
|
|
|
||
|
|
result = compare_fertilization_strategies(
|
||
|
|
weather=weather_payload,
|
||
|
|
soil=soil_payload,
|
||
|
|
crop_parameters={"crop_name": "wheat", "TSUM1": 800},
|
||
|
|
strategies=[
|
||
|
|
{"label": "base", "agromanagement": agm_base},
|
||
|
|
{"label": "high_n", "agromanagement": agm_high_n},
|
||
|
|
],
|
||
|
|
site_parameters={"WAV": 40.0},
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نکات مهم توسعه
|
||
|
|
|
||
|
|
### 1. نقطه اصلی inject کردن توصیهها
|
||
|
|
اگر بخواهید recommendationهای جدید را وارد شبیهسازی کنید، مهمترین نقطه:
|
||
|
|
|
||
|
|
```python
|
||
|
|
_merge_management_recommendations()
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. نقطه اصلی اجرای موتور
|
||
|
|
اگر بخواهید backend engine عوض شود یا مدل جدید اضافه شود:
|
||
|
|
|
||
|
|
```python
|
||
|
|
PcseSimulationManager.run_simulation()
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. نقطه اصلی مدیریت lifecycle سناریو
|
||
|
|
اگر بخواهید queueing، logging یا audit بیشتری اضافه کنید:
|
||
|
|
|
||
|
|
```python
|
||
|
|
CropSimulationService._execute_scenario()
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. ارتباط با اپهای recommendation
|
||
|
|
اگر stageها یا strategyهای آبیاری/کودهی تغییر کنند، باید این فایلها بررسی شوند:
|
||
|
|
|
||
|
|
- `irrigation/apps.py`
|
||
|
|
- `fertilization/apps.py`
|
||
|
|
|
||
|
|
چون optimizer از آنها defaultها را میخواند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## جمعبندی
|
||
|
|
|
||
|
|
اگر بخواهیم نقش هر فایل را در یک جمله بگوییم:
|
||
|
|
|
||
|
|
- `crop_simulation/services.py`: اجرای شبیهسازی، ساخت scenario/run، و merge کردن recommendationها با management
|
||
|
|
- `crop_simulation/apps.py`: نقطه دسترسی مرکزی به optimizer
|
||
|
|
- `irrigation/apps.py`: تنظیمات پایه برای سناریوهای بهینهسازی آبیاری
|
||
|
|
- `fertilization/apps.py`: تنظیمات پایه برای سناریوهای بهینهسازی کودهی
|
||
|
|
|
||
|
|
و زنجیره کلی این است:
|
||
|
|
|
||
|
|
```text
|
||
|
|
defaults in app config
|
||
|
|
-> optimizer
|
||
|
|
-> recommendation events
|
||
|
|
-> crop_simulation/services.py
|
||
|
|
-> PCSE execution
|
||
|
|
-> scenario result
|
||
|
|
```
|
||
|
|
|
||
|
|
اگر بخواهید، قدم بعدی میتوانم یک فایل دوم هم بسازم که فقط نمونه request/response واقعی برای هر تابع و هر سناریو را بهصورت cookbook نشان بدهد.
|