# مستند فرمول‌های `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_moisture` → `moisture` - `sensor.soil_ph` → `ph` - `sensor.electrical_conductivity` → `ec` - `sensor.soil_temperature` - میانگین `forecast.humidity_mean` برای ۳ پیش‌بینی اول → `humidity` ### فرمول‌ها #### 1. امتیاز سلامت مزرعه (`farm_health_score`) ```text 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`) ```text water_stress = clamp(round(35 - (moisture / 2)), 0, 100) ``` توضیح: - هرچه رطوبت خاک بیشتر شود، تنش آبی کمتر می‌شود. آستانه نمایش: - اگر `water_stress <= 20` → `پایین` - در غیر این صورت → `متوسط` #### 3. ریسک بیماری (`disease_risk`) ```text disease_risk = clamp( round((humidity * 0.4) + (soil_temperature * 0.6) - 20), 0, 100 ) ``` توضیح: - دمای خاک وزن ۶۰٪ دارد. - رطوبت هوا وزن ۴۰٪ دارد. آستانه نمایش: - اگر `disease_risk < 30` → `پایین` - در غیر این صورت → `متوسط` #### 4. میانگین رطوبت خاک (`avg_soil_moisture`) ```text avg_soil_moisture = round(moisture) ``` نکته: - اینجا عملاً فقط از `sensor.soil_moisture` فعلی استفاده می‌شود و واقعاً میانگین چند سنسور یا چند ناحیه محاسبه نمی‌شود. آستانه نمایش: - اگر `45 <= moisture <= 75` → `بهینه` - در غیر این صورت → `نیازمند بررسی` #### 5. پیش‌بینی عملکرد (`yield_prediction`) ```text yield_prediction = round(max(5, health_score / 2.1), 1) ``` فرمول متن chip: ```text yield_chip = "+" + str(max(0, health_score - 50)) + "%" ``` #### 6. ریسک آفات (`pest_risk`) ```text 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. وضعیت آب‌وهوا ```text condition = weather_condition(current_forecast.weather_code) ``` که `weather_code` با جدول `WMO_CONDITIONS` به متن فارسی تبدیل می‌شود. #### 2. دما ```text temperature = round( safe_number(current_forecast.temperature_mean, current_forecast.temperature_max) ) ``` یعنی: - اول `temperature_mean` - اگر `None` بود، `temperature_max` #### 3. رطوبت ```text humidity = round(average([current_forecast.humidity_mean], default=0)) ``` نکته: - چون فقط یک مقدار داخل `average` قرار می‌گیرد، عملاً همان `humidity_mean` فعلی است. #### 4. سرعت باد ```text windSpeed = round(safe_number(current_forecast.wind_speed_max, 0)) ``` #### 5. نمودار دما برای ۷ روز اول: ```text 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. هشدار کمبود آب ```text low_water_count = 2 if moisture < 45 else 0 ``` #### 2. هشدار ریسک قارچی ```text fungal_count = 1 if (humidity > 70 and moisture > 60) else 0 ``` #### 3. هشدار یخبندان ```text frost_count = count( forecast for first 3 forecasts if temperature_min <= 0 ) ``` در کد: ```text frost_count = sum( 1 for forecast in forecasts[:3] if safe_number(forecast.temperature_min, 10) <= 0 ) ``` #### 4. مجموع هشدارها ```text totalAlerts = low_water_count + fungal_count + frost_count ``` #### 5. مقدار radial bar ```text radialBarValue = min(100, totalAlerts * 10) ``` --- ## 4) `sensor_values_list.py` تابع سازنده: `build_sensor_values_list` این کارت برای هر آیتم، مقدار فعلی و trend را می‌سازد. ### فرمول trend برای هر سنسور که از `compute_trend` استفاده می‌کند: ```text trendNumber = round(current - previous, 1) trend = "positive" if trendNumber >= 0 else "negative" ``` ### آیتم‌ها #### 1. دمای هوا ```text title = round(current_weather.temperature_mean or 0) + "°C" previous = latest_history_value(history, "soil_temperature", 0) ``` نکته مهم: - trend دمای هوا با `soil_temperature` از history مقایسه می‌شود، نه با history دمای هوا. #### 2. دمای خاک ```text current = sensor.soil_temperature previous = latest_history_value(history, "soil_temperature", 0) ``` #### 3. رطوبت هوا ```text current = current_weather.humidity_mean or 0 previous = 0 ``` نکته: - همیشه نسبت به صفر trend می‌گیرد، نه history. #### 4. رطوبت خاک ```text current = sensor.soil_moisture previous = latest_history_value(history, "soil_moisture", 0) ``` #### 5. pH خاک ```text current = sensor.soil_ph previous = latest_history_value(history, "soil_ph", 0) ``` #### 6. هدایت الکتریکی ```text current = sensor.electrical_conductivity previous = latest_history_value(history, "electrical_conductivity", 0) ``` #### 7. شدت نور ```text title = "850" trendNumber = 0 trend = "positive" ``` نکته: - این مقدار کاملاً ثابت (hard-coded) است و فرمولی ندارد. #### 8. سرعت باد ```text current = current_weather.wind_speed_max or 0 previous = 0 ``` نکته: - trend سرعت باد هم نسبت به صفر محاسبه می‌شود. --- ## 5) `sensor_radar_chart.py` تابع سازنده: `build_sensor_radar_chart` ### تابع نرمال‌سازی ```text 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) ``` ### سری «امروز» به‌ترتیب: ```text 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) ``` خروجی: ```text series[0].data = [ soil_temperature_score, soil_moisture_score, soil_ph_score, ec_score, 85, wind_score ] ``` ### سری «ایده‌آل» ```text [80, 70, 75, 75, 90, 50] ``` نکته: - این مقادیر ثابت هستند و از دیتابیس محاسبه نمی‌شوند. --- ## 6) `sensor_comparison_chart.py` تابع سازنده: `build_sensor_comparison_chart` ### ورودی‌ها - `current_sensor.soil_moisture` → `current_value` - `history[:7]` → داده‌های هفته جاری - `history[7:14]` → داده‌های هفته قبل ### فرمول‌ها #### 1. مقدار فعلی ```text currentValue = round(sensor.soil_moisture) ``` #### 2. سری هفته جاری ```text recent = reversed(history[:7]) this_week = [round(item.soil_moisture or current_value) for item in recent] ``` اگر کمتر از ۷ مقدار باشد: ```text while len(this_week) < 7: this_week.append(current_value) ``` #### 3. سری هفته قبل ```text previous = reversed(history[7:14]) last_week = [round(item.soil_moisture or (current_value - 5)) for item in previous] ``` اگر کمتر از ۷ مقدار باشد: ```text while len(last_week) < 7: last_week.append(max(0, current_value - 5)) ``` #### 4. درصد تغییر نسبت به هفته قبل ```text 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 ``` نمایش متن: ```text vsLastWeek = f"{'+' if delta >= 0 else ''}{delta}%" vsLastWeekValue = delta ``` #### 5. دسته‌بندی روزها روزهای ۷ روز اخیر با نام فارسی weekday ساخته می‌شوند: ```text 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 رطوبت خاک فقط وقتی ساخته می‌شود که: ```text moisture < 45 ``` ساختار خروجی: ```text value = round(moisture) + "%" expected = "45-65%" deviation = round(moisture - 55) + "%" severity = "warning" ``` نکته: - deviation نسبت به نقطه مرجع ۵۵٪ محاسبه می‌شود، نه مرز ۴۵٪. ### 2. anomaly pH خاک فقط وقتی ساخته می‌شود که: ```text soil_ph < 6 or soil_ph > 7 ``` ساختار خروجی: ```text 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` برمی‌دارد: ```text alerts = ai_bundle.get("timeline", []) ``` --- ## 9) `water_need_prediction.py` تابع سازنده: `build_water_need_prediction` برای ۷ forecast اول: ### فرمول نیاز آبی روزانه ```text et0 = safe_number(forecast.et0, 4) rain = safe_number(forecast.precipitation, 0) need = max(0, round((et0 * 100) - (rain * 20))) ``` توضیح: - `ET0` در ۱۰۰ ضرب می‌شود. - بارش در ۲۰ ضرب و از آن کم می‌شود. - مقدار منفی به صفر clamp می‌شود. ### خروجی نهایی ```text daily_needs = [need for first 7 forecasts] totalNext7Days = sum(daily_needs) categories = ["روز 1", "روز 2", ...] series = [{"name": "نیاز آبی", "data": daily_needs}] ``` واحد خروجی: ```text unit = "m³" ``` --- ## 10) `harvest_prediction_card.py` تابع سازنده: `build_harvest_prediction_card` ### ورودی‌ها - میانگین `temperature_mean` تمام forecastها → `avg_temp` - `sensor.soil_moisture` → `moisture_factor` - نام اولین گیاه → `plant_name` ### فرمول‌ها #### 1. میانگین دما ```text avg_temp = average([forecast.temperature_mean for forecast in forecasts], default=24) ``` #### 2. فاکتور رطوبت ```text moisture_factor = sensor.soil_moisture if available else 50 ``` #### 3. روز باقی‌مانده تا برداشت ```text days_until = max(10, int(90 - avg_temp - (moisture_factor / 5))) ``` توضیح: - هرچه دمای متوسط بیشتر باشد، `days_until` کمتر می‌شود. - هرچه رطوبت خاک بیشتر باشد، `days_until` کمتر می‌شود. - حداقل ۱۰ روز است. #### 4. تاریخ برداشت و بازه بهینه ```text target_date = today + days_until optimalWindowStart = target_date - 3 days optimalWindowEnd = target_date + 3 days ``` #### 5. توضیح متنی اگر گیاه وجود داشته باشد: ```text description = "بر اساس دمای فعلی، رطوبت خاک و اطلاعات . بازه بهینه برداشت محاسبه شده است." ``` اگر گیاهی وجود نداشته باشد: ```text plant_name = "محصول" ``` --- ## 11) `yield_prediction_chart.py` تابع سازنده: `build_yield_prediction_chart` ### فرمول‌ها #### 1. مقدار پایه ```text base = max(10, round(sensor.soil_moisture * 0.6)) ``` #### 2. سری سال جاری ```text 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. سری سال گذشته ```text last_year = [value - 3 for value in current_year] ``` #### 4. خلاصه کارت عملکرد پیش‌بینی‌شده: ```text summary[0].amount = current_year[9] + " تن" ``` یعنی مقدار ماه دهم لیست (اندیس ۹). تاریخ برداشت: ```text harvest_month = "حدود " + str(today.month) summary[1].amount = "+8%" ``` نکته: - `+8%` مقدار ثابت است و از فرمول نیامده. --- ## 12) `soil_moisture_heatmap.py` تابع سازنده: `build_soil_moisture_heatmap` ### ورودی‌ها - `sensor.soil_moisture` → `base_moisture` - `depth.wv0033` برای هر لایه عمق خاک ### منطق اولیه ```text hours = ["۶ ص", "۸ ص", "۱۰ ص", "۱۲ ظ", "۱۴ ع", "۱۶ ع", "۱۸ ع"] ``` اگر `depths` خالی باشد: ```text depths = [None, None] ``` ### فرمول zone offset برای هر depth: ```text depth_offset = 0 if depth is None else round(depth.wv0033 / 10) ``` ### فرمول هر خانه heatmap برای هر zone و هر ساعت: ```text value = clamp( round(base_moisture + depth_offset - abs(3 - hour_index) * 2), 0, 100 ) ``` توضیح: - `hour_index = 3` مرکز نمودار است و بیشترین مقدار را می‌دهد. - هرچه از مرکز دورتر شویم، به ازای هر پله ۲ واحد کم می‌شود. ساختار خروجی هر نقطه: ```text {"x": hour, "y": value} ``` --- ## 13) `ndvi_health_card.py` تابع سازنده: `build_ndvi_health_card` ### ورودی‌ها - `sensor.nitrogen` → `nitrogen` - `sensor.soil_moisture` → `moisture` ### فرمول NDVI ```text ndvi = round( clamp(((nitrogen / 100) * 0.4) + ((moisture / 100) * 0.6), 0.1, 0.95), 2 ) ``` توضیح: - نیتروژن ۴۰٪ وزن دارد. - رطوبت خاک ۶۰٪ وزن دارد. - خروجی بین `0.1` و `0.95` محدود می‌شود. ### وضعیت‌های متنی #### 1. تنش نیتروژن ```text "پایین" if nitrogen >= 30 else "بالا" ``` #### 2. سلامت محصول ```text "خوب" if ndvi >= 0.65 else "متوسط" ``` --- ## 14) `recommendations_list.py` تابع سازنده: `build_recommendations_list` ### منطق این کارت فرمول داخلی ندارد: ```text recommendations = ai_bundle.get("recommendations", []) ``` --- ## 15) `economic_overview.py` تابع سازنده: `build_economic_overview` ### ورودی‌ها - `forecast.et0` برای ۶ forecast اول - `sensor.nitrogen` - `sensor.phosphorus` - `sensor.potassium` ### فرمول‌ها #### 1. هزینه آب ```text water_cost = round(sum(max(0, forecast.et0 * 20) for first 6 forecasts)) ``` در کد با fallback: ```text water_cost = round( sum(max(0, safe_number(forecast.et0, 0) * 20) for forecast in forecasts[:6]) ) ``` #### 2. نیاز کودی ```text fertilizer_need = round((nitrogen + phosphorus + potassium) / 3) ``` با fallback صفر برای هر کدام. #### 3. پیش‌بینی درآمد ```text revenue = round(max(1000, water_cost * 4.5)) ``` #### 4. صرفه‌جویی آب هوشمند ```text smart_saving = round(water_cost * 0.18) ``` ### chartSeries #### سری هزینه آب ```text water_series = [max(1, round(water_cost / 6)) for _ in range(6)] ``` #### سری کود ```text 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 ساده استفاده می‌کنند.