Files
Logic/Modules/Ai/crop_simulation/SERVICES_INTEGRATION.md
2026-05-11 03:27:21 +03:30

22 KiB

راهنمای کامل 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

نمونه ورودی آبیاری:

{
    "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ها به شبیه‌سازی است.

کار این تابع:

  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 مورد استفاده را مشخص می‌کند.

مدل پیش‌فرض:

Wofost81_NWLP_CWB_CNB

run_simulation(...)

ورودی خام می‌گیرد، normalize می‌کند، dependencyهای PCSE را load می‌کند، و شبیه‌سازی را اجرا می‌کند.

پارامترها:

  • weather
  • soil
  • crop_parameters
  • agromanagement
  • site_parameters

خروجی:

{
    "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های مدل تبدیل می‌کند.

نمونه:

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 باید وجود داشته باشد

خروجی ساده‌شده:

{
    "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(...)

قلب اجرای سناریو است.

جریان:

  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 بسازید:

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_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 آبیاری معمولا به شکل زیر خوانده می‌شود:

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

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 فرمول در صورت نیاز

نحوه استفاده در کد

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. سناریو اجرا و مقایسه می‌شود

الگوی ارتباط کامل بین سه بخش

سناریوی آبیاری

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.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: تنظیمات پایه برای سناریوهای بهینه‌سازی کودهی

و زنجیره کلی این است:

defaults in app config
    -> optimizer
    -> recommendation events
    -> crop_simulation/services.py
    -> PCSE execution
    -> scenario result

اگر بخواهید، قدم بعدی می‌توانم یک فایل دوم هم بسازم که فقط نمونه request/response واقعی برای هر تابع و هر سناریو را به‌صورت cookbook نشان بدهد.