19 KiB
مستند فرمولهای dashboard_data/cards
این فایل توضیح میدهد هر کارت در dashboard_data/cards چطور دادههای خروجی خود را محاسبه میکند، از چه فیلدهایی استفاده میکند، و چه fallbackهایی دارد.
منبع دادههای مشترک
کانتکست بیشتر کارتها از dashboard_data/context.py میآید:
sensor: رکورد اصلی سنسورlocation: لوکیشن سنسورdepths: دادههای عمق خاک ازSoilDepthDataforecasts: حداکثر ۷ پیشبینی آبوهوا از امروز به بعد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_moisture→moisturesensor.soil_ph→phsensor.electrical_conductivity→ecsensor.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 = 0humidity = 0windSpeed = 0chartData.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_moisture→moisture- میانگین
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_moisture→current_valuehistory[: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_moisture→moisture_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_moisture→base_moisturedepth.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.nitrogen→nitrogensensor.soil_moisture→moisture
فرمول 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.nitrogensensor.phosphorussensor.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.pyrecommendations_list.py
نکات مهم برای تیم
- چند اسم کارت با واقعیت محاسبهشان دقیقاً منطبق نیست؛ مثلاً
avg_soil_moistureواقعاً average چند منبع نیست. - بعضی trendها نسبت به history درستِ همان شاخص محاسبه نمیشوند؛ مخصوصاً در
sensor_values_list.py. - چند مقدار hard-coded هستند، مثل:
light_score = 85sensor_values_listبرای نور =850yield_prediction_chartبرای برداشت =+8%- سری ایدهآل در
sensor_radar_chart
- چند کارت بهجای مدل تحلیلی واقعی، از فرمولهای heuristic ساده استفاده میکنند.