Files
Ai/dashboard_data/cards/CARD_FORMULAS.md
T
2026-03-22 03:08:27 +03:30

19 KiB
Raw Blame History

مستند فرمول‌های dashboard_data/cards

این فایل توضیح می‌دهد هر کارت در dashboard_data/cards چطور داده‌های خروجی خود را محاسبه می‌کند، از چه فیلدهایی استفاده می‌کند، و چه fallbackهایی دارد.

منبع داده‌های مشترک

کانتکست بیشتر کارت‌ها از dashboard_data/context.py می‌آید:

  • sensor: رکورد اصلی سنسور
  • location: لوکیشن سنسور
  • depths: داده‌های عمق خاک از SoilDepthData
  • forecasts: حداکثر ۷ پیش‌بینی آب‌وهوا از امروز به بعد
  • history: حداکثر ۳۰ رکورد تاریخچه سنسور، مرتب‌شده از جدید به قدیم
  • plants: گیاه‌های متصل به سنسور
  • irrigation_methods: حداکثر ۵ روش آبیاری

توابع کمکی مشترک

این توابع در dashboard_data/card_utils.py استفاده می‌شوند:

  • safe_number(value, default=0): اگر مقدار None باشد، default برمی‌گرداند.
  • average(values, default=0): میانگین مقادیر غیر None را می‌دهد؛ اگر هیچ مقداری نبود default برمی‌گرداند.
  • latest_history_value(history, field_name, default=None): مقدار field_name را از جدیدترین رکورد history می‌گیرد.
  • compute_trend(current, previous):
    • diff = round(current_value - previous_value, 1)
    • trend = "positive" اگر diff >= 0، وگرنه "negative"

1) farm_overview_kpis.py

تابع سازنده: build_farm_overview_kpis

ورودی‌های اصلی

  • sensor.soil_moisturemoisture
  • sensor.soil_phph
  • sensor.electrical_conductivityec
  • sensor.soil_temperature
  • میانگین forecast.humidity_mean برای ۳ پیش‌بینی اول → humidity

فرمول‌ها

1. امتیاز سلامت مزرعه (farm_health_score)

health_score = clamp(
  round(
    100
    - abs(65 - moisture)
    - (abs(6.8 - ph) * 10)
    - (ec * 5)
  ),
  0,
  100
)

توضیح:

  • رطوبت ایده‌آل ۶۵٪ فرض شده.
  • pH ایده‌آل ۶.۸ فرض شده.
  • EC بالاتر، امتیاز را کم می‌کند.

آستانه‌های نمایش:

  • اگر health_score >= 70 → وضعیت خوب و رنگ success
  • در غیر این صورت → متوسط و رنگ warning

2. شاخص تنش آبی (water_stress_index)

water_stress = clamp(round(35 - (moisture / 2)), 0, 100)

توضیح:

  • هرچه رطوبت خاک بیشتر شود، تنش آبی کمتر می‌شود.

آستانه نمایش:

  • اگر water_stress <= 20پایین
  • در غیر این صورت → متوسط

3. ریسک بیماری (disease_risk)

disease_risk = clamp(
  round((humidity * 0.4) + (soil_temperature * 0.6) - 20),
  0,
  100
)

توضیح:

  • دمای خاک وزن ۶۰٪ دارد.
  • رطوبت هوا وزن ۴۰٪ دارد.

آستانه نمایش:

  • اگر disease_risk < 30پایین
  • در غیر این صورت → متوسط

4. میانگین رطوبت خاک (avg_soil_moisture)

avg_soil_moisture = round(moisture)

نکته:

  • اینجا عملاً فقط از sensor.soil_moisture فعلی استفاده می‌شود و واقعاً میانگین چند سنسور یا چند ناحیه محاسبه نمی‌شود.

آستانه نمایش:

  • اگر 45 <= moisture <= 75بهینه
  • در غیر این صورت → نیازمند بررسی

5. پیش‌بینی عملکرد (yield_prediction)

yield_prediction = round(max(5, health_score / 2.1), 1)

فرمول متن chip:

yield_chip = "+" + str(max(0, health_score - 50)) + "%"

6. ریسک آفات (pest_risk)

pest_risk = max(5, round(disease_risk * 0.7))

نکته:

  • ریسک آفات به‌صورت مستقیم از ۷۰٪ ریسک بیماری ساخته شده.

2) farm_weather_card.py

تابع سازنده: build_farm_weather_card

منطق کلی

اگر forecasts خالی باشد:

  • condition = "نامشخص"
  • temperature = 0
  • humidity = 0
  • windSpeed = 0
  • chartData.labels = []
  • chartData.series = [[]]

در غیر این صورت:

فرمول‌ها

1. وضعیت آب‌وهوا

condition = weather_condition(current_forecast.weather_code)

که weather_code با جدول WMO_CONDITIONS به متن فارسی تبدیل می‌شود.

2. دما

temperature = round(
  safe_number(current_forecast.temperature_mean, current_forecast.temperature_max)
)

یعنی:

  • اول temperature_mean
  • اگر None بود، temperature_max

3. رطوبت

humidity = round(average([current_forecast.humidity_mean], default=0))

نکته:

  • چون فقط یک مقدار داخل average قرار می‌گیرد، عملاً همان humidity_mean فعلی است.

4. سرعت باد

windSpeed = round(safe_number(current_forecast.wind_speed_max, 0))

5. نمودار دما

برای ۷ روز اول:

labels = [str(forecast.forecast_date) for forecast in forecasts[:7]]
series = [[round(safe_number(forecast.temperature_mean, 0)) for forecast in forecasts[:7]]]

3) farm_alerts_tracker.py

تابع سازنده: build_farm_alerts_tracker

ورودی‌ها

  • sensor.soil_moisturemoisture
  • میانگین humidity_mean برای ۳ forecast اول → humidity
  • temperature_min برای ۳ forecast اول

فرمول‌ها

1. هشدار کمبود آب

low_water_count = 2 if moisture < 45 else 0

2. هشدار ریسک قارچی

fungal_count = 1 if (humidity > 70 and moisture > 60) else 0

3. هشدار یخبندان

frost_count = count(
  forecast for first 3 forecasts
  if temperature_min <= 0
)

در کد:

frost_count = sum(
  1 for forecast in forecasts[:3]
  if safe_number(forecast.temperature_min, 10) <= 0
)

4. مجموع هشدارها

totalAlerts = low_water_count + fungal_count + frost_count

5. مقدار radial bar

radialBarValue = min(100, totalAlerts * 10)

4) sensor_values_list.py

تابع سازنده: build_sensor_values_list

این کارت برای هر آیتم، مقدار فعلی و trend را می‌سازد.

فرمول trend

برای هر سنسور که از compute_trend استفاده می‌کند:

trendNumber = round(current - previous, 1)
trend = "positive" if trendNumber >= 0 else "negative"

آیتم‌ها

1. دمای هوا

title = round(current_weather.temperature_mean or 0) + "°C"
previous = latest_history_value(history, "soil_temperature", 0)

نکته مهم:

  • trend دمای هوا با soil_temperature از history مقایسه می‌شود، نه با history دمای هوا.

2. دمای خاک

current = sensor.soil_temperature
previous = latest_history_value(history, "soil_temperature", 0)

3. رطوبت هوا

current = current_weather.humidity_mean or 0
previous = 0

نکته:

  • همیشه نسبت به صفر trend می‌گیرد، نه history.

4. رطوبت خاک

current = sensor.soil_moisture
previous = latest_history_value(history, "soil_moisture", 0)

5. pH خاک

current = sensor.soil_ph
previous = latest_history_value(history, "soil_ph", 0)

6. هدایت الکتریکی

current = sensor.electrical_conductivity
previous = latest_history_value(history, "electrical_conductivity", 0)

7. شدت نور

title = "850"
trendNumber = 0
trend = "positive"

نکته:

  • این مقدار کاملاً ثابت (hard-coded) است و فرمولی ندارد.

8. سرعت باد

current = current_weather.wind_speed_max or 0
previous = 0

نکته:

  • trend سرعت باد هم نسبت به صفر محاسبه می‌شود.

5) sensor_radar_chart.py

تابع سازنده: build_sensor_radar_chart

تابع نرمال‌سازی

to_score(value, lower, upper):
  if value is None -> 0
  if value <= lower -> 0
  if value >= upper -> 100
  else -> round(((value - lower) / (upper - lower)) * 100)

سری «امروز»

به‌ترتیب:

soil_temperature_score = to_score(sensor.soil_temperature, 0, 40)
soil_moisture_score = to_score(sensor.soil_moisture, 0, 100)
soil_ph_score = to_score(sensor.soil_ph, 0, 14)
ec_score = to_score(sensor.electrical_conductivity, 0, 5)
light_score = 85
wind_score = to_score(current_weather.wind_speed_max if current_weather else 0, 0, 30)

خروجی:

series[0].data = [
  soil_temperature_score,
  soil_moisture_score,
  soil_ph_score,
  ec_score,
  85,
  wind_score
]

سری «ایده‌آل»

[80, 70, 75, 75, 90, 50]

نکته:

  • این مقادیر ثابت هستند و از دیتابیس محاسبه نمی‌شوند.

6) sensor_comparison_chart.py

تابع سازنده: build_sensor_comparison_chart

ورودی‌ها

  • current_sensor.soil_moisturecurrent_value
  • history[:7] → داده‌های هفته جاری
  • history[7:14] → داده‌های هفته قبل

فرمول‌ها

1. مقدار فعلی

currentValue = round(sensor.soil_moisture)

2. سری هفته جاری

recent = reversed(history[:7])
this_week = [round(item.soil_moisture or current_value) for item in recent]

اگر کمتر از ۷ مقدار باشد:

while len(this_week) < 7:
  this_week.append(current_value)

3. سری هفته قبل

previous = reversed(history[7:14])
last_week = [round(item.soil_moisture or (current_value - 5)) for item in previous]

اگر کمتر از ۷ مقدار باشد:

while len(last_week) < 7:
  last_week.append(max(0, current_value - 5))

4. درصد تغییر نسبت به هفته قبل

avg_this = sum(this_week) / len(this_week)
avg_last = sum(last_week) / len(last_week)
delta = round(((avg_this - avg_last) / avg_last) * 100) if avg_last else 0

نمایش متن:

vsLastWeek = f"{'+' if delta >= 0 else ''}{delta}%"
vsLastWeekValue = delta

5. دسته‌بندی روزها

روزهای ۷ روز اخیر با نام فارسی weekday ساخته می‌شوند:

categories = [
  PERSIAN_WEEKDAYS[(today - offset_days).weekday()]
  for offset_days in range(6, -1, -1)
]

7) anomaly_detection_card.py

تابع سازنده: build_anomaly_detection_card

این کارت anomalyها را فقط برای دو شاخص تولید می‌کند: رطوبت خاک و pH خاک.

1. anomaly رطوبت خاک

فقط وقتی ساخته می‌شود که:

moisture < 45

ساختار خروجی:

value = round(moisture) + "%"
expected = "45-65%"
deviation = round(moisture - 55) + "%"
severity = "warning"

نکته:

  • deviation نسبت به نقطه مرجع ۵۵٪ محاسبه می‌شود، نه مرز ۴۵٪.

2. anomaly pH خاک

فقط وقتی ساخته می‌شود که:

soil_ph < 6 or soil_ph > 7

ساختار خروجی:

value = format(soil_ph, ".1f")
expected = "6.0-7.0"
deviation = round(soil_ph - 6.5, 1)
severity = "error" if (soil_ph < 5.5 or soil_ph > 7.5) else "warning"

8) farm_alerts_timeline.py

تابع سازنده: build_farm_alerts_timeline

منطق

این کارت هیچ فرمول داخلی ندارد و داده را مستقیماً از ai_bundle برمی‌دارد:

alerts = ai_bundle.get("timeline", [])

9) water_need_prediction.py

تابع سازنده: build_water_need_prediction

برای ۷ forecast اول:

فرمول نیاز آبی روزانه

et0 = safe_number(forecast.et0, 4)
rain = safe_number(forecast.precipitation, 0)
need = max(0, round((et0 * 100) - (rain * 20)))

توضیح:

  • ET0 در ۱۰۰ ضرب می‌شود.
  • بارش در ۲۰ ضرب و از آن کم می‌شود.
  • مقدار منفی به صفر clamp می‌شود.

خروجی نهایی

daily_needs = [need for first 7 forecasts]
totalNext7Days = sum(daily_needs)
categories = ["روز 1", "روز 2", ...]
series = [{"name": "نیاز آبی", "data": daily_needs}]

واحد خروجی:

unit = "m³"

10) harvest_prediction_card.py

تابع سازنده: build_harvest_prediction_card

ورودی‌ها

  • میانگین temperature_mean تمام forecastها → avg_temp
  • sensor.soil_moisturemoisture_factor
  • نام اولین گیاه → plant_name

فرمول‌ها

1. میانگین دما

avg_temp = average([forecast.temperature_mean for forecast in forecasts], default=24)

2. فاکتور رطوبت

moisture_factor = sensor.soil_moisture if available else 50

3. روز باقی‌مانده تا برداشت

days_until = max(10, int(90 - avg_temp - (moisture_factor / 5)))

توضیح:

  • هرچه دمای متوسط بیشتر باشد، days_until کمتر می‌شود.
  • هرچه رطوبت خاک بیشتر باشد، days_until کمتر می‌شود.
  • حداقل ۱۰ روز است.

4. تاریخ برداشت و بازه بهینه

target_date = today + days_until
optimalWindowStart = target_date - 3 days
optimalWindowEnd = target_date + 3 days

5. توضیح متنی

اگر گیاه وجود داشته باشد:

description = "بر اساس دمای فعلی، رطوبت خاک و اطلاعات <plant_name>. بازه بهینه برداشت محاسبه شده است."

اگر گیاهی وجود نداشته باشد:

plant_name = "محصول"

11) yield_prediction_chart.py

تابع سازنده: build_yield_prediction_chart

فرمول‌ها

1. مقدار پایه

base = max(10, round(sensor.soil_moisture * 0.6))

2. سری سال جاری

current_year = [
  base + 0,
  base + 2,
  base + 4,
  base + 6,
  base + 8,
  base + 10,
  base + 12,
  base + 11,
  base + 9,
  base + 7,
  base + 5,
  base + 4
]

3. سری سال گذشته

last_year = [value - 3 for value in current_year]

4. خلاصه کارت

عملکرد پیش‌بینی‌شده:

summary[0].amount = current_year[9] + " تن"

یعنی مقدار ماه دهم لیست (اندیس ۹).

تاریخ برداشت:

harvest_month = "حدود " + str(today.month)
summary[1].amount = "+8%"

نکته:

  • +8% مقدار ثابت است و از فرمول نیامده.

12) soil_moisture_heatmap.py

تابع سازنده: build_soil_moisture_heatmap

ورودی‌ها

  • sensor.soil_moisturebase_moisture
  • depth.wv0033 برای هر لایه عمق خاک

منطق اولیه

hours = ["۶ ص", "۸ ص", "۱۰ ص", "۱۲ ظ", "۱۴ ع", "۱۶ ع", "۱۸ ع"]

اگر depths خالی باشد:

depths = [None, None]

فرمول zone offset

برای هر depth:

depth_offset = 0 if depth is None else round(depth.wv0033 / 10)

فرمول هر خانه heatmap

برای هر zone و هر ساعت:

value = clamp(
  round(base_moisture + depth_offset - abs(3 - hour_index) * 2),
  0,
  100
)

توضیح:

  • hour_index = 3 مرکز نمودار است و بیشترین مقدار را می‌دهد.
  • هرچه از مرکز دورتر شویم، به ازای هر پله ۲ واحد کم می‌شود.

ساختار خروجی هر نقطه:

{"x": hour, "y": value}

13) ndvi_health_card.py

تابع سازنده: build_ndvi_health_card

ورودی‌ها

  • sensor.nitrogennitrogen
  • sensor.soil_moisturemoisture

فرمول NDVI

ndvi = round(
  clamp(((nitrogen / 100) * 0.4) + ((moisture / 100) * 0.6), 0.1, 0.95),
  2
)

توضیح:

  • نیتروژن ۴۰٪ وزن دارد.
  • رطوبت خاک ۶۰٪ وزن دارد.
  • خروجی بین 0.1 و 0.95 محدود می‌شود.

وضعیت‌های متنی

1. تنش نیتروژن

"پایین" if nitrogen >= 30 else "بالا"

2. سلامت محصول

"خوب" if ndvi >= 0.65 else "متوسط"

14) recommendations_list.py

تابع سازنده: build_recommendations_list

منطق

این کارت فرمول داخلی ندارد:

recommendations = ai_bundle.get("recommendations", [])

15) economic_overview.py

تابع سازنده: build_economic_overview

ورودی‌ها

  • forecast.et0 برای ۶ forecast اول
  • sensor.nitrogen
  • sensor.phosphorus
  • sensor.potassium

فرمول‌ها

1. هزینه آب

water_cost = round(sum(max(0, forecast.et0 * 20) for first 6 forecasts))

در کد با fallback:

water_cost = round(
  sum(max(0, safe_number(forecast.et0, 0) * 20) for forecast in forecasts[:6])
)

2. نیاز کودی

fertilizer_need = round((nitrogen + phosphorus + potassium) / 3)

با fallback صفر برای هر کدام.

3. پیش‌بینی درآمد

revenue = round(max(1000, water_cost * 4.5))

4. صرفه‌جویی آب هوشمند

smart_saving = round(water_cost * 0.18)

chartSeries

سری هزینه آب

water_series = [max(1, round(water_cost / 6)) for _ in range(6)]

سری کود

fertilizer_series = [max(1, round(fertilizer_need / 6)) for _ in range(6)]

نکته:

  • هر دو سری، ۶ مقدار تکراری یکسان تولید می‌کنند و روند ماهانه واقعی ندارند.

کارت‌های بدون فرمول محاسباتی

این کارت‌ها فقط داده را از ai_bundle می‌خوانند:

  • farm_alerts_timeline.py
  • recommendations_list.py

نکات مهم برای تیم

  • چند اسم کارت با واقعیت محاسبه‌شان دقیقاً منطبق نیست؛ مثلاً avg_soil_moisture واقعاً average چند منبع نیست.
  • بعضی trendها نسبت به history درستِ همان شاخص محاسبه نمی‌شوند؛ مخصوصاً در sensor_values_list.py.
  • چند مقدار hard-coded هستند، مثل:
    • light_score = 85
    • sensor_values_list برای نور = 850
    • yield_prediction_chart برای برداشت = +8%
    • سری ایده‌آل در sensor_radar_chart
  • چند کارت به‌جای مدل تحلیلی واقعی، از فرمول‌های heuristic ساده استفاده می‌کنند.