api / app.py
safraeli's picture
Deploy: 2026 sensor migration + redesign + bucket B endpoints
13fc29d verified
Raw
History Blame Contribute Delete
6.15 kB
"""
Streamlit app: SolarWine — Smart Shading for Vineyard Solar Panels.
5 tabs: Overview, Photosynthesis & Data, Forecasting, Shading Simulator, Documentation.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
try:
from dotenv import load_dotenv
load_dotenv(PROJECT_ROOT / ".env")
except ImportError:
pass
import os
import streamlit as st
# Copy Streamlit secrets into os.environ so downstream clients (ThingsBoard,
# IMS, etc.) can read them via os.environ without any code changes.
for key, value in st.secrets.items():
if isinstance(value, str) and key not in os.environ:
os.environ[key] = value
from ui.bootstrap import img_to_base64, now_israel
# ---------------------------------------------------------------------------
# Page config
# ---------------------------------------------------------------------------
st.set_page_config(page_title="SolarWine - Smart Shading for Vineyards", layout="wide")
# ---------------------------------------------------------------------------
# Migration banner (shows only when new frontend URL is configured)
# ---------------------------------------------------------------------------
_NEW_FRONTEND_URL = os.environ.get("SOLARWINE_FRONTEND_URL", "")
if _NEW_FRONTEND_URL:
st.info(
f"A new version of the SolarWine dashboard is available at "
f"[{_NEW_FRONTEND_URL}]({_NEW_FRONTEND_URL}). "
f"This Streamlit app will be retired soon.",
icon="🔄",
)
# ---------------------------------------------------------------------------
# Brand CSS
# ---------------------------------------------------------------------------
_BRAND_GREEN = "#00BD3E"
_BRAND_DARK = "#1A1A1A"
st.markdown(f"""
<style>
/* SolarWine brand overrides — async font load (non-blocking) */
@font-face {{
font-family: 'Open Sans';
font-style: normal;
font-weight: 300 400 600 700;
font-display: swap;
src: url('https://fonts.gstatic.com/s/opensans/v40/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-mu0SC55I.woff2') format('woff2');
}}
html, body, [class*="css"] {{
font-family: 'Open Sans', sans-serif;
}}
/* Header bar */
header[data-testid="stHeader"] {{
background-color: {_BRAND_DARK};
}}
/* Metric value color */
[data-testid="stMetricValue"] {{
color: {_BRAND_GREEN};
}}
/* Tab labels */
button[data-baseweb="tab"] {{
font-weight: 600;
}}
/* Sidebar background */
section[data-testid="stSidebar"] {{
background-color: {_BRAND_DARK};
}}
section[data-testid="stSidebar"] * {{
color: #FFFFFF !important;
}}
section[data-testid="stSidebar"] button {{
background-color: {_BRAND_GREEN} !important;
color: #FFFFFF !important;
border: none !important;
border-radius: 6px !important;
}}
section[data-testid="stSidebar"] button:hover {{
background-color: #00a035 !important;
}}
/* Hero banner */
.hero-banner {{
background: linear-gradient(135deg, {_BRAND_DARK} 0%, #2d3a2d 100%);
padding: 2rem 2.5rem;
border-radius: 12px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 2rem;
}}
.hero-banner img {{
max-height: 48px;
}}
.hero-text {{
color: #FFFFFF;
}}
.hero-text h1 {{
margin: 0 0 0.3rem 0;
font-size: 1.8rem;
font-weight: 700;
color: #FFFFFF;
}}
.hero-text p {{
margin: 0;
font-size: 1.05rem;
color: #B8D4B8;
}}
</style>
""", unsafe_allow_html=True)
# ---------------------------------------------------------------------------
# Hero banner with logo
# ---------------------------------------------------------------------------
_ASSETS = PROJECT_ROOT / "assets"
_logo_path = _ASSETS / "logo.png"
if _logo_path.exists():
_logo_b64 = img_to_base64(_logo_path)
st.markdown(f"""
<div class="hero-banner">
<img src="data:image/png;base64,{_logo_b64}" alt="SolarWine logo">
<div class="hero-text">
<h1>Smart Shading for Vineyard Solar Panels</h1>
<p>Empowering growers · Harvesting sunshine</p>
</div>
</div>
""", unsafe_allow_html=True)
else:
st.title("SolarWine — Smart Shading for Vineyard Solar Panels")
# ---------------------------------------------------------------------------
# Sidebar
# ---------------------------------------------------------------------------
sidebar = st.sidebar
if _logo_path.exists():
sidebar.image(str(_logo_path), width=180)
_now = now_israel()
sidebar.caption("**Site:** Yeruham, Israel")
sidebar.markdown(f"**Date:** {_now.strftime('%Y-%m-%d')}")
sidebar.markdown(f"**Time (local):** {_now.strftime('%H:%M')}")
st.session_state.current_time_israel = _now
# ---------------------------------------------------------------------------
# Navigation — only the selected page renders (avoids running all 6 tabs)
# ---------------------------------------------------------------------------
_PAGES = [
"System Status",
"Overview",
"Photosynthesis & Data",
"Forecasting",
"Shading Simulator",
"Documentation",
]
_selected = sidebar.radio("Navigate", _PAGES, label_visibility="collapsed")
if _selected == "System Status":
from ui.tab_system_status import render_tab_system_status
render_tab_system_status()
elif _selected == "Overview":
from ui.tab_overview import render_tab_overview
render_tab_overview()
elif _selected == "Photosynthesis & Data":
from ui.tab_data import render_tab_data
render_tab_data()
elif _selected == "Forecasting":
from ui.tab_forecast import render_tab_forecast
render_tab_forecast()
elif _selected == "Shading Simulator":
from ui.tab_shading import render_tab_shading
render_tab_shading()
elif _selected == "Documentation":
from ui.tab_docs import render_tab_docs
render_tab_docs()