Spaces:
Sleeping
Sleeping
| import os | |
| import math | |
| import streamlit as st | |
| import pandas as pd | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| st.set_page_config(page_title="GridCoin Calculator", layout="wide", page_icon="β‘") | |
| st.markdown(""" | |
| <style> | |
| .stNumberInput, .stSelectbox, .stSlider { | |
| margin-bottom: -1rem; | |
| } | |
| div[data-testid="stVerticalBlock"] > div { | |
| gap: 0.5rem; | |
| } | |
| .stSubheader { | |
| margin-top: 0.5rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.title("GridCoin β DER vs Non-DER Dashboard") | |
| left_col, right_col = st.columns([1, 1]) | |
| with left_col: | |
| st.subheader("βοΈ System Configuration") | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| owner = st.selectbox("Household type", ["DER Homeowner", "Non-DER Homeowner"]) | |
| pv_kw = st.number_input("Solar size (kW)", 0.0, 50.0, 7.0, step=0.5) | |
| batt_kwh = st.number_input("Battery (kWh)", 0.0, 40.0, 0.0, step=0.5) | |
| with c2: | |
| annual_load = st.number_input("Annual use (kWh)", 500, 30000, 12000) | |
| retail_rate = st.number_input("Retail rate ($/kWh)", 0.01, 1.0, 0.13, format="%.3f") | |
| upfront = st.number_input("System cost ($)", 0, 100000, 25000) | |
| st.markdown("---") | |
| st.subheader("π° Policy & GridCoin Settings") | |
| c3, c4 = st.columns(2) | |
| with c3: | |
| exp_actual = st.number_input("Export credit ($/kWh)", 0.0, 1.0, 0.075, format="%.3f") | |
| exp_evc = st.number_input("EVC Bonus ($/kWh)", 0.0, 1.0, 0.123, format="%.3f") | |
| grid_fee = st.number_input("Grid fee ($/kW-mo)", 0.0, 20.0, 4.0) | |
| with c4: | |
| peak_start = st.number_input("Peak start (hr)", 0, 23, 17) | |
| peak_end = st.number_input("Peak end (hr)", 0, 23, 21) | |
| coin_value = st.number_input("GridCoin value ($)", 0.0, 1.0, 0.05, format="%.3f") | |
| st.markdown("---") | |
| st.subheader("π GridCoin Calculator") | |
| c5, c6 = st.columns(2) | |
| with c5: | |
| peak_fraction = st.slider("Peak export share", 0.0, 1.0, 0.30, 0.05) | |
| with c6: | |
| dr_kwh = st.number_input("DR savings (kWh)", 0.0, 5000.0, 0.0) | |
| pv_yield = pv_kw * 1250 | |
| self_use_ratio = 0.65 | |
| self_used = pv_yield * self_use_ratio | |
| exports = max(0, pv_yield - self_used - batt_kwh * 200) | |
| peak_exports = exports * peak_fraction | |
| coins_peak = peak_exports * 1.0 | |
| coins_dr = dr_kwh * 0.5 | |
| coins_total = coins_peak + coins_dr | |
| coin_dollars = coins_total * coin_value | |
| bill_nonder = annual_load * retail_rate | |
| imports_der = max(0, annual_load - self_used) | |
| exp_credit = exports * exp_actual | |
| fee_der = grid_fee * pv_kw * 12 | |
| bill_der = imports_der * retail_rate + fee_der - exp_credit - coin_dollars | |
| savings = max(0, bill_nonder - bill_der) | |
| payback = upfront / savings if savings > 0 else math.inf | |
| with right_col: | |
| st.subheader("π Results Dashboard") | |
| m1, m2 = st.columns(2) | |
| m1.metric("GridCoins Earned", f"{coins_total:,.1f}") | |
| m2.metric("GridCoin Value", f"${coin_dollars:,.2f}") | |
| st.markdown("---") | |
| st.markdown("### π΅ Annual Bill Comparison") | |
| b1, b2, b3 = st.columns(3) | |
| b1.metric("Non-DER Bill", f"${bill_nonder:,.0f}") | |
| b2.metric("DER Bill", f"${bill_der:,.0f}", delta=f"${bill_der-bill_nonder:,.0f}") | |
| b3.metric("Payback Period", f"{payback:.1f} yrs" if payback != math.inf else "N/A") | |
| st.markdown("---") | |
| st.markdown("### π€ AI Analysis") | |
| if os.getenv("OPENAI_API_KEY"): | |
| try: | |
| import openai | |
| openai.api_key = os.getenv("OPENAI_API_KEY") | |
| prompt = ( | |
| f"Write a clear, well-formatted summary in 2-3 complete sentences with proper spacing:\n\n" | |
| f"A {owner} with a {pv_kw} kW solar system and {batt_kwh} kWh battery " | |
| f"earns {coins_total:.1f} GridCoins valued at ${coin_dollars:.2f} annually. " | |
| f"Their annual electricity bill is ${bill_der:,.0f} (DER) compared to ${bill_nonder:,.0f} (Non-DER), " | |
| f"with a payback period of {payback:.1f} years.\n\n" | |
| "Explain the financial benefits clearly and concisely. Use proper sentence structure with spaces between words." | |
| ) | |
| with st.spinner("Generating..."): | |
| resp = openai.ChatCompletion.create( | |
| model="gpt-4o", | |
| messages=[ | |
| {"role": "system", "content": "You are a helpful energy policy assistant. Provide clear, concise summaries."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| max_tokens=150, | |
| temperature=0.7 | |
| ) | |
| summary = resp.choices[0].message.content.strip() | |
| st.markdown(f""" | |
| <div style="background-color: #e8f4f8; padding: 1rem; border-radius: 0.5rem; border-left: 4px solid #0068c9;"> | |
| <p style="margin: 0; line-height: 1.6; color: #0c0c0c;">{summary}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| except Exception as e: | |
| st.warning(f"AI summary unavailable: {e}") | |
| else: | |
| st.caption("Set OPENAI_API_KEY to enable AI summaries.") |