| # 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 | |