|
Download context/3_todo.md from SolarWine/api: direct link, hf CLI and curl.
- Browser
- Download file 61.8 kB
-
https://huggingface.co/spaces/SolarWine/api/resolve/main/context/3_todo.md
- Command line
-
hf download hf://spaces/SolarWine/api/context/3_todo.md
-
curl -L -o 3_todo.md https://huggingface.co/spaces/SolarWine/api/resolve/main/context/3_todo.md
61.8 kB
| # SolarWine β Todo Tracker | |
| This file mirrors the high-level plan in `2_plan.md` and tracks day-to-day progress. | |
| --- | |
| ## Baseline v1 (Complete) | |
| - [x] **scaffold** β project directory structure, `.env`, `.gitignore`, `requirements.txt` | |
| - [x] **context-files** β create `context/1_purpose.md`, `context/2_plan.md`, `context/3_todo.md` | |
| - [x] **git-init** β create git repository and make initial commit | |
| - [x] **oop-architecture** β implement OOP modules in `src/` | |
| - [x] **ims-fetch** β implement `IMSClient` and cache IMS data | |
| - [x] **stage1-farquhar** β implement mechanistic Farquhar/GreerβWeedon model | |
| - [x] **stage1-validate** β validate Stage 1 A outputs and plots | |
| - [x] **stage2-preprocess** β preprocessing and temporal split | |
| - [x] **stage2-models** β train and evaluate ML models | |
| - [x] **stage2-visualize** β build evaluation visualizations | |
| - [x] **streamlit-app** β build Streamlit UI (6 tabs) | |
| --- | |
| ## SolarWine 2.0 | |
| ### Phase 1: Configuration & Data Infrastructure | |
| - [x] **config-extend** β add energy budget, no-shade windows, fruiting zone, and tradeoff params to `config/settings.py` (most params added in earlier session; no-shade windows `NO_SHADE_BEFORE_HOUR`, `NO_SHADE_MONTHS`, `NO_SHADE_GHI_BELOW`, `NO_SHADE_TLEAF_BELOW` and `SHADE_ELIGIBLE_HOURS` added 2026-03-10; guardrails wired to reference settings) | |
| - [x] **data-schema** β `src/data/data_schema.py` (telemetry dataclasses: SensorRaw, BiologicalState, TrackerKinematics, SimulationLog). Re-exported via `src/data_schema.py` | |
| - [x] **import-layout** β `scripts/import_layout.py` generates `Data/layout.json` from config geometry + TB device registry (4 rows, 22 devices with 3D positions). Added 2026-03-10 | |
| ### Phase 2: Biological Digital Twin Upgrades | |
| - [x] **fvcb-semillon** β upgrade `src/farquhar_model.py` with `calc_photosynthesis_semillon()` β returns (A, limiting_state, shading_helps). Added 2026-03-10: uses configurable `transition_temp` (default `SEMILLON_TRANSITION_TEMP_C=30Β°C`); `shading_helps` is True only when Rubisco-limited AND Aj > Ac (light abundant relative to enzyme capacity) | |
| - [x] **shadow-fruiting** β upgrade `src/solar_geometry.py` with `fruiting_zone_shadow()` and `evaluate_candidate_offsets()` (added 2026-03-10) | |
| - [x] **canopy-expose** β `compute_vine_A()` now returns `fruiting_zone_A`, `fruiting_zone_par`, `top_canopy_A`, `top_canopy_par` alongside existing fields. Added 2026-03-10 | |
| - [x] **safety-rails** β `src/advisor/safety_rails.py` (FvCB vs ML divergence check, 12% threshold β fallback). Re-exported via `src/safety_rails.py` | |
| - [x] **spectral-agg** β `src/spectral_aggregator.py` with `aggregate_spectral()` (single-timestep) and `aggregate_spectral_df()` (batch DataFrame). CWSI from explicit value β delta-T proxy β VPD fallback. Physical bounds clipping with quality flags. Added 2026-03-10 | |
| ### Phase 3: The Control Algorithm (Core) | |
| - [x] **energy-budget** β create `src/energy_budget.py` (hierarchical YearβMonthβWeekβDayβSlot budget planner). Added 2026-03-10: `EnergyBudgetPlanner` with `compute_annual_plan()`, `compute_weekly_plan()`, `compute_daily_plan()`, `spend_slot()`, `emergency_draw()`, `compute_daily_rollover()`. Analytical fallback when no ShadowModel. Budget: ~920 kWh potential β 46 kWh budget (5%), July gets 17.6 kWh (45%) | |
| - [x] **intervention-gate** β `InterventionGate` in `src/shading/tradeoff_engine.py` (physiology-only pass/fail: GHI, Tleaf, CWSI, shading_helps; no time/month hard-coding β geometry decides). Re-exported via `src/tradeoff_engine.py` | |
| - [x] **min-dose-engine** β `TradeoffEngine` in `src/shading/tradeoff_engine.py` (smallest offset satisfying: fruiting PAR < 400 Β΅mol, top canopy β₯ 85% baseline, sacrifice β€ budget; geometric feasibility pre-check; face-PAR sun-side selection) | |
| - [x] **command-arbiter** β `src/command_arbiter.py` `CommandArbiter` (5-level priority stack: weatherβharvestβsafetyβtimeoutβengine; hysteresis filter with configurable window/tolerance; `arbitrate()` combines selection + filter; weather/harvest bypass hysteresis). Added 2026-03-10 | |
| - [x] **astro-tracker** β `AstronomicalTracker` in `src/command_arbiter.py` (pure sun-following via ShadowModel; `get_angle(timestamp)` interface). Added 2026-03-10 | |
| ### Phase 3.5: Agronomic Weighting & Day-Ahead Planner | |
| - [x] **zone-crop-weights** β spatial zone weighting implemented in `DayAheadPlanner._get_crop_multiplier()` using `STAGE_CROP_MULTIPLIER` + `ZONE_CROP_WEIGHTS` from settings. Added 2026-03-10 | |
| - [x] **day-ahead-dp** β created `src/day_ahead_planner.py` with `DayAheadPlanner` (DP trajectory optimizer: forward pass over daylight slots Γ candidate offsets, budget constraint via discretised DP, movement cost penalty for smooth trajectories, simplified InterventionGate using forecast data). Returns `DayAheadPlan` with `SlotPlan` list + explainability tags. Added 2026-03-10 | |
| - [x] **daily-plan-output** β `DayAheadPlan.to_dict()` serialises plan to JSON-ready dict with per-slot offset, energy cost, gate status, and tags. Added 2026-03-10 | |
| - [x] **phenology-tracker** β `src/models/phenology.py` upgraded with three estimation methods: (1) GDD-based via `compute_gdd_from_ims()` + `estimate_stage_by_gdd()` using IMS tdmax/tdmin and `PHENOLOGY_GDD_THRESHOLDS`, (2) camera-based via `detect_stage_from_camera()` using Gemini Vision on live vineyard feed, (3) calendar fallback. `estimate_stage_combined()` merges all three (camera high-confidence > GDD > calendar). Backward-compatible API. Added 2026-03-10 | |
| - [x] **baseline-ps** β created `src/baseline_predictor.py` (hybrid FvCB+ML with RoutingAgent rule-based selection per slot); wired into DayAheadPlanner as optional `baseline_predictor` param, replaces temperature heuristic with actual A predictions for crop value | |
| - [x] **arbiter-plan-integration** β ControlLoop now tracks plan divergence (cumulative kWh + consecutive slots), triggers re-plan when thresholds exceeded, with cooldown to avoid thrashing | |
| - [x] **tracker-rpc** β added `send_rpc_command()` and `set_device_attributes()` to ThingsBoardClient (two-way RPC with one-way fallback, shared attribute writes) | |
| - [x] **tracker-verify** β TrackerDispatcher verifies |actual - target| < tolerance after dispatch via `_verify_all()` | |
| - [x] **tracker-dispatch** β created `src/tracker_dispatcher.py`: takes `ArbiterDecision` β sends to all 4 trackers via RPC/attribute fallback β verifies execution β logs result | |
| - [x] **control-loop** β created `src/control_loop.py`: 15-min main loop (fetch sensors β plan lookup β live gate check β budget guard β arbitrate β fleet overrides β dispatch β budget spend β divergence check β re-plan β log). One-shot, continuous, and dry_run modes. TrackerFleet/Scheduler wired for per-tracker overrides (default = all same). EnergyBudgetPlanner wired for real-time budget consumption. Plan divergence tracking with auto re-plan | |
| ### Phase 4: Operational Modes | |
| - [x] **ops-modes** β created `src/operational_modes.py` (WindStow, HailStow, HeatShield, HarvestMode + composite `OperationalModeChecker`; integrated into ControlLoop) | |
| ### Phase 5: ROI Reporting | |
| - [x] **roi-service** β created `src/roi_service.py` (BudgetStatus, InterventionStats, LERResult, ROIService with budget tracking + LER computation) | |
| ### Phase 6: Dashboard v2 | |
| - [x] **tab-system-status** β unified System Status tab (`ui/tab_system_status.py`) with 5 sub-sections: | |
| - **Tracker Status**: live angles from TB, coherence check, bar chart | |
| - **Energy Budget**: annual/monthly budget allocation, gauge, weekly plan | |
| - **Control Replay**: load simulation logs, timeline charts, slot-by-slot detail | |
| - **Fruiting Zone**: live canopy temp/PAR/VPD, sunburn risk assessment, thresholds | |
| - **ROI Dashboard**: budget utilisation gauge, intervention stats, LER calculator | |
| ### Phase 7: Integration Testing | |
| - [x] **sim-script** β created `scripts/run_control_simulation.py` (historical replay through full control loop with per-day summary, CLI args, JSON output) | |
| - [x] **validate-budget** β `scripts/validate_control_system.py` verifies cumulative sacrifice never exceeds `max_energy_reduction_pct` (tested with real sim log + synthetic violations) | |
| - [x] **validate-gates** β validates no interventions in May, before 10:00, below transition temp, or below GHI threshold (3/3 violation types caught in self-test) | |
| - [x] **validate-dose** β validates average offset < 10Β°, max offset < 20Β°, intervention rate < 30% (catches excessive dose in self-test) | |
| ### Phase 8: AI-Enhanced Forecasting & Routing | |
| #### 8A β Chronos-2 Multivariate Day-Ahead Forecasting | |
| - [x] **install-chronos** β install `chronos-forecasting>=2.0` and verify compatibility with existing env (done: conda env `solarwine` with Python 3.12, chronos 2.2.2, torch 2.10, llvm-openmp for XGBoost) | |
| - [x] **chronos-data-prep** β build multivariate input pipeline: combine historical A_n (target) with past IMS station 43 measurements + on-site sensors (PAR, T_canopy, VPD, CWSI) as historical context, plus IMS *forecast* data (predicted GHI, air temperature, wind speed, humidity) as known-future covariates for the prediction horizon; IMS forecasts are available at inference time so this is not a data leak β only actual future sensor measurements would be | |
| - [x] **chronos-predict** β execute zero-shot 96-step (24h @ 15-min) day-ahead A_n prediction using Chronos-2 (`amazon/chronos-t5-small` or `-base`) with multivariate context (past A_n + past sensor measurements as history, IMS forecasts as known-future covariates); integrate into `src/day_ahead_planner.py` baseline prediction | |
| - [x] **chronos-benchmark** β benchmark Chronos-2 MAE against existing ML baseline (MAE β 2.7) on held-out test set; compare univariate (A_n only) vs multivariate (A_n + IMS covariates) performance; document results and decide on integration path | |
| #### 8B β Gemini LLM Data Engineering | |
| - [x] **install-gemini** β `google-genai>=1.0` added to `requirements.txt`; already installed in `solarwine` conda env (v1.65.0) | |
| - [x] **llm-data-cleaning** β `src/llm_data_engineer.py` `LLMDataEngineer.analyze_anomalies()`: sends per-column descriptive stats + domain context (physical ranges, Negev site notes) to Gemini; receives Z-score/IQR/bound thresholds as JSON; `apply_cleaning()` applies clip/nan/drop strategies; graceful fallback to physics-based bounds when API key unavailable | |
| - [x] **llm-feature-eng** β `LLMDataEngineer.engineer_features()`: adds `hour_sin`, `hour_cos`, `doy_sin`, `doy_cos` (cyclical time encodings) and `stress_risk_score` (normalised weighted VPD + CWSI, weights confirmed by Gemini); `run_pipeline()` orchestrates clean β feature-engineer in one call | |
| - [x] **dashboard-8b** β Data Explorer: added **AI Data Engineering** as third radio option (Gemini anomaly thresholds, cleaning summary, before/after charts, engineered features, daytime stress profile). Presentation tab: added Gemini LLM data engineering to βWhatβs Nextβ completed list, Key takeaways, and βWhat each tab showsβ (Data Explorer bullet). All data precomputed in-tab (no cache loader). | |
| #### 8C β Gemini Intelligent Routing Layer | |
| - [x] **routing-prompt** β define supervisory system prompt for Gemini model router: given real-time telemetry (Temp, Irradiance, CWSI), route to FvCB (Model A, accurate under standard conditions) or ML (Model B, handles non-linear stress) β reply only `MODEL_A` or `MODEL_B` | |
| - [x] **routing-agent** β build `RoutingAgent` function: executes Gemini API call at start of 15-min control loop, constructs prompt from live telemetry, parses response to determine model path | |
| - [x] **arbiter-routing** β RoutingAgent wired into ControlLoop tick (step 2b): routes each slot to FvCB or ML via rule-based fast path (>90% of cases) with Gemini fallback for transition zone; `model_route` field added to TickResult; safety_rails divergence check remains as independent safety layer | |
| #### 8E β Gemini Day-Ahead Advisor | |
| - [x] **day-ahead-advisor** β `src/day_ahead_advisor.py` DayAheadAdvisor: Gemini analyzes IMS forecast + vine state β structured stress profile, budget recommendations, model routing, Chronos sanity check; dashboard display in Predictions tab | |
| #### 8D β Shadow Mode Validation | |
| - [x] **shadow-mode-test** β created `scripts/shadow_mode_test.py`: 4 validation tests β (1) LLM data cleaning filters catch anomalies, (2) BaselinePredictor generates valid A profiles (peak=17.0 Β΅mol, daylight mean=6.1), (3) RoutingAgent routes coolβfvcb and hotβml correctly, (4) full pipeline shadow simulation over date range | |
| ### Phase 9: Vineyard Advisor Chatbot | |
| - [x] **chatbot-todo** β add Phase 9 to `context/3_todo.md` | |
| - [x] **chatbot-scaffold** β create `src/vineyard_chatbot.py` scaffold + Gemini client (lazy init, same pattern as DayAheadAdvisor) | |
| - [x] **chatbot-system-prompt** β write `CHATBOT_SYSTEM_PROMPT` (persona, site facts, 8 biology rules, tool definitions, response rules) | |
| - [x] **chatbot-tool-defs** β implement `TOOL_DEFINITIONS` list + `_dispatch_tool()` router | |
| - [x] **chatbot-tools-weather** β implement `_tool_current_weather()`, `_tool_calc_photosynthesis()`, `_tool_explain_rule()` | |
| - [x] **chatbot-tools-shading** β implement `_tool_simulate_shading()`, `_tool_compare_angles()`, `_tool_daily_schedule()`, `_tool_day_ahead_advisory()` | |
| - [x] **chatbot-chat-flow** β implement two-pass chat flow (`_build_messages()`, `_call_gemini()`, `chat()`) | |
| - [x] **chatbot-tab** β add Vineyard Advisor tab to `app.py` (Vineyard Advisor tab, chat UI, session state) | |
| - [x] **chatbot-quick-actions** β add quick-action buttons + `_render_tool_data()` helper + clear chat | |
| - [x] **chatbot-test** β syntax check, import check, tabs render, tool call test, fallback test | |
| ### Phase 9.5: Chatbot Data Integration | |
| Wire all five data sources into the chatbot (IMS weather, TB sensors, PS prediction, | |
| energy generation, energy prediction) via a loosely-coupled provider layer. | |
| #### Architecture (`src/data_providers.py`) | |
| ``` | |
| VineyardChatbot βββΆ DataHub βββ¬ββ WeatherService (IMS, 30-min TTL cache) | |
| (tool dispatch) (registry) βββ VineSensorService (TB, 5-min TTL snapshot) | |
| βββ PhotosynthesisService (FvCB + ML + forecast) | |
| βββ EnergyService (TB generation + prediction) | |
| βββ AdvisoryService (Gemini day-ahead) | |
| βββ BiologyService (rules lookup) | |
| ``` | |
| - Chatbot never imports data clients directly β only talks to `self.hub`. | |
| - Each Service owns its caching (TTLCache), error handling, lazy init. | |
| - Adding a new source = write a Service subclass + `hub.register()`. | |
| - `summarise_dataframe()` auto-compresses large results for LLM context. | |
| #### Data strategy | |
| | Source | Service | Strategy | Detail | | |
| |---|---|---|---| | |
| | IMS weather | WeatherService | Cache (preload) | `ims_merged_15min.csv`, 30-min TTL | | |
| | TB sensor snapshot | VineSensorService | On-demand + 5-min TTL | `get_vine_snapshot()` | | |
| | TB sensor time-series | VineSensorService | On-demand | `get_timeseries()` β hourly downsample | | |
| | PS prediction (FvCB/ML) | PhotosynthesisService | On-demand (compute) | Lazy-load models | | |
| | Energy generation (TB) | EnergyService | On-demand | Fetch from inverter/meter devices | | |
| | Energy prediction | EnergyService | On-demand (compute) | GHI Γ panel Γ efficiency Γ cos(ΞΈ) | | |
| #### Steps | |
| - [x] **chatbot-provider-scaffold** β Create `src/data_providers.py` with BaseService ABC, TTLCache, summarise_dataframe, DataHub registry, and 6 service classes (Weather, VineSensor, Photosynthesis, Energy, Advisory, Biology) | |
| - [x] **chatbot-hub-wiring** β Refactored `VineyardChatbot.__init__` to accept a `DataHub` (default: `DataHub.default()`); rewrote `_dispatch_tool()` to delegate to hub services (15 tools); removed all direct imports of IMSClient/ThingsBoardClient/FarquharModel from chatbot | |
| - [x] **chatbot-ims-history** β Implemented `WeatherService.get_history()` with hourly resample + `summarise_dataframe()`; added `get_weather_history` tool to system prompt and dispatch | |
| - [x] **chatbot-tb-timeseries** β Implemented `VineSensorService.get_history()` (device_type, area, hours_back β hourly averages via `get_timeseries()`); added `get_sensor_history` tool to system prompt and dispatch | |
| - [x] **chatbot-ml-predictor** β Implemented `PhotosynthesisService.predict_ml()` with train-once-cache pattern: lazy loads sensor data + IMS, runs Preprocessor + PhotosynthesisPredictor, picks best model by MAE, auto-fills features from latest IMS if not provided. *Note: requires IMS cache with temperature/GHI overlapping sensor data dates* | |
| - [x] **chatbot-ps-forecast** β Implemented `PhotosynthesisService.forecast_day_ahead()`: FvCB-based projection over IMS hourly weather (PAR~2ΓGHI, Tleaf~Tair+2Β°C, VPD from T+RH). Returns hourly A profile + peak/stress summary | |
| - [x] **chatbot-energy-devices** β Discovered Plant asset "Yeruham Vineyard" (ASSET, not DEVICE) with `power` (W) + `production` (Wh/5min) keys. Added `ASSET_REGISTRY`, `get_asset_timeseries()`, `get_asset_latest()` to TB client. Also registered 4 Tracker devices (501-509) with angle/mode telemetry | |
| - [x] **chatbot-energy-tool** β `EnergyService` rewritten: past/today β real TB Plant asset data (daily ~270-290 kWh for 48 kW system); future β analytical estimate (persistence forecast Γ system capacity). Hourly profiles in Israel local time | |
| - [x] **chatbot-energy-predict** β `EnergyService.predict()` now routes: past/today β `get_daily_production()` (real TB), future β `_predict_analytical()` (IMS GHI Γ 48 kW) | |
| - [x] **chatbot-prompt-update** β Updated system prompt with all 15 tools (grouped: weather, vine sensors, photosynthesis, shading, energy, advisory, biology); added energy/prediction/irrigation/fertiliser keywords to fallback matcher | |
| - [x] **chatbot-integration-test** β All 15 tools dispatch correctly; verified imports + instantiation (no circular imports); tested fallback keyword matching; `summarise_dataframe()` compresses >48-row results. Remaining data gaps: IMS cache lacks GHI/temperature (only wind_speed_ms), no date overlap between IMS and sensors for ML training | |
| ### Phase 10: Reliable Advisor β Anti-Hallucination Guardrails | |
| Deterministic guardrails to prevent the chatbot from hallucinating data, | |
| violating biology rules, or answering data questions without tool grounding. | |
| #### Step 1: Structured Response Schema | |
| - [x] **structured-response** β Extended `ChatResponse` with `confidence`, `sources`, `caveats`, `rule_violations` fields; all responses carry grounding metadata | |
| #### Step 2: Mandatory Tool Grounding | |
| - [x] **query-classifier** β `classify_query()` in `src/chatbot/guardrails.py`: regex-based classification (data / knowledge / greeting / ambiguous); data queries force tool calls | |
| - [x] **tool-enforcement** β If LLM answers a data query without calling a tool, chatbot re-prompts: "You MUST call a tool" | |
| #### Step 3: Post-Response Rule Validator | |
| - [x] **response-validator** β `validate_response()` in `src/chatbot/guardrails.py`: deterministic checks for no_shade_before_10 (block), no_shade_in_may (block), temperature_transition (warn), no_leaves_no_shade_problem (warn), no_shading_must_explain (warn) | |
| - [x] **validator-integration** β Validator runs on every response in `chat()`; block-severity overrides response text; warn-severity appends caveats | |
| #### Step 4: Source-Tagged Context Injection | |
| - [x] **source-tagging** β `tag_tool_result()` adds `_source`, `_data_age_minutes`, `_freshness_warning` to tool results before Gemini Pass 2 | |
| - [x] **citation-prompt** β Pass 2 prompt instructs Gemini to cite source and timestamp when quoting numbers | |
| #### Step 5: RAG Knowledge Base | |
| - [x] **rag-knowledge-base** β Keyword-indexed rule retrieval with pinned rules (no_shade_before_10, energy_budget, temperature_transition) + top-scoring rules per query; `retrieve_relevant_rules()` + `build_contextual_prompt()` in vineyard_chatbot.py | |
| #### Step 6: Conversation Memory | |
| - [x] **sliding-context** β Summarize older messages into topics, keep recent 6 verbatim, pin critical context; `_summarize_history()` in vineyard_chatbot.py | |
| #### Step 7: Confidence-Gated Responses | |
| - [x] **confidence-estimator** β `estimate_confidence()` maps data freshness to high/medium/low/insufficient_data | |
| - [x] **confidence-ui** β `_render_grounding_metadata()` in `ui/tab_advisor.py` shows colored confidence badge, sources, caveats, rule warnings | |
| #### Step 8: Guardrail Test Suite | |
| - [x] **qa-tests-5** β 5 adversarial QA tests in `tests/test_advisor_chatbot.py`: shade-before-10 blocked, data-question forces tool, stale-data warns, no-shade-without-reason warns, tool-failure returns insufficient_data | |
| - [x] **guardrail-unit-tests** β Unit tests for QueryClassifier, ResponseValidator, ConfidenceEstimation, SourceTagging (42 tests total, all passing) | |
| - [x] **qa-tests-expand** β Expanded to 21 adversarial prompts (QA1-QA21) covering May shading, dormancy, night queries, prompt injection, boundary conditions, multi-rule violations, energy/irrigation classification, ambiguous queries. 58 tests total, all passing | |
| #### Step 9: Dual-Channel Advisory | |
| - [x] **dual-channel** β `classify_response_mode()` separates info mode (blue, factual) from advisory mode (amber, recommendations); `response_mode` field on ChatResponse; UI renders mode indicator | |
| #### Step 10: Feedback Loop | |
| - [x] **feedback-ui** β Thumbs up/down/flag buttons on each response in `ui/tab_advisor.py`; `_render_feedback_buttons()` + `_submit_feedback()` | |
| - [x] **feedback-storage** β `src/chatbot/feedback.py` logs feedback to JSONL (query, response, tool_calls, rules, confidence, comment) | |
| --- | |
| ## Phase 11: Production Migration | |
| Full architecture documented in `context/4_production.md`. | |
| 3-layer architecture: Lovable/React frontend (Cloudflare Pages) β FastAPI middleware (HuggingFace Spaces) β existing Python core. | |
| Cron workers via GitHub Actions (free for private repos). | |
| ### Phase 11.1: Redis Caching Layer | |
| Replace in-memory TTLCache with Upstash Redis so API and workers share state. | |
| - [x] **upstash-setup** β Upstash Redis instance created (many-mammal-76248.upstash.io), credentials in `.env`, connectivity verified with dual-layer TTLCache round-trip | |
| - [x] **redis-client** β Create `src/data/redis_cache.py`: thin wrapper around Upstash Redis REST API with `get_json()`, `set_json(ttl)`, `delete()`, `exists()`, `ping()`. Singleton via `get_redis()`, returns None when not configured | |
| - [x] **datahub-redis** β Refactored `TTLCache` in `data_providers.py`: tries Redis first (via `redis_prefix`), falls back to in-memory. WeatherService prefix `weather:`, VineSensorService prefix `vine:`. Streamlit works unchanged (no Redis = pure in-memory) | |
| - [x] **tick-result-redis** β `backend/workers/control_tick.py` writes TickResult to Redis `control:last_tick` (20-min TTL) after each tick | |
| - [x] **plan-redis** β `backend/workers/daily_planner.py` writes plan to Redis `control:plan` (24h TTL) + backup `Data/daily_plan.json` | |
| - [x] **budget-redis** β ControlLoop `_ensure_daily_budget()` restores from Redis `control:budget` on startup; `_persist_budget()` saves after every `spend_slot()`. Falls back to recompute if Redis unavailable | |
| ### Phase 11.2: FastAPI Middleware (HuggingFace Spaces) | |
| Create the API gateway in `backend/api/`, deploy as HF Space (Docker SDK). | |
| - [x] **fastapi-scaffold** β Created `backend/api/main.py` with FastAPI app, CORS (configurable via `ALLOWED_ORIGINS`), lifespan, all route includes | |
| - [x] **fastapi-deps** β Created `backend/api/deps.py` with `get_datahub()` (lru_cache singleton) and `get_redis_client()` | |
| - [x] **route-weather** β `backend/api/routes/weather.py`: GET `/api/weather/current`, `/api/weather/history?start_date&end_date`, `/api/weather/forecast` | |
| - [x] **route-sensors** β `backend/api/routes/sensors.py`: GET `/api/sensors/snapshot`, `/api/sensors/history?type&area&hours` | |
| - [x] **route-energy** β `backend/api/routes/energy.py`: GET `/api/energy/current`, `/api/energy/daily/{date}`, `/api/energy/history`, `/api/energy/predict/{date}` | |
| - [x] **route-photosynthesis** β `backend/api/routes/photosynthesis.py`: GET `/api/photosynthesis/current?model`, `/api/photosynthesis/forecast` | |
| - [x] **route-control** β `backend/api/routes/control.py`: GET `/api/control/status`, `/api/control/plan`, `/api/control/budget`, `/api/control/trackers` | |
| - [x] **route-chatbot** β `backend/api/routes/chatbot.py`: POST `/api/chatbot/message`, POST `/api/chatbot/feedback` with Pydantic request models | |
| - [x] **route-biology** β `backend/api/routes/biology.py`: GET `/api/biology/phenology`, `/api/biology/rules`, `/api/biology/rules/{name}` | |
| - [x] **rate-limiting** β Added `slowapi`: 60 req/min global default, 10 req/min on `/api/chatbot/message` | |
| - [x] **fastapi-dockerfile** β Created `backend/Dockerfile` (Python 3.12 slim, port 7860, copies src/config/backend/Data) | |
| - [x] **hf-space-readme** β Created `backend/HF_README.md` (sdk: docker, app_port: 7860, private: true, endpoint summary) | |
| - [x] **fastapi-test** β Smoke tested: `/api/health` returns OK (uptime + redis status), `/api/weather/current` returns live IMS data | |
| ### Phase 11.3: Background Workers (GitHub Actions) | |
| Extract ControlLoop and DayAheadPlanner into standalone entry points, run as scheduled GitHub Actions. | |
| - [x] **worker-control** β Created `backend/workers/control_tick.py`: loads ControlLoop, runs single tick, writes TickResult to Redis, supports `--dry-run` | |
| - [x] **worker-planner** β Created `backend/workers/daily_planner.py`: runs DayAheadPlanner, writes plan to Redis + JSON backup | |
| - [x] **gh-action-tick** β Created `.github/workflows/control-tick.yml`: schedule `*/15 * * * *`, Python 3.12 + pip cache, all secrets | |
| - [x] **gh-action-planner** β Created `.github/workflows/daily-planner.yml`: schedule `0 2 * * *`, Python 3.12 + pip cache, all secrets | |
| - [x] **gh-secrets** β All 7 secrets added to GitHub repo Settings β Actions secrets | |
| - [x] **worker-logging** β Structured JSON logging (asctime + level + name + message) in both workers | |
| - [x] **worker-test** β Dry-run tested: `python -m backend.workers.control_tick --dry-run` loads plan (43 slots), computes angle (-10.5Β°), dispatches to 4 trackers (DRY RUN), logs tick result | |
| ### Phase 11.4: Lovable Frontend (React + Cloudflare Pages) | |
| Generate and customize the production frontend via Lovable. | |
| - [x] **lovable-project** β Created via Lovable, exported to `solarwine-ai/solarvine-dashboard` repo | |
| - [x] **lovable-dashboard** β Dashboard page: weather card, energy (yesterday/today/tomorrow), phenology, quick stats | |
| - [x] **lovable-advisor** β Advisor page: chat panel with bubbles, advisory cards, confidence/channel badges, feedback | |
| - [x] **lovable-control** β Control page: tracker bars, energy budget, last tick, plan timeline | |
| - [x] **lovable-photosynthesis** β Photosynthesis page: FvCB vs ML, forecast chart, sensor snapshot | |
| - [x] **lovable-shading** β Shading page: offset slider, 3-zone canopy, stress heatmap | |
| - [x] **lovable-docs** β Docs page: biology rules accordion, architecture cards, API reference (18 endpoints) | |
| - [x] **lovable-export** β Imported into `frontend/` directory in main repo | |
| - [x] **api-hooks** β 7 hooks (weather, energy, sensors/PS, control, chatbot, auth) + `api.ts` + `types/api.ts` | |
| - [x] **api-integration** β All 6 pages wired to real API: mock data replaced with live TanStack Query hooks, field names mapped to backend snake_case responses | |
| - [x] **chatbot-component** β Real mutations (useSendMessage/useSendFeedback), confidence badges, source citations, caveats, dual-channel (INFO/ADVISORY), auto-scroll | |
| - [x] **responsive-polish** β Sidebar hamburger already implemented (lg breakpoint). Fixed: Power chart responsive height, Shading heatmap mobile min-width, Home grid breakpoints, camera/map sizing | |
| ### Phase 11.5: Deployment & Integration | |
| Wire everything together and deploy. | |
| - [x] **frontend-hosting** β Switched from Vercel (requires Pro for private org repos) to **Cloudflare Pages** (free, unlimited bandwidth, private org repos supported). Connect GitHub repo β root `frontend/`, build `npm run build`, output `dist`, set `VITE_API_URL` env var | |
| - [x] **hf-deploy** β HF Space `SolarWine/api` deployed (Docker SDK, public), secrets injected via huggingface_hub, `/api/health` returns OK with Redis connected. 14/17 endpoints passing; 3 fixed (phenology missing arg, photosynthesis missing method, sensors null area) | |
| - [ ] **upstash-connect** β Verify Redis connectivity from HF Space and GitHub Actions, test cache read/write round-trip | |
| - [x] **e2e-smoke** β Created `scripts/smoke_test.py`: tests 12 endpoints (health, weather, sensors, energy, PS, control, biology, chatbot). Run: `python scripts/smoke_test.py [url]` | |
| - [x] **streamlit-deprecate** β Added migration banner to `app.py`; shows only when `SOLARWINE_FRONTEND_URL` env var is set (invisible until new frontend is live) | |
| - [ ] **dns-custom** β (Optional) Configure custom domain on Cloudflare Pages if available | |
| ### Phase 11.6: Security & Monitoring (Post-Launch) | |
| - [x] **auth-jwt** β Created `backend/api/auth.py` (create_token, require_auth, optional_auth) + `backend/api/routes/login.py` (POST `/api/auth/login`). Auth returns `role: "guest"` when JWT_SECRET not set (read-only). PyJWT added to backend/requirements.txt | |
| - [x] **api-hardening** β Standardized error handling (HTTPException everywhere, no JSONResponse for errors), data provider error dict detection, startup path validation, Sentry fail-loud, chatbot session_id validation + blank message guard + init failure circuit breaker, Redis singleton retry-storm fix, CORS defaults include HF Space + Cloudflare Pages URLs | |
| - [x] **auth-frontend** β Created `useAuth.ts` hook (login/logout/token restore from localStorage) + updated `api.ts` to auto-attach JWT Bearer header + auto-clear on 401 | |
| - [ ] **monitoring-health** β UptimeRobot or similar free monitor on HF Space `/api/health` (5-min interval, email alerts) | |
| - [x] **monitoring-logs** β Structured logging (asctime/level/name/message) in `main.py` + request logging middleware (method, path, status, duration). Health endpoint extended with ThingsBoard/IMS/Gemini connectivity checks | |
| - [x] **error-tracking** β Added Sentry integration to `backend/api/main.py` (optional, enabled via `SENTRY_DSN` env var). `sentry-sdk[fastapi]>=2.0` in backend/requirements.txt | |
| - [x] **budget-alerts** β `control_tick.py` checks budget after each tick; logs warning if >80% spent before 14:00 IST; sends webhook to `BUDGET_ALERT_WEBHOOK` if configured | |
| --- | |
| ## Phase 12: Robustness & Efficiency | |
| ### 12.1: Immediate (Week 1) | |
| - [x] **tb-health-fix** β Fixed ThingsBoard health check: `/api/noauth/health` returns 404, switched to root URL. Needs HF Space redeploy | |
| - [x] **hf-redeploy** β Redeployed HF Space via `huggingface_hub` API (git push blocked by binary file policy). Uploaded 8 files, space auto-rebuilt | |
| - [x] **redis-verify** β Smoke test against live HF URL: 11/12 pass (control plan 404 expected β no cron run yet). Redis connected, IMS/Gemini configured | |
| - [x] **weather-fix** β Fixed WeatherService crash: Redis deserialised DataFrame as dict/list β `.empty` AttributeError. Added type guard + skip DataFrame Redis serialisation | |
| - [x] **uptime-monitor** β UptimeRobot configured on `/api/health` (5-min interval). Added HEAD method support to health endpoint for monitoring compatibility | |
| - [x] **monitoring-page** β Redesigned Alarms page β Monitoring & Alarms: overall status banner, 3 data source cards (IMS/TB/Energy), backend services panel (Redis/TB/IMS/Gemini), data flow issues surface as system alarms. Sidebar renamed Alarms β Monitoring | |
| - [x] **code-review-fixes** β Fixed email alerter string formatting bug (CRITICAL), duplicate className in Home.tsx, DataFlowStatus null safety, energy price consistency (0.162 USD/kWh), table accessibility (scope attrs), negative age logging | |
| - [x] **data-flow-monitor** β New `/api/health/data-sources` endpoint returning per-source green/yellow/red status (IMS weather, TB sensors, TB energy) with age tracking and configurable thresholds. `DataFlowMonitor` in `backend/services/data_flow_monitor.py`. Thresholds in `config/settings.py`: IMS yellow 60m/red 180m, TB yellow 15m/red 60m | |
| - [x] **data-flow-alerts** β `EmailAlerter` in `backend/services/email_alerter.py`: SMTP email alerts when sources go red, per-source cooldown (60 min). Activated by `SMTP_HOST` + `ALERT_EMAIL_TO` env vars. Background loop in `main.py` checks every 5 min, pushes SSE `health` event | |
| - [x] **data-flow-frontend** β `DataFlowStatus` component on Home page: colored dots (green/yellow/red) for each source, hover tooltips, auto-refresh via SSE. Hook: `useDataFlowStatus`, types: `DataSourceStatus` / `DataFlowStatusResponse` | |
| - [x] **chatbot-range-validation** β `validate_numeric_ranges()` in guardrails.py: checks tool results against physical bounds (temp, GHI, PAR, VPD, CO2, power, etc.), flags sensor faults as caveats | |
| - [x] **chatbot-cross-source** β `check_cross_source_consistency()` in guardrails.py: compares IMS vs TB temperature readings, warns when they diverge by >5Β°C | |
| - [x] **chatbot-rag-scoring** β Improved `retrieve_relevant_rules()`: weighted scoring (exact match +2, partial word overlap +0.5), increased max_rules to 6 | |
| ### 12.2: Short-term (Week 2β4) | |
| - [x] **responsive-polish** β Sidebar hamburger already implemented (lg breakpoint). Fixed: Power chart responsive height (280/350/450px), Shading heatmap mobile min-width (400px), Home grid md breakpoint for map/camera/power row, camera/map responsive min-heights | |
| - [x] **load-test** β `scripts/load_test.py` (Locust): 16 weighted endpoints, 10 users Γ 1 min = 157 requests, p50=250ms, p95=1700ms, 0 errors. All endpoints healthy except `/api/health/data-sources` (not yet deployed at test time) | |
| - [x] **circuit-breaker** β Added `CircuitBreaker` class (3 failures in 60s β open for 5 min) to VineSensorService and EnergyService. Prevents retry storms when TB is down | |
| - [x] **tracker-timeout** β Added exponential backoff retry (3 attempts: 1s, 2s) for TB shared attribute writes in TrackerDispatcher | |
| ### 12.2b: Code quality improvements (done 2026-03-29) | |
| - [x] **code-review-critical** β Fixed: Redis health try-catch, chatbot init race condition (lock before null check), background task try-finally, daily planner IST timezone, JWT login parse safety, error boundary on Outlet | |
| - [x] **error-standardization** β Extracted `check_service_error()` to `backend/api/utils.py`, used by energy + sensors routes. Fixed energy route ignoring errors when `daily_kwh` present | |
| - [x] **control-auth** β Added `optional_auth` to all `/control/*` endpoints (status, plan, budget) | |
| - [x] **farquhar-sigmoid** β Replaced hard 30Β°C RuBPβRubisco cutoff with smooth sigmoid (28β32Β°C). New "Transition" state. Prevents flip-flopping near threshold | |
| - [x] **divergence-fix** β Fixed inverted logic in `control_loop.py:704` divergence check | |
| - [x] **frontend-extract** β Extracted `StatCard` to `/components/StatCard.tsx`, date formatters to `/lib/dateUtils.ts`, energy price to `/config/constants.ts`. Home.tsx reduced by ~50 lines | |
| - [x] **strict-types** β Added TypeScript interfaces: `ChillUnitResponse`, `SoilMoistureRow`, `RainRow`, `TrackerInfo`, `ControlTrackers`, `ControlBudget`. Replaced inline `any` types | |
| - [x] **chart-memoize** β `useMemo` on tempData chart transformation in Home.tsx | |
| ### 12.2c: Architecture refactors (done 2026-03-29) | |
| - [x] **vectorize-farquhar** β `compute_all()` rewritten with vectorized pandas/numpy (no iterrows). 10K rows: 7.5ms (was ~750ms). Numerically identical output (max diff=0) | |
| - [x] **control-loop-di** β `ControlLoop.__init__()` accepts all 8 dependencies as keyword args. Pass None (default) for lazy auto-create; pass mocks for testing | |
| - [x] **gate-pipeline** β `InterventionGate.evaluate()` split into 5 methods (`_check_meaningful_sun`, `_check_heat_stress`, `_check_water_stress`, `_check_radiation_load`, `_check_biology`) composed as pipeline. First rejection short-circuits | |
| - [x] **budget-audit** β `src/budget_audit.py`: `BudgetAuditLog` appends one parquet row per control tick. `daily_summary(date)` and `weekly_report()` for compliance verification. Wired into `control_loop.py` tick step 12. CLI: `python -m src.budget_audit --report` | |
| - [x] **chatbot-hebrew** β Added 18 Hebrew keywords to query classifier. System prompt: language matching, phenology-first, conciseness. IMS stale β auto-supplement with TB sensors. Confidence rule override for dormancy/blocks | |
| - [x] **refactor-tests** β 23 new tests in `tests/test_refactors.py`: vectorized vs scalar (4), sigmoid transition (2), gate pipeline (6), ControlLoop DI (3), budget audit (3), Hebrew classification (3), confidence override (2) | |
| ### 12.2e: Data + energy-model refresh β done 2026-05-18 | |
| - [x] **ims-refresh** β Pulled IMS station 43 from 2025-12-01 β 2026-05-10 (10,748 rows, 15-min). API has ~8-day lag; data ends 2026-05-10. | |
| - [x] **scripts-refresh-energy-data** β New `scripts/refresh_energy_data.py`: joins TB Plant `production` (1h SUM agg) with IMS hourly weather, reindexes to contiguous hours (nighttime = 0 kWh), adds time + solar-geometry features, writes `Data/energy_weather_merged.csv` (3,477 rows Γ 18 cols). | |
| - [x] **scripts-train-energy** β New `scripts/train_energy_predictor.py`: temporal hold-out (last 14 days), sklearn `GradientBoostingRegressor` + `LinearRegression` fallback (xgboost requires libomp on macOS; not installed). Bundle slot named `xgb_model` for backward compat with `EnergyPredictor.__init__`. | |
| - [x] **energy-backtest-fix** β `EnergyPredictor.backtest()` now reads weather from `IMSClient` instead of the dead Air1 device. | |
| - [x] **energy-model-validation** β 1,343 train rows / 337 hold-out rows. Hourly MAE = 4.5 kWh. **Daily mean signed error = +50%** β model systematically over-predicts in late April/May. Cause: model has no `tracker_angle` feature, so it can't see the agrivoltaic interventions that actively detune trackers as growing season starts. See 12.5 below for the planned fix. | |
| - [x] **energy-tracker-feature-attempted** β Wired `tracker_angle_mean` + `tracker_angle_std` + `abs_tracker_angle` from TB into the refresh pipeline (`scripts/refresh_energy_data.py:fetch_tb_tracker_angles`). Two variants tried: (a) with imputation of pre-Feb history via hour-of-day medians β bias worsened to **+57%**; (b) without imputation + dropped `sin_elevation` β bias worsened to **+75%**. **Root cause is distribution shift, not features**: training tracker_angle avg = -15Β° (mild), hold-out avg = -51Β° (aggressive interventions). The model can't extrapolate operational regimes it didn't see in training. Reverted features to the original best config. Tracker telemetry is still collected into the CSV (columns `tracker_angle_mean`/`_std`) β ready to be consumed once 2026 growing-season data accumulates. See 12.6 / `bigml-tracker-feature` for the proper fix path. | |
| ### 12.2d: 2026 sensor migration β done 2026-05-18 | |
| - [x] **tb-2026-registry** β Clean-replaced `DEVICE_REGISTRY` in `src/data/thingsboard_client.py` with the 2026 fleet (35 entries: 19 Crop_2Soil + 12 Thermocouples + 4 Trackers). Legacy `Air1-4`, `Crop1-7`, `Soil*`, `Irrigation1`, `Thermocouples1-2` removed. Added `DeviceInfo.position` field (north/south/center/etc.). Convenience subsets `TREATMENT_DEVICES`/`REFERENCE_DEVICES`/`CROP_2SOIL_DEVICES`/`THERMOCOUPLE_DEVICES` derived at import. Treatment = rows 501/502/503/504/509; Reference = row 202. | |
| - [x] **vinesnapshot-2026** β Rewrote `VineSnapshot` with 61 fields. Removed PAR/DLI/VPD/CO2/Irrigation (no 2026 sensor); added NDVI/PRI/PSRI/SIPI/GCI/LCI/DUVI ratios, dual soil profile (shallow + deep moisture/temp/pore-water EC), `panel_temp_active_count`. Legacy field names kept as None-valued compat shims so downstream consumers don't crash. Per-position keys use `{row}-{cardinal}` form (e.g. `"502-south-east"`). | |
| - [x] **vinesnapshot-aggregation** β `get_vine_snapshot()` no longer hard-codes device names; iterates `DEVICE_REGISTRY` filtered by `area`. Full fleet fetched in 1.1 s via `ThreadPoolExecutor`. | |
| - [x] **bounds-fixes** β Added physical bounds for PSRI/SIPI/GCI/LCI/DUVI/leaf-wetness in `_BOUNDS`. **Critical fix**: GCI was being clipped to `[-1, 1]` (real range 0..30), causing all daytime values to be discarded. Split `treatment_soil_pore_water_ec` into shallow + `_deep`. De-duped the `treatment_pri` computation. | |
| ### 12.3: Medium-term (Month 1β2) | |
| - [ ] **chatbot-feedback** β Review first 20 thumbs-down/flag submissions, identify top 3 guardrail failures, tune rules | |
| - [ ] **model-versioning** β Create `model_registry/` to track Chronos/ML/Farquhar parameter versions, enable A/B testing | |
| - [ ] **phenology-validation** β Cross-reference GDD-based stage estimates vs camera observations, refine thresholds for Sde Boker | |
| ### 12.5: 2026 sensor follow-ups (queued after the foundational registry rewrite) | |
| Bugs and design debt surfaced during the May 2026 sensor migration. Foundational | |
| work landed in 12.2d above; these are the deferred items. | |
| **Remaining bugs** | |
| - [ ] **panel-tc-semantics** β `panel_temp_active_count` is per-device but the panel-temp avg is per-channel β inconsistent. Pick one. (`src/data/thingsboard_client.py:get_vine_snapshot`) | |
| - [x] **tab-system-status-2026** β `ui/tab_system_status.py` `_render_fruiting_zone` rewritten to use NDVI / PRI / leaf-air ΞT / PSRI; risk assessment now triggers on ΞT > 3Β°C and PSRI > 0.2 in addition to canopy temp. Done 2026-05-18. | |
| - [ ] **frontend-types-2026** β `frontend/src/types/api.ts` and the React hooks (`useSensors`, etc.) still declare `treatment_par_umol: number | null` and similar. After the migration these are always null β type narrows to `null`. Audit `frontend/src/lib/` + `frontend/src/hooks/` and remove or mark deprecated. | |
| **Architectural concerns (numbered to match the post-migration code review)** | |
| - [ ] **2026-A-auto-sync-registry** β Hardcoded `DEVICE_REGISTRY` is brittle: each TB change requires a code edit + redeploy. Build `scripts/sync_devices.py` that reads the `Crop 2Soil 2026` + `Thermocouples 2026` entity groups, writes `Data/devices.json`, and have `thingsboard_client.py` prefer that file with the hardcoded dict as offline fallback. Position decoding belongs in this sync script (regex over the Hebrew TB labels: `Χ¦Χ€ΧΧ`βnorth, `ΧΧ¨ΧΧ`βsouth, `ΧΧΧ¨Χ`βeast, `ΧΧ’Χ¨Χ`βwest, `ΧΧ¨ΧΧ`βcenter). | |
| - [ ] **2026-B-vinesnapshot-nest** β `VineSnapshot` is 61 flat fields and growing. Nest as `snapshot.treatment.{air,canopy,soil_shallow,soil_deep}` and `snapshot.panel`, expose flat `to_dict()` only at the JSON boundary. Defer until you're already breaking the API for another reason. | |
| - [ ] **2026-C-schema-version** β Compat shims (`treatment_par_umol=None` etc.) silently say "sensor down" when the truth is "this metric no longer exists." Add `schema_version: "2026.1"` to the snapshot and let consumers branch / error explicitly; remove the shims one release later. | |
| - [ ] **2026-D-split-fetch-aggregate** β `get_vine_snapshot()` mixes ~80 lines of I/O with ~70 lines of transformation. Split into `_fetch_all_latest(plan) β raw_dict` + `_aggregate_snapshot(raw, now) β VineSnapshot`. Then unit-test the aggregate step with fixture dicts (currently no test covers it). | |
| - [ ] **2026-E-position-source-of-truth** β Position labels were decoded by hand from Hebrew TB labels β not reproducible. After 2026-A lands, decode at sync time. Until then, store the original TB label as `DeviceInfo.tb_label_hebrew` so the human-readable source survives. | |
| - [ ] **2026-F-per-device-freshness** β `DataFlowMonitor` checks aggregate "newest TB timestamp" β one fast-reporting device makes the fleet look healthy even if 11 thermocouples are silent. Move to per-device staleness with grace per device-type (Crop_2Soil ~15min, Thermocouples up to ~60min due to deep-sleep cycles). | |
| - [x] **2026-G-layout-regen** β `scripts/import_layout.py` updated to handle 2026 rows (202 reference; 501/502/503/504/509 treatment) and the `DeviceInfo.position` field via `_POSITION_OFFSETS`. `Data/layout.json` regenerated: 6 rows, 35 devices, each with `position_m: [x, y, z]`. Done 2026-05-18. | |
| - [ ] **2026-H-position-taxonomy** β Position strings (`"south-east"`, `"center-east"`, β¦) are free-form. Convert to a `CardinalPosition` enum in `src/data/data_schema.py` (or document the canonical form in CLAUDE.md) so future labels match. | |
| - [ ] **2026-I-aggregation-tests** β No unit test covers the new aggregation logic. Add at least 3 fixture-based tests in `tests/test_thingsboard_client.py`: full fleet, 1 device per area, all-empty. Depends on 2026-D. | |
| **Follow-on integration work** | |
| - [ ] **2026-J-ims-ambient-bridge** β Wire IMS Sde Boker station 43 β `VineSnapshot.ambient_*` fields so they stop returning None. Currently `Alarms.tsx` and `Home.tsx` fall through to `treatment_air_temp_c`, which is misleading (it's not ambient). | |
| - [ ] **2026-K-chatbot-spectral-tools** β Add chatbot tools `get_vegetation_indices()` and `get_canopy_health()` exposing the new 2026-only data (NDVI/PRI/PSRI/SIPI per row + position). | |
| - [ ] **2026-L-spectrometer-page** β New frontend page (or extend `Agro.tsx`): vegetation-index time series, NDVI/SIPI heatmap across the 17 active Crop_2Soil devices, panel surface-temp 4Γ12 grid from the thermocouples. | |
| ### 12.6: "Big" β Stage 2 photosynthesis ML retrain on 2026 schema | |
| Plan, not yet executed. The Stage 2 ML predictor (`src/forecasting/predictor.py`) | |
| forecasts A_n (photosynthesis rate) from weather features. Currently trained on | |
| `Data/Seymour/sensors_wide.csv` (frozen at 2026-02-01) β features include PAR, | |
| airTemperature, CO2, VPD, leafTemperature from the legacy Air1/Crop fleet. | |
| The 2026 sensor refresh deprecated all of those sensors. The new fleet emits | |
| NDVI/PRI/PSRI/spectrometer bands + dual-soil profile + IRT ambient β a | |
| fundamentally different feature space. This is a redesign, not a refresh. | |
| **Phase 1 β Data collection pipeline (2-3 days work)** | |
| - [x] **bigml-collection** β `scripts/collect_2026_training_data.py` written | |
| and smoke-tested 2026-05-18. Pulls 15-min telemetry from the 10 treatment + | |
| 7 reference Crop_2Soil devices via TB, averages per area per timestep, | |
| inner-joins with IMS 15-min weather, writes to | |
| `Data/2026/sensor_history.parquet`. Append-mode dedupes on | |
| (timestamp, area). May 1β10 sample: **1,896 rows** (948 per area). | |
| - [x] **bigml-stage1-2026** β Farquhar inputs derived from the 2026 fleet: | |
| PAR β 2.0 Γ GHI (Akitsu et al. 2017, Β±10 %), VPD via Tetens from IMS | |
| RH + Tair, CO2 = 420 ppm assumed, CWSI = (Tleaf β Tair)/15 clipped to | |
| [0, 1]. `compute_farquhar_a()` runs `FarquharModel.calc_photosynthesis()` | |
| row-by-row; result column `a_farquhar_umol`. Validation: daytime mean | |
| 13.1 Β΅mol COβ/mΒ²/s, peak 19.6 Β΅mol β squarely in Greer & Weedon's | |
| empirical Semillon range for the leaf-temp window seen. | |
| - [x] **bigml-collection-shading-correction** β `apply_shading_correction()` | |
| added to `scripts/collect_2026_training_data.py`. For each treatment-area | |
| timestep: pulls `tracker_angle_mean` (or astronomical fallback when telemetry | |
| missing), runs `ShadowModel.project_shadow()` + `compute_par_distribution()`, | |
| takes the fruiting-zone mean PAR, and multiplies the IMS-derived | |
| `par_umol_derived` by that factor. Reference rows untouched. May 1β10 sample: | |
| treatment PAR factor distribution is 0.10β1.0 (median 0.82, mean 0.73) β | |
| panels meaningfully reduce fruiting-zone PAR. Farquhar A label is recomputed | |
| after the correction. Done 2026-05-18. | |
| - [ ] **bigml-baseline** β Until enough 2026 data accumulates (β₯3 months | |
| growing-season hours), run Farquhar-only as the photosynthesis estimate | |
| (skip Stage 2 ML for 2026). The chatbot's photosynthesis tool already | |
| supports this via `PhotosynthesisService.compute_fvcb()`. | |
| - [ ] **bigml-collection-cron** β Schedule | |
| `scripts.collect_2026_training_data` to run weekly via GitHub Actions | |
| (similar pattern to `control-tick.yml` / `daily-planner.yml`). Window = | |
| last 8 days (overlaps to be safe against IMS's ~8-day publication lag). | |
| **Phase 2 β Feature engineering (1-2 days)** | |
| - [ ] **bigml-features** β Define the 2026 ML feature set. Candidates: | |
| IMS GHI/Tair/RH/wind (forecastable), Crop_2Soil NDVI/PRI/PSRI/SIPI (now), | |
| soilMoisture (shallow + deep), leafTemperature, tracker angle (current), | |
| hour, doy_sin/cos. Decide whether to keep the per-device dimension (input | |
| = 17 device readings per timestep) or aggregate to area-level (~5 features). | |
| - [ ] **bigml-spectral-pca** β With 17 spectrometer bands per device Γ 17 | |
| devices = 289 columns, dimensionality reduction is needed. Run PCA on the | |
| spectral subset and keep the top 5-8 components. | |
| **Phase 3 β Training + validation (1 day)** | |
| - [ ] **bigml-train** β Train RF / GBR / Linear regressors (analogous to | |
| Stage 2 v1). Use temporal hold-out from the latest 14 days of the | |
| collected 2026 data. Compare MAE vs Farquhar-only baseline. | |
| - [ ] **bigml-tracker-feature** β Add `tracker_angle` and | |
| `intervention_active` as features to the **energy predictor too** (fixes | |
| the +50% over-prediction bias seen in 12.2e). Requires pulling tracker | |
| telemetry alongside production into the energy refresh script. | |
| - [ ] **bigml-deploy** β Wire the trained model into | |
| `PhotosynthesisService.predict_ml()`, replacing the current | |
| Stage 2 v1 model. Update model_versioning in `model_registry/` | |
| (Phase 12.3 β `model-versioning`). | |
| **Dependencies / open questions** | |
| - Need β₯3 months of 2026 growing-season data before Phase 3 is statistically | |
| meaningful. With data collection starting May 2026, retraining is realistic | |
| for **AugustβSeptember 2026**. | |
| - CO2 atmospheric assumption (420 ppm) is conservative but the lack of a | |
| real sensor is a real degradation vs the legacy fleet. Consider sourcing | |
| hourly CO2 from a nearby Mauna Loa proxy if precision matters. | |
| - Tracker-angle feature needs ThingsBoard telemetry alignment β trackers | |
| emit `angle` as shared-attribute, not telemetry by default. Verify the | |
| control loop is writing telemetry to TB. | |
| ### 12.7: Frontend redesign β Claude Design handoff (done 2026-05-18) | |
| - [x] **redesign-foundation** β Replaced `:root` tokens with Negev sand + vine green + sun gold; loaded Source Serif 4 + IBM Plex Sans + IBM Plex Mono; added brand stripe motif + masked-PNG wordmark; re-targeted shadcn aliases. `tailwind.config.ts` extended with serif/mono families and brand color tokens. | |
| - [x] **redesign-layout** β Rewrote `AppSidebar` to a 240 px sand sidebar with 4-section nav (Vineyard / Energy / Operations / Knowledge), masked logo, brand stripes, vine left-bar on active. New `Topbar.tsx` (breadcrumb Β· βK search Β· refresh Β· bell Β· avatar). New `PageHeader.tsx` (eyebrow + serif H1 with optional italic emphasis + deck + 3-row meta panel). `AppLayout` adds Topbar + footer hairline strip. | |
| - [x] **redesign-shared-components** β New `Sparkline`, `CardHead`, `DataFlowRail`, `TrackerArc`, `PlanTimeline` (96-cell ribbon), `StressHeatmap`. `StatCard` rewritten with eyebrow + delta + serif tabular number + caption + Sparkline. New chart wrappers `charts/SwAreaChart.tsx` and `charts/SwLineChart.tsx` with shared SolarWine defaults (hairline grid, mono ticks, brand palette, dashed reference lines). | |
| - [x] **redesign-home** β Editorial header, data-flow rail, 4-stat row, 96-slot plan ribbon + power chart, tracker fleet with arc dials + status pills, FvCB-vs-ML photosynthesis card, stress heatmap, **dark Advisor card** with sun-gold "Apply at next tick" CTA, 7-day climate w/ 32Β°C dashed line, chill-accumulation bars. | |
| - [x] **redesign-pages-all** β All 10 remaining pages rewritten: | |
| - **Agronomy** β 4-stat row + A1βA4 block table w/ vigor bars + soil profile + agronomy log + water budget | |
| - **Photosynthesis** β model-comparison big numbers + Chronos forecast + sensor explorer | |
| - **Shading** β 64 px serif offset slider w/ vineβsun gradient, 3-zone canopy diagram, stressΓoffset map, 8-rule policy grid | |
| - **Power** β 4-cell KPI strip + today-vs-yesterday-vs-astro line, inverter cards, weekly bars, revenue ledger | |
| - **Trackers** β fleet table w/ arc dials + status pills + 24h sparklines, dispatch log, reliability fields | |
| - **Control loop** β 5-stage pipeline with active highlight, L0..L4 priority stack, hierarchical energy budget, plan ribbon | |
| - **Advisor** β chat surface w/ confidence pills + citations + feedback, dark day-ahead brief, active recommendations, quick-asks | |
| - **Monitoring** (was Alarms) β alert stream w/ severity pills + ack, 6-source health column | |
| - **Documentation** β numbered serif accordion (10 biology rules), API ref table (21 endpoints), 12-term glossary | |
| - **Research** (new) β vineyard-photo hero band, 3 paper cards, datasets table, 7-person crew strip. `/research` route added. | |
| - [x] **redesign-build** β TypeScript clean, vite build 2.5 s, 240 KB gzipped JS. Dev server at http://localhost:8080/. | |
| **TODO markers inline (would benefit from new backend endpoints)** | |
| - [ ] **redesign-stress-grid** β `/api/photosynthesis/stress-grid?date=...` for live 4Γ24 CWSI grid (used by Home + Shading; currently synthetic). | |
| - [ ] **redesign-inverters** β `/api/energy/inverters` for per-inverter live state on Power. | |
| - [ ] **redesign-dispatch-stats** β `/api/control/dispatches` summary for p95 ack latency on Trackers. | |
| - [ ] **redesign-events-dispatch** β surface dispatch + alert events through `/api/events/*` so the Trackers log + Monitoring stream are live, not seeded. | |
| - [ ] **redesign-frontend-types-purge** β `frontend/src/types/api.ts` and hooks still declare `treatment_par_umol: number | null` etc. After the 2026 fleet migration these are always null; trim or mark deprecated. | |
| ### 12.8: Streamlit staleness audit (done 2026-05-18) | |
| - [x] **streamlit-control-replay-freshness** β `ui/tab_system_status.py:_render_control_replay`: empty-state message now uses today's date; loaded log shows a freshness warning when older than 30 days; caption surfaces the simulated date range (sim_2026-05-01 β 2026-05-10). | |
| - [x] **scripts-sim-defaults** β `scripts/run_control_simulation.py` defaults changed from `2025-07-01 / 2025-07-07` to `today β 7 d / today` so future runs are fresh. | |
| - [x] **fresh-sim-log** β Generated `Data/simulation_logs/sim_2026-05-01_2026-05-10.json` (960 ticks). Required installing `astral` into the venv (was missing; fixed 960/960 errors on the first attempt). | |
| - [x] **streamlit-shading-caption** β Added "historical analysis Β· `sensors_wide.csv` frozen at Feb 2026" caption to the Shading tab so the date picker isn't read as live. | |
| - [x] **camera-caption** β Camera image caption now says "if the view shows only sky, the on-site camera needs realignment". Physical site issue; can't be fixed from code. | |
| ### 12.4: Long-term (3+ months) | |
| - [ ] **multi-vineyard** β Refactor `config/settings.py` to multi-tenant pattern (vineyard ID β settings), add Postgres (Neon free tier) | |
| - [ ] **licor-calibration** β Ingest first real LiCor measurements, retrain Farquhar Vcmax/Jmax via `src/calibration_pipeline.py` | |
| - [ ] **chronos-vs-ml** β 3-month live comparison of Chronos-2 vs ML ensemble, promote winner to primary predictor | |
| --- | |
| ## Session notes | |
| ### 2026-02-27 β Phase 8B: Gemini LLM Data Engineering | |
| - Created `src/llm_data_engineer.py` with `LLMDataEngineer` class. | |
| - `analyze_anomalies()`: constructs a structured prompt from per-column descriptive stats + site-specific domain context (physical ranges, Negev notes) β Gemini returns a JSON with `lower_bound`, `upper_bound`, `zscore_threshold`, `iqr_multiplier`, `rationale` per column; falls back to physics-based bounds when API unavailable. | |
| - `apply_cleaning()`: applies union of bound violations + Z-score + IQR anomaly flags; three strategies: `clip` (default), `nan`, `drop`. Detected 14 PAR anomalies (0.05%), 9 VPD anomalies (0.03%), 168 COβ anomalies (0.58%) on `sensors_wide_sample.csv`. | |
| - `engineer_features()`: adds `hour_sin`, `hour_cos`, `doy_sin`, `doy_cos` (cyclical encodings); `stress_risk_score = vpd_weight Γ norm(VPD) + cwsi_weight Γ norm(CWSI)` in [0, 1]; weights sourced from Gemini (default: 0.6/0.4, falls back gracefully without CWSI). | |
| - `run_pipeline()`: single-call orchestration of anomaly detection β cleaning β feature engineering. | |
| - Added `google-genai>=1.0` to `requirements.txt`. | |
| ### 2026-02-27 β Dashboard & pvlib | |
| - **Data Explorer:** Intro text now lists all three data sources (Vineyard sensors, Weather station data, **AI Data Engineering**). Third option runs full LLM pipeline in-tab (thresholds, cleaning summary, before/after PAR/VPD, feature spec, stress profile); no cache loader, all precomputed on selection. | |
| - **Presentation tab:** βWhatβs Nextβ completed list includes **Gemini LLM data engineering** with pointer to Data Explorer β AI Data Engineering. βWhat each tab showsβ updated so Data Explorer bullet mentions AI Data Engineering. Key takeaways add Gemini data engineering (anomaly cleaning + stress/time features). | |
| - **pvlib:** `src/solar_geometry.py` β `compute_tracker_tilt()` now calls `pvlib.tracking.singleaxis()` with first two args positional (zenith, azimuth) so it works with both pvlib < 0.13.1 (`apparent_azimuth`) and β₯ 0.13.1 (`solar_azimuth`). Fixes βShadow model demo unavailable: singleaxis() got an unexpected keyword argument 'solar_azimuth'β. | |
| ### 2026-03-05 β ThingsBoard live telemetry integration (Step 1) | |
| - Rewrote `src/thingsboard_client.py`: full device registry (22 devices, UUIDs from devices.csv), area classification (treatment rows 501β502 / reference rows 503β504 / ambient Air1), parallel fetch with `ThreadPoolExecutor`, JWT auth with token refresh. | |
| - Added `VineSnapshot` dataclass: aggregated treatment vs reference readings β Air, Crop (per panel position: west-bottom/east-upper/east-bottom/west-upper), Soil, Irrigation, Thermocouples. Includes `to_advisor_text()` and `to_dict()`. | |
| - Added `_bounded_avg()` with `_BOUNDS` dict: physical plausibility filter rejects sensor faults (e.g. Soil5 thermocouple reading 653Β°C). Soil, Air, PAR, NDVI, VPD, COβ all filtered by domain bounds. | |
| - Wired into `src/vineyard_chatbot.py`: new `_tool_get_vine_state()` tool + `get_vine_state` in system prompt + dispatch router; `_tool_day_ahead_advisory()` now passes `VineSnapshot` to advisor. | |
| - Wired into `src/day_ahead_advisor.py`: optional `vine_snapshot` param in `advise()`; `to_advisor_text()` inserted before weather forecast in Gemini prompt. | |
| - TB credentials (`TB_USERNAME`, `TB_PASSWORD`) added to `.env`; config also accepts `THINGSBOARD_USERNAME`/`THINGSBOARD_PASSWORD`. | |
| - Tested live: all 22 devices fetched in 1.1 s, PAR shading ratio 0.33 (67% reduction), soil temp 15.5Β°C (outlier-filtered). | |
| - Synced Phase 9 checklist: all chatbot tasks marked done (implemented in a prior session, todo file not updated). | |
| ### 2026-03-09 β Phase 10: Anti-Hallucination Guardrails | |
| - Created `src/chatbot/guardrails.py` with 4 components: QueryClassifier (regex data/knowledge/greeting routing), ResponseValidator (deterministic rule checks with block/warn severity), estimate_confidence (data freshness β confidence level), tag_tool_result (source metadata injection). | |
| - Rewrote `src/chatbot/vineyard_chatbot.py`: integrated all guardrails into `chat()` flow β query classification β forced tool re-prompting β source-tagged tool results β response validation β confidence estimation. Extended `ChatResponse` with confidence/sources/caveats/rule_violations. Strengthened system prompt: "NEVER invent sensor readings", "MUST call a tool for data questions", "cite source and timestamp". | |
| - Updated `ui/tab_advisor.py`: new `_render_grounding_metadata()` renders colored confidence badge, source citations, caveats, and rule violation warnings under each chat response. Metadata persisted in chat history. | |
| - Added 23 new tests to `tests/test_advisor_chatbot.py`: 5 adversarial QA scenarios (shade-before-10 blocked, data-question forces tool call, stale-data warns, no-shade-without-reason warns, tool-failure returns insufficient_data) + unit tests for all guardrail components. Total: 42 tests, all passing. | |
| - Updated `context/2_plan.md` with Phase 10 architecture, validation rules, confidence mapping, and remaining steps. Updated dependency order. | |
| ### 2026-02-17 β Code quality fixes | |
| - Fixed `Preprocessor.merge_ims_with_labels` position-based fallback: now raises `ValueError` instead of silently misaligning rows when `timestamp_index_labels=False` and labels have no named index. | |
| - Removed all ~50 deprecated `use_container_width=True` calls in `app.py` (Streamlit 1.54): `st.image` β `width='stretch'`; `st.plotly_chart` / `st.dataframe` β argument removed (default is already `'stretch'`). | |
| - Fixed follow-up `SyntaxError`: regex removal of `use_container_width=True` in the middle of argument lists left double commas (`,,`); collapsed all `,,` β `,`. | |
| - Updated `context/CODE_REVIEW.md` sections 16β17 with applied fixes and remaining work list. | |
| - Updated "What's next" sections in `app.py` (tab_photosynthesis section 6 and About tab) to reflect Chronos-2 and Gemini routing as completed. | |