22 KiB
راهنمای کامل crop_simulation/services.py
این فایل توضیح میدهد که سرویسهای شبیهسازی در crop_simulation/services.py چه کاری انجام میدهند، ورودی و خروجی هر بخش چیست، و چگونه با تنظیمات موجود در irrigation/apps.py و fertilization/apps.py ارتباط میگیرند.
نمای کلی
فایل crop_simulation/services.py هسته اجرای سناریوهای شبیهسازی محصول در پروژه است. این فایل سه مسئولیت اصلی دارد:
- نرمالسازی ورودیها برای موتور شبیهسازی
- اجرای مدل PCSE/WOFOST
- ذخیره و مدیریت سناریوها و runها در دیتابیس
در عمل این فایل بین دادههای خام مزرعه/هواشناسی/مدیریتی و خروجی نهایی شبیهسازی قرار میگیرد.
ساختار کلی فایل
این فایل را میتوان به ۴ بخش تقسیم کرد:
- توابع کمکی برای تبدیل ورودیها
- کلاس
PcseSimulationManager - کلاس
CropSimulationService - wrapperهای سطح ماژول برای استفاده سادهتر
بخش اول: ثابتها و Exception
DEFAULT_OUTPUT_VARS
لیست متغیرهایی که از خروجی روزانه مدل میخواهیم:
DVSLAITAGPTWSOSM
DEFAULT_SUMMARY_VARS
متغیرهای خلاصه:
TAGPTWSOCTRATRD
DEFAULT_TERMINAL_VARS
متغیرهای انتهایی:
TAGPTWSOLAIDVS
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 تبدیل میکند.
ورودی قابل قبول:
datedatetime- رشته ISO مثل
2026-04-01
اگر نوع پشتیبانی نشود، CropSimulationError میدهد.
_normalize_weather_records(weather)
ورودی آبوهوا را به فرمت استاندارد موردنیاز PCSE تبدیل میکند.
ورودی قابل قبول:
- یک
dict - یک
list[dict] - یک آبجکت با کلید
records
خروجی همیشه لیستی از رکوردهای نرمالشده با کلیدهای زیر است:
DAYLATLONELEVIRRADTMINTMAXVAPWINDRAINE0ES0ET0
اگر رکوردها خالی باشند، خطا میدهد.
_normalize_agromanagement(agromanagement)
ورودی agromanagement را به یک list[dict] تبدیل میکند.
ورودی قابل قبول:
- دیکشنری با کلید
AgroManagement - لیست
- یک دیکشنری تکی
اگر خالی باشد، خطا میدهد.
_deep_copy_json_like(value)
نسخه deep copy ساده از objectهای JSON-like میسازد.
برای جلوگیری از mutation روی ورودی اصلی استفاده میشود.
_parse_recommendation_events(...)
دادههای توصیه آبیاری یا کودهی را به فرمت event قابل الحاق به TimedEvents تبدیل میکند.
این تابع از چند شکل ورودی پشتیبانی میکند:
eventsscheduleapplicationsplan
نمونه ورودی آبیاری:
{
"events": [
{"date": "2026-04-25", "amount": 2.5, "efficiency": 0.8}
]
}
نمونه خروجی:
[
{
"event_signal": "irrigate",
"name": "irrigate recommendation",
"comment": "",
"events_table": [
{
date(2026, 4, 25): {"amount": 2.5, "efficiency": 0.8}
}
],
}
]
_merge_management_recommendations(...)
مهمترین تابع glue برای اتصال recommendationها به شبیهسازی است.
کار این تابع:
- agromanagement را normalize میکند
- توصیه آبیاری را به eventهای
irrigateتبدیل میکند - توصیه کودهی را به eventهای
apply_nتبدیل میکند - همه آنها را داخل اولین campaign معتبر در
TimedEventsmerge میکند
این تابع همان نقطهای است که 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 میکند:
ParameterProviderWeatherDataProviderWeatherDataContainerpcse.models
اگر pcse نصب نباشد، None برمیگرداند.
_resolve_model_class(bindings, model_name)
کلاس مدل PCSE را با نامی مثل Wofost81_NWLP_CWB_CNB پیدا میکند.
PreparedSimulationInput
این dataclass ورودیهای نرمالشده برای اجرای مدل را نگه میدارد:
weathersoilcropsiteagromanagement
این ساختار باعث میشود manager با یک payload استاندارد کار کند.
بخش سوم: PcseSimulationManager
این کلاس فقط مسئول اجرای موتور شبیهسازی است و وارد منطق ذخیره سناریوها نمیشود.
__init__(model_name="Wofost81_NWLP_CWB_CNB")
مدل PCSE مورد استفاده را مشخص میکند.
مدل پیشفرض:
Wofost81_NWLP_CWB_CNB
run_simulation(...)
ورودی خام میگیرد، normalize میکند، dependencyهای PCSE را load میکند، و شبیهسازی را اجرا میکند.
پارامترها:
weathersoilcrop_parametersagromanagementsite_parameters
خروجی:
{
"engine": "pcse",
"model_name": "Wofost81_NWLP_CWB_CNB",
"metrics": {...},
"daily_output": [...],
"summary_output": [...],
"terminal_output": [...]
}
_run_with_pcse(prepared, bindings)
اجرای واقعی مدل را انجام میدهد.
جریان داخلی:
- ساخت weather provider سفارشی از روی dictها
- ساخت
ParameterProvider - ساخت instance مدل PCSE
- اجرای
run_till_terminate()یاrun() - گرفتن خروجیها
- تبدیل خروجی به فرم نهایی
_build_result(...)
metricهای کلیدی را از خروجیهای terminal/summary/daily استخراج میکند:
yield_estimatebiomassmax_lai
اولویت انتخاب metricها:
- terminal
- summary
- آخرین رکورد daily
بخش چهارم: CropSimulationService
این کلاس service layer سطح بالاتر است. علاوه بر اجرای مدل، سناریوها و runها را در دیتابیس ذخیره میکند.
مدلهای مرتبط:
SimulationScenarioSimulationRun
__init__(manager=None)
اگر manager داده نشود، از PcseSimulationManager() پیشفرض استفاده میشود.
متدهای public اصلی
1) run_single_simulation(...)
برای اجرای یک سناریوی تکی.
پارامترها:
weathersoilcrop_parametersagromanagementsite_parametersirrigation_recommendationfertilization_recommendationname
کارها:
- merge کردن recommendationها داخل management
- ساخت
SimulationScenarioبا نوعSINGLE - ساخت
SimulationRun - اجرای سناریو
مهم: اگر recommendationهای آبیاری/کودهی بدهید، این متد آنها را به eventهای مدل تبدیل میکند.
نمونه:
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_acrop_b
خروجی:
- سناریو با نوع
CROP_COMPARISON - دو run
- comparison شامل best run و yield gap
3) recommend_best_crop(...)
برای مقایسه چند محصول و انتخاب بهترین گزینه.
ورودی مهم:
crops: list[dict]
شرط:
- حداقل دو crop باید وجود داشته باشد
خروجی سادهشده:
{
"scenario_id": ...,
"scenario_type": "crop_comparison",
"recommended_crop": {
"run_key": "...",
"label": "...",
"expected_yield_estimate": ...
},
"candidates": [...],
"raw_result": {...}
}
4) compare_fertilization_strategies(...)
برای مقایسه چند strategy کودهی روی یک crop ثابت.
ورودی ویژه:
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(...)
قلب اجرای سناریو است.
جریان:
- status سناریو را
RUNNINGمیکند - تکتک runها را اجرا میکند
- خروجی هر run را ذخیره میکند
- اگر exception رخ دهد:
- همان run را
FAILUREمیکند - سناریو را
FAILUREمیکند - خطا را ذخیره میکند
- همان run را
- اگر همه چیز موفق باشد:
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 بسازید:
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 میکند:
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 است
یعنی:
optimizer = apps.get_app_config("crop_simulation").get_recommendation_optimizer()
و بعد optimizer در داخل خودش از CropSimulationService استفاده میکند.
ارتباط با irrigation/apps.py
فایل irrigation/apps.py خودش شبیهسازی اجرا نمیکند؛ بلکه تنظیمات default برای optimizer آبیاری را نگه میدارد.
فیلدهای مهم
tone_file
مسیر tone مربوط به LLM:
config/tones/irrigation_tone.txt
optimizer_defaults
این property تنظیمات پایه بهینهسازی آبیاری را برمیگرداند:
validity_daysminimum_event_mmsignificant_rain_threshold_mmstage_targetsstrategy_profiles
stage_targets
هدف رطوبت یا رفتار پایه برای stageهای مختلف:
initialvegetativefloweringfruiting
strategy_profiles
سه سناریوی پایه برای optimizer:
conservativebalancedprotective
هر سناریو مشخص میکند:
- ضریب آب (
multiplier) - ضریب تعداد دفعات (
frequency_factor) - تعداد event پایه (
event_count)
نحوه استفاده در کد
در optimizer آبیاری معمولا به شکل زیر خوانده میشود:
defaults = apps.get_app_config("irrigation").get_optimizer_defaults()
سپس این defaults به سناریوهای recommendation تبدیل میشوند و در صورت نیاز به run_single_simulation() پاس داده میشوند.
نقش آن در ارتباط با services.py
ارتباط غیرمستقیم است:
irrigation/apps.pyتنظیمات baseline را میدهد- optimizer با این تنظیمات candidate strategy میسازد
- strategyها به recommendation event تبدیل میشوند
crop_simulation/services.pyآنها را داخل agromanagement merge و اجرا میکند
ارتباط با fertilization/apps.py
این فایل مشابه irrigation است اما برای منطق کودهی.
tone_file
config/tones/fertilization_tone.txt
optimizer_defaults
این تنظیمات را میدهد:
validity_daysrain_delay_threshold_mmstage_targetsstrategy_profiles
stage_targets
برای هر stage اطلاعات زیر مشخص میشود:
npkformulaapplication_methodtiming
strategy_profiles
سناریوهای پایه:
maintenancebalancedcorrective
هرکدام مشخص میکنند:
- ضریب مصرف (
multiplier) - focus تغذیهای
- روش مصرف
- override فرمول در صورت نیاز
نحوه استفاده در کد
defaults = apps.get_app_config("fertilization").get_optimizer_defaults()
سپس optimizer با این defaults چند strategy میسازد. اگر لازم باشد این strategyها به compare_fertilization_strategies() یا run_single_simulation() داده میشوند.
ارتباط آن با services.py
ارتباط باز هم غیرمستقیم است:
fertilization/apps.pyپروفایل stage و strategy را میدهد- optimizer از روی آن strategy تولید میکند
- strategy به eventهای
apply_nتبدیل میشود services.pyاین eventها را داخل agromanagement merge میکند- سناریو اجرا و مقایسه میشود
الگوی ارتباط کامل بین سه بخش
سناریوی آبیاری
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
سناریوی کودهی
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
نمونه استفاده واقعی
اجرای یک شبیهسازی ساده
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},
)
مقایسه دو محصول
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های کودهی
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های جدید را وارد شبیهسازی کنید، مهمترین نقطه:
_merge_management_recommendations()
2. نقطه اصلی اجرای موتور
اگر بخواهید backend engine عوض شود یا مدل جدید اضافه شود:
PcseSimulationManager.run_simulation()
3. نقطه اصلی مدیریت lifecycle سناریو
اگر بخواهید queueing، logging یا audit بیشتری اضافه کنید:
CropSimulationService._execute_scenario()
4. ارتباط با اپهای recommendation
اگر stageها یا strategyهای آبیاری/کودهی تغییر کنند، باید این فایلها بررسی شوند:
irrigation/apps.pyfertilization/apps.py
چون optimizer از آنها defaultها را میخواند.
جمعبندی
اگر بخواهیم نقش هر فایل را در یک جمله بگوییم:
crop_simulation/services.py: اجرای شبیهسازی، ساخت scenario/run، و merge کردن recommendationها با managementcrop_simulation/apps.py: نقطه دسترسی مرکزی به optimizerirrigation/apps.py: تنظیمات پایه برای سناریوهای بهینهسازی آبیاریfertilization/apps.py: تنظیمات پایه برای سناریوهای بهینهسازی کودهی
و زنجیره کلی این است:
defaults in app config
-> optimizer
-> recommendation events
-> crop_simulation/services.py
-> PCSE execution
-> scenario result
اگر بخواهید، قدم بعدی میتوانم یک فایل دوم هم بسازم که فقط نمونه request/response واقعی برای هر تابع و هر سناریو را بهصورت cookbook نشان بدهد.