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