File size: 10,644 Bytes
938949f bfbaecb | 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | # 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
|