# Configuration: paths, IMS station/channel config, model params from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent DATA_DIR = PROJECT_ROOT / "Data" IMS_CACHE_DIR = DATA_DIR / "ims" PROCESSED_DIR = DATA_DIR / "processed" OUTPUTS_DIR = PROJECT_ROOT / "outputs" # On-site sensor data (Stage 1) SEYMOUR_DIR = DATA_DIR / "Seymour" SENSORS_WIDE_PATH = SEYMOUR_DIR / "sensors_wide.csv" SENSORS_WIDE_SAMPLE_PATH = SEYMOUR_DIR / "sensors_wide_sample.csv" SENSORS_WIDE_METADATA_PATH = SEYMOUR_DIR / "sensors_wide_metadata.csv" # IMS API (station 43 - Sde Boker) IMS_STATION_ID = 43 IMS_BASE_URL = "https://api.ims.gov.il/v1/envista/stations" # Station 43 channel IDs -> output column names (from --list-channels) IMS_CHANNEL_MAP = { 6: "air_temperature_c", # TD 8: "tdmax_c", # TDmax 9: "tdmin_c", # TDmin 10: "ghi_w_m2", # Grad (GHI) 7: "rh_percent", # RH 20: "rain_mm", # Rain 3: "wind_speed_ms", # WS # Station 43 has no BP; WD optional: 4 } # Preprocessor TRAIN_RATIO = 0.75 # Growing season: vine is dormant Oct–April (no photosynthesis). Keep May–September only. GROWING_SEASON_MONTHS = (5, 6, 7, 8, 9) # May through September # Site location (Sde Boker, Israel) SITE_LATITUDE = 30.87 SITE_LONGITUDE = 34.79 SITE_ALTITUDE = 475.0 # meters # Agrivoltaic panel geometry PANEL_WIDTH = 1.13 # m (E-W dimension) PANEL_HEIGHT = 2.05 # m above ground ROW_SPACING = 3.0 # m between vine row centers CANOPY_HEIGHT = 1.2 # m (VSP trellis) CANOPY_WIDTH = 0.6 # m ROW_AZIMUTH = 315.0 # degrees CW from north (NW–SE row orientation) # === TRACKER CONSTRAINTS === TRACKER_MAX_ANGLE = 60.0 # degrees — mechanical limit of single-axis tracker TRACKER_GCR = 0.377 # ground coverage ratio (panel_width / row_spacing) # === TRACKER ID MAPPING === # Canonical mapping between integer IDs (DB/fleet) and string names (ThingsBoard) TRACKER_ID_MAP = { 501: "Tracker501", 502: "Tracker502", 503: "Tracker503", 509: "Tracker509", } # --------------------------------------------------------------------------- # SolarWine 2.0 — Control System Parameters # --------------------------------------------------------------------------- # === PV SYSTEM === SYSTEM_CAPACITY_KW = 48.0 # DC nameplate capacity (from ThingsBoard Digital Twin) STC_IRRADIANCE_W_M2 = 1000.0 # Standard Test Conditions irradiance for normalisation # === ENERGY BUDGET === # Hard ceiling: fraction of annual PV generation the vines can "spend" on shading. MAX_ENERGY_REDUCTION_PCT = 5.0 # % of annual generation (user's hard ceiling) ANNUAL_RESERVE_PCT = 15.0 # emergency reserve — not allocated to any month WEEKLY_RESERVE_PCT = 20.0 # within-week flexibility buffer DAILY_MARGIN_PCT = 20.0 # real-time response pool within the day # Monthly budget weights — must sum to 1.0 across growing season. # May budget is very low (extreme heat emergency only); the 3D model will # naturally produce no effective dose in most May slots because fruit-set # geometry and low stress do not warrant intervention. MONTHLY_BUDGET_WEIGHTS = { 5: 0.02, # May — near-zero; extreme emergency only (fruit-set geometry protects naturally) 6: 0.05, # June — rare; only extreme heat spikes 7: 0.45, # July — peak heat; primary shading window 8: 0.40, # August — sustained heat; fruit ripening / sunburn risk 9: 0.08, # Sept — occasional late heat waves } # === NO-SHADE WINDOWS (hard constraints — shading PROHIBITED) === # These are enforced by the InterventionGate AND the chatbot guardrails. NO_SHADE_BEFORE_HOUR = 10 # local solar time — morning light is sacred for carbon fixation NO_SHADE_MONTHS = [5] # May — full spring exposure for flowering / fruit set NO_SHADE_GHI_BELOW = 300 # W/m² — overcast, already diffuse; no stress to relieve NO_SHADE_TLEAF_BELOW = 28.0 # °C — below RuBP→Rubisco transition zone; vine wants light # === SHADE-ELIGIBLE CONDITIONS (ALL must be true to allow intervention) === SHADE_ELIGIBLE_TLEAF_ABOVE = 30.0 # °C — Semillon Rubisco transition (heat bottleneck) SHADE_ELIGIBLE_CWSI_ABOVE = 0.4 # moderate water stress confirmed by sensors SHADE_ELIGIBLE_GHI_ABOVE = 400 # W/m² — significant direct radiation load (night/deep-overcast guard) SHADE_ELIGIBLE_HOURS = (10, 16) # local solar time window (10:00–16:00) # Minimum GHI below which the sun is too weak to cause stress (night, dense cloud). # No offset can help; skip shadow computation entirely. MIN_MEANINGFUL_GHI = 100 # W/m² # === FRUITING ZONE === FRUITING_ZONE_INDEX = 1 # mid-canopy zone in the 3-zone ShadowModel (0=basal, 1=fruiting, 2=apical) FRUITING_ZONE_HEIGHT_M = 0.6 # center height of grape cluster zone (m) BERRY_SUNBURN_TEMP_C = 35.0 # berry surface temperature damage threshold (°C) FRUITING_ZONE_TARGET_PAR = 400 # µmol/m²/s — quality threshold; above this → sunburn risk # === TRADEOFF ENGINE === # Candidate shading offsets tested in order (minimum-dose search: stop at first effective offset). CANDIDATE_OFFSETS = [0, 3, 5, 8, 10, 15, 20] # degrees off astronomical position SIMULATION_TIMEOUT_SEC = 5 # max seconds for one offset simulation # === SAFETY RAILS === DIVERGENCE_THRESHOLD = 0.12 # 12% — if |FvCB_A - ML_A| / max > threshold → fallback to FvCB # === SEMILLON FvCB — Rubisco transition === SEMILLON_TRANSITION_TEMP_C = 30.0 # °C — below: RuBP-limited (light bottleneck); above: Rubisco-limited (heat bottleneck) # === WEATHER PROTECTION / OPERATIONAL MODES === WIND_STOW_SPEED_MS = 15.0 # m/s — panels stow flat (0°) above this wind speed HEAT_SHIELD_TEMP_C = 38.0 # °C — emergency heat shield: maximum shade regardless of budget HEAT_SHIELD_CWSI = 0.6 # CWSI threshold that activates heat shield # === MECHANICAL HARVESTING === HARVEST_PARK_CLEARANCE_CM = 250 # cm — minimum clearance for harvesting machine HARVEST_LATERAL_WIDTH_CM = 18 # cm — lateral harvester arm width HARVESTER_RPM_RANGE = (430, 460) # harvester operating RPM range # === HYSTERESIS (command arbiter) === HYSTERESIS_WINDOW_MIN = 15 # minutes — minimum time between consecutive tilt changes ANGLE_TOLERANCE_DEG = 2.0 # degrees — changes smaller than this are suppressed # === PLAN DIVERGENCE RE-PLANNING === PLAN_DIVERGENCE_THRESHOLD_KWH = 0.5 # cumulative |planned − actual| energy that triggers re-plan PLAN_DIVERGENCE_THRESHOLD_SLOTS = 4 # consecutive divergent slots that triggers re-plan PLAN_REPLAN_COOLDOWN_SLOTS = 8 # minimum slots between re-plans (~2 hours) # === ROI / LAND EQUIVALENT RATIO === TARGET_LER = 1.5 # Land Equivalent Ratio target (energy + crop combined) # --------------------------------------------------------------------------- # Agronomic Value Weighting # --------------------------------------------------------------------------- # Spatial zone weights for crop value calculation. # The 3-zone ShadowModel: zone 0 = basal/trunk (~0.2m), zone 1 = fruiting (~0.6m), zone 2 = apical (~1.0m). # During veraison, zone 2 (upper canopy) has the highest marginal value for sugar loading. ZONE_CROP_WEIGHTS = { "pre_veraison": [0.25, 0.35, 0.40], # [zone0, zone1, zone2] "veraison": [0.10, 0.30, 0.60], # apical leaves dominate sugar loading "post_harvest": [0.15, 0.15, 0.70], # reserve building; top canopy matters most } # Temporal (phenological stage) crop value multipliers. # Applied on top of zone weights; reflects how much each unit of photosynthesis # contributes to final economic yield at different growth stages. STAGE_CROP_MULTIPLIER = { "pre_flowering": 1.2, # setting yield capacity (bunch number, berry set) "fruit_set": 1.0, # baseline — rapid cell division "veraison": 1.5, # sugar loading; highest crop value per unit carbon "post_harvest": 0.5, # reserve building only; energy production prioritized } # Growing Degree Day thresholds for Semillon at Sde Boker (base temperature 10°C). PHENOLOGY_GDD_THRESHOLDS = { "budburst": 0, # GDD accumulation starts ~March "flowering": 350, # ~May "fruit_set": 500, # ~early June "veraison": 1200, # ~mid July "harvest": 1800, # ~late August / early September } # --------------------------------------------------------------------------- # Day-Ahead DP Planner # --------------------------------------------------------------------------- DP_SLOTS_PER_DAY = 96 # 15-min intervals × 24 h DP_SLOT_DURATION_MIN = 15 # minutes per slot DP_MOVEMENT_COST = 0.5 # penalty per degree of tilt change (kWh-equivalent) # biases optimizer toward smooth trajectories # Flat energy price (ILS/kWh) used when real-time tariff is unavailable. # Replace with time-of-use tariff schedule for production. DP_FLAT_ENERGY_PRICE_ILS_KWH = 0.35 # Base crop value (ILS / µmol CO₂ m⁻² s⁻¹ per 15-min slot) used in the # DP utility function U_t(θ) = Price_energy · E_t(θ) + Price_crop · A_t(θ). # Calibrate from vineyard revenue per kg grape × expected yield per A unit. DP_BASE_CROP_VALUE = 0.10 # --------------------------------------------------------------------------- # Simulation Log Storage # --------------------------------------------------------------------------- SIMULATION_LOG_DIR = DATA_DIR / "simulation_logs" SIMULATION_LOG_PATH = SIMULATION_LOG_DIR / "control_loop.parquet" DAILY_PLAN_PATH = DATA_DIR / "daily_plan.json" # --------------------------------------------------------------------------- # Data Flow Monitoring # --------------------------------------------------------------------------- # Staleness thresholds (minutes) — green → yellow → red IMS_STALE_YELLOW_MIN = 60 # IMS weather data older than this = yellow IMS_STALE_RED_MIN = 180 # IMS weather data older than this = red TB_STALE_YELLOW_MIN = 15 # ThingsBoard sensor data older than this = yellow TB_STALE_RED_MIN = 60 # ThingsBoard sensor data older than this = red ENERGY_STALE_YELLOW_MIN = 15 # Energy telemetry older than this = yellow ENERGY_STALE_RED_MIN = 60 # Energy telemetry older than this = red # Email alerts (activated when SMTP_HOST + ALERT_EMAIL_TO env vars are set) ALERT_COOLDOWN_MIN = 60 # minimum minutes between repeat alerts for same source