api / config /settings.py
safraeli's picture
Deploy: data flow monitoring, email alerts, chatbot validation
bfbaecb verified
# 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