Spaces:
Sleeping
Sleeping
File size: 7,501 Bytes
2f39e98 6f9f2ac def034a 2f39e98 6f9f2ac def034a 2f39e98 6f9f2ac 3d28307 6f9f2ac f528de8 3d28307 6f9f2ac 2f39e98 6f9f2ac def034a 6f9f2ac 2f39e98 6f9f2ac 2f39e98 6f9f2ac 81d47a9 6f9f2ac 2f39e98 81d47a9 2f39e98 6f9f2ac 2f39e98 2dbbc07 2f39e98 6f9f2ac 2f39e98 def034a 2f39e98 f528de8 2f39e98 3d28307 2f39e98 f528de8 2f39e98 f528de8 b0ae086 f528de8 2f39e98 6f9f2ac 3d28307 2f39e98 6f9f2ac 2f39e98 31f5215 2f39e98 6f9f2ac 2f39e98 6f9f2ac 2f39e98 6f9f2ac 2f39e98 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | from typing import Optional, Dict, Any
from app.services.bbch_service import get_stage_by_code
from app.services.sensor_service import SensorService
from app.core.time_engine import TimeEngine
from app.schemas.sensor import SensorData
from app.services.phenology_service import PhenologyService
from app.utils.logger import setup_logger
logger = setup_logger()
class ContextEngine:
def __init__(self):
self.sensor_service = SensorService()
self.time_engine = TimeEngine()
self.phenology_service = PhenologyService()
async def build_farming_context(
self,
crop_type: Optional[str] = None,
bbch_stage: Optional[str] = None,
crop_source: Optional[str] = None,
bbch_source: Optional[str] = None,
sensor_data: Optional[SensorData] = None,
weather_data: Optional[Dict] = None,
disease_detected: Optional[str] = None,
disease_image_analyzed: bool = False,
sensors_enabled: bool = True,
weather_enabled: bool = True,
) -> Dict[str, Any]:
context: Dict[str, Any] = {
"time_context": {},
"crop_context": {},
"phenology_context": {},
"weather_context": {},
"sensor_context": {},
"disease_context": {},
"recommendations": [],
}
# Time
context["time_context"] = {
"date": self.time_engine.get_current_date(),
"time": self.time_engine.get_current_time(),
"time_of_day": self.time_engine.get_time_of_day(),
"season": self.time_engine.get_season(),
}
# Crop + BBCH
if crop_type:
context["crop_context"]["crop"] = crop_type
context["crop_context"]["crop_source"] = crop_source
if bbch_stage:
stage_info = get_stage_by_code(bbch_stage, crop_type)
if stage_info:
context["crop_context"]["bbch_stage"] = {
"code": bbch_stage,
"name": stage_info.name,
"description": stage_info.description,
}
context["crop_context"]["growth_phase"] = self._get_growth_phase(bbch_stage)
# Phenology
if crop_type and bbch_stage:
pheno = self.phenology_service.get_phenology(crop_type, bbch_stage)
if pheno:
context["phenology_context"] = pheno
if pheno.get("needs"):
context["recommendations"].append(
f"Stage needs: {', '.join(pheno['needs'][:3])}"
)
if pheno.get("risks"):
context["recommendations"].append(
f"Key risks: {', '.join(pheno['risks'][:3])}"
)
# Weather
if weather_enabled:
if weather_data and weather_data.get("forecast"):
context["weather_context"] = weather_data
first = weather_data["forecast"][0]
if first.get("rain_chance", 0) > 70:
context["recommendations"].append(
f"High rain ({first['rain_chance']}%) tomorrow — delay irrigation."
)
if first.get("temp_max", 0) > 30:
context["recommendations"].append(
f"Heat ({first['temp_max']}°C) forecast — irrigate early morning."
)
if first.get("temp_max", 0) < 5:
context["recommendations"].append(
f"Frost risk ({first['temp_max']}°C) — protect crops."
)
else:
context["weather_context"] = {"no_data": True}
else:
context["weather_context"] = {"disabled": True}
# Sensors
if sensors_enabled:
if sensor_data:
# Always inject raw sensor values — analysis requires crop+bbch but raw data does not
raw = {
"soil_moisture": sensor_data.soil_moisture,
"soil_temperature": sensor_data.soil_temperature,
"air_temperature": sensor_data.air_temperature,
"humidity": sensor_data.humidity,
}
if crop_type and bbch_stage:
try:
analysis = self.sensor_service.analyze_sensor_data(
sensor_data, crop_type, bbch_stage
)
context["sensor_context"] = {
**raw,
"status": analysis.status,
"soil_moisture_status": analysis.soil_moisture_status,
"soil_temperature_status": analysis.soil_temperature_status,
"air_temperature_status": analysis.air_temperature_status,
"humidity_status": analysis.humidity_status,
"message": analysis.message,
}
if analysis.status in ("warning", "critical"):
context["recommendations"].append(f"Sensor alert: {analysis.message}")
except Exception as e:
logger.warning(f"Sensor analysis error: {e}")
context["sensor_context"] = {**raw, "status": "unknown", "message": ""}
else:
# No crop/BBCH context — inject raw values without analysis
context["sensor_context"] = {**raw, "status": "unknown", "message": ""}
else:
context["sensor_context"] = {"no_data": True}
else:
context["sensor_context"] = {"disabled": True}
# Disease (from image analysis or explicit report)
if disease_detected and disease_detected.lower() not in ("healthy", "none", ""):
context["disease_context"] = {
"detected": True,
"disease": disease_detected,
"natural_description": (
f"{disease_detected} (from image)" if disease_image_analyzed
else f"{disease_detected} (reported)"
),
"analyzed_from_image": disease_image_analyzed,
}
context["recommendations"].append(
f"Disease detected: {disease_detected}. Check Agridatalogia products or active ingredient recommendations."
)
return context
def _get_growth_phase(self, bbch_code: str) -> str:
try:
code_num = int(bbch_code.split("-")[0]) if "-" in bbch_code else int(bbch_code)
except (ValueError, AttributeError):
return "Unknown"
if code_num < 10:
return "Germination"
elif code_num < 20:
return "Leaf development"
elif code_num < 30:
return "Tillering / Branching"
elif code_num < 40:
return "Stem elongation"
elif code_num < 50:
return "Booting / Heading"
elif code_num < 60:
return "Inflorescence emergence"
elif code_num < 70:
return "Flowering"
elif code_num < 80:
return "Fruit development"
elif code_num < 90:
return "Ripening"
else:
return "Senescence"
|