Israelbliz's picture
Upload 15 files
c7c92fb verified
Raw
History Blame Contribute Delete
5.75 kB
"""
Streamlit front-end for the DREEF PUE report generator.
Upload a community master-sheet workbook, set the developer / date (and any
externally-supplied mini-grid capacities), and download a template-faithful
Word report. Optional AI narratives use Gemini and require GOOGLE_API_KEY.
Run locally: streamlit run app.py
Deploy: push this folder to a Hugging Face Space (SDK = Streamlit).
"""
from __future__ import annotations
import os
import tempfile
from io import BytesIO
import streamlit as st
from pue_report_agent import extract_master_sheet, recompute, validate
st.set_page_config(page_title="PUE Report Generator", page_icon="📄",
layout="centered")
st.title("📄 PUE Report Generator")
st.caption(
"Turn a community PUE master-sheet workbook into a template-faithful Word "
"report. Every number is recomputed from the sheet — the AI only ever "
"touches prose, never figures."
)
# --------------------------------------------------------------------------- #
# Sidebar — options
# --------------------------------------------------------------------------- #
with st.sidebar:
st.header("Report settings")
developer = st.text_input("Mini-grid developer",
value="the mini-grid developer")
report_date = st.text_input("Report date (title page)", value="")
st.subheader("Mini-grid capacities")
st.caption(
"Developer-supplied; only fill these if they aren't already in the "
"sheet's Key Findings."
)
solar_pv = st.text_input("Solar PV", value="", placeholder="e.g. 6.45 MWp")
battery = st.text_input("Battery storage", value="", placeholder="e.g. 10 MWh")
annual_consumption = st.text_input("Annual consumption", value="",
placeholder="e.g. 5 MW")
st.subheader("AI narratives")
def _has_api_key() -> bool:
if os.environ.get("GOOGLE_API_KEY"):
return True
try:
return bool(st.secrets.get("GOOGLE_API_KEY"))
except Exception:
return False
has_key = _has_api_key()
want_narratives = st.toggle(
"Generate AI prose sections (Gemini)", value=False,
help="Needs GOOGLE_API_KEY in the Space secrets or environment. "
"Without it, the three prose sections use a plain fallback.")
if want_narratives and not has_key:
st.warning("No GOOGLE_API_KEY found — narratives will be skipped.")
# --------------------------------------------------------------------------- #
# Main — upload & generate
# --------------------------------------------------------------------------- #
uploaded = st.file_uploader(
"Upload the PUE master sheet (.xlsx)", type=["xlsx", "xlsm"])
if uploaded is None:
st.info("Upload a master-sheet workbook to begin.")
st.stop()
if st.button("Generate report", type="primary"):
with st.spinner("Extracting, recomputing, validating…"):
# extract_master_sheet expects a path, so spill the upload to a temp file.
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
tmp.write(uploaded.getvalue())
xlsx_path = tmp.name
try:
data = extract_master_sheet(xlsx_path)
data = recompute(data)
# Merge any developer-supplied capacity overrides.
if solar_pv:
data.minigrid.solar_pv = solar_pv
if battery:
data.minigrid.battery_storage = battery
if annual_consumption:
data.minigrid.annual_consumption = annual_consumption
data = validate(data)
narrative_note = None
if want_narratives:
from pue_report_agent import generate_narratives
if generate_narratives is None:
narrative_note = ("langchain isn't installed — prose "
"sections used the fallback.")
elif not has_key:
narrative_note = ("No API key — prose sections used the "
"fallback.")
else:
data = generate_narratives(data)
# Render to a temp .docx and read the bytes back for download.
from pue_report_agent.render import render
out_path = os.path.join(tempfile.gettempdir(),
"PUE_Report.docx")
render(data, out_path, developer=developer,
report_date=report_date)
with open(out_path, "rb") as fh:
docx_bytes = fh.read()
finally:
try:
os.unlink(xlsx_path)
except OSError:
pass
st.success(f"Report generated for {data.community.name or 'the community'}.")
if narrative_note:
st.info(narrative_note)
fname = f"{(data.community.name or 'Community').replace(' ', '_')}_PUE_Report.docx"
st.download_button(
"⬇️ Download Word report", data=docx_bytes, file_name=fname,
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
type="primary")
# Quick at-a-glance summary.
col1, col2, col3 = st.columns(3)
col1.metric("Interviewed", data.interviews.total)
col2.metric("Machines", len(data.processors.machinery))
col3.metric("Equipment cost (₦)",
f"{(data.budget.grand_total or 0):,.0f}")
if data.warnings:
with st.expander(f"⚠️ {len(data.warnings)} item(s) to review", expanded=True):
for w in data.warnings:
st.write("• " + w)
else:
st.caption("No validation warnings.")