|
import streamlit as st |
|
import fitz |
|
from PIL import Image |
|
import pytesseract |
|
import folium |
|
from streamlit_folium import st_folium |
|
import re |
|
import tempfile |
|
import requests |
|
|
|
|
|
def extract_pdf_text(file): |
|
"""Extract text from PDF.""" |
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file: |
|
temp_file.write(file.read()) |
|
temp_path = temp_file.name |
|
doc = fitz.open(temp_path) |
|
text = "" |
|
for page in doc: |
|
text += page.get_text() |
|
return text |
|
|
|
def extract_image_text(file): |
|
"""Extract text from image.""" |
|
img = Image.open(file) |
|
return pytesseract.image_to_string(img) |
|
|
|
def extract_sanction_load_from_text(text): |
|
"""Extract sanction load from the bill text.""" |
|
match = re.search(r"load[:\-]?\s*(\d+)", text, re.IGNORECASE) |
|
return int(match.group(1)) if match else None |
|
|
|
def extract_location_from_pdf(text): |
|
"""Extract location from the second line of the PDF text.""" |
|
lines = text.split('\n') |
|
if len(lines) >= 2: |
|
location = lines[1].strip() |
|
return location |
|
return None |
|
|
|
def clean_address(address): |
|
"""Clean the extracted address text to remove unnecessary characters.""" |
|
if address: |
|
address = address.replace('\n', ' ').strip() |
|
address = re.sub(r'\s+', ' ', address) |
|
return address |
|
return None |
|
|
|
def extract_values_from_lines(text, start_line=53, end_line=60): |
|
"""Extract numeric values from a specified range of lines in the text.""" |
|
lines = text.split('\n') |
|
|
|
relevant_lines = lines[start_line-1:end_line] |
|
values = [] |
|
for line in relevant_lines: |
|
match = re.search(r"(\d+\.?\d*)", line) |
|
if match: |
|
values.append(float(match.group(1))) |
|
return values |
|
|
|
def get_suggested_solar_size(values): |
|
"""Suggest the solar size based on the maximum value found.""" |
|
if values: |
|
max_value = max(values) |
|
|
|
suggested_size = max_value / 360 |
|
return round(suggested_size, 2) |
|
return None |
|
|
|
def geocode_address_nominatim(address): |
|
"""Convert address to latitude and longitude using Nominatim API.""" |
|
if not address: |
|
return None, None |
|
|
|
url = f"https://nominatim.openstreetmap.org/search?format=json&q={address}" |
|
headers = {'User-Agent': 'EasySun-Solar-Planner/1.0 (your-email@example.com)'} |
|
try: |
|
response = requests.get(url, headers=headers) |
|
if response.status_code != 200: |
|
st.error(f"Failed to get a valid response from Nominatim API. Status Code: {response.status_code}") |
|
return None, None |
|
result = response.json() |
|
if result: |
|
lat = float(result[0]['lat']) |
|
lon = float(result[0]['lon']) |
|
st.write(f"Found location: Latitude {lat}, Longitude {lon}") |
|
return lat, lon |
|
else: |
|
st.error(f"No results found for address: {address}") |
|
return None, None |
|
except requests.exceptions.RequestException as e: |
|
st.error(f"Error connecting to Nominatim API: {e}") |
|
return None, None |
|
|
|
def calculate_solar_system(area_sqft, panel_rating, inverter_type, backup_time=0): |
|
"""Calculate solar system size and components for the Pakistan region.""" |
|
panel_area_sqft = 20 |
|
system_capacity_kw = (panel_rating / 1000) * (area_sqft / panel_area_sqft) |
|
num_panels = area_sqft // panel_area_sqft |
|
inverter_type = inverter_type.lower() |
|
|
|
|
|
battery_capacity = 0 |
|
cable_length = system_capacity_kw * 1.5 |
|
breaker_rating = system_capacity_kw * 1.25 |
|
earthing = system_capacity_kw * 0.75 |
|
|
|
|
|
if inverter_type == "off-grid" or inverter_type == "hybrid": |
|
|
|
if backup_time > 0: |
|
battery_capacity = system_capacity_kw * backup_time |
|
else: |
|
st.warning("Please provide backup time for Off-Grid or Hybrid inverter type.") |
|
|
|
inverter_type = "Hybrid" if inverter_type == "hybrid" else "Off-Grid" if inverter_type == "off-grid" else "On-Grid" |
|
|
|
|
|
panels_in_series = 3 |
|
strings_in_parallel = num_panels // panels_in_series |
|
|
|
return { |
|
"System Size (kW)": round(system_capacity_kw, 2), |
|
"Number of Panels": round(num_panels), |
|
"Inverter Type": inverter_type.capitalize(), |
|
"Battery Capacity (kWh)": round(battery_capacity, 2), |
|
"Cable Length (meters)": round(cable_length, 2), |
|
"Breaker Rating (Amps)": round(breaker_rating, 2), |
|
"Earthing (meters)": round(earthing, 2), |
|
"Panels in Series": panels_in_series, |
|
"Strings in Parallel": strings_in_parallel, |
|
} |
|
|
|
|
|
def main(): |
|
st.set_page_config(page_title="๐ EasySun: Solar Planner for All", layout="wide") |
|
|
|
|
|
st.markdown( |
|
""" |
|
<h1 style="text-align:center; color: #4CAF50;">๐ EasySun: AI Solar Planner for All</h1> |
|
<p style="text-align:center; font-size: 18px;">Simplified Solar Planning for Pakistan</p> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("### Step 1: Upload Your Electricity Bill") |
|
uploaded_file = st.file_uploader("Upload your electricity bill (PDF/JPG)", type=["pdf", "jpg", "jpeg"]) |
|
sanction_load = None |
|
location = None |
|
city = None |
|
lat, lon = None, None |
|
|
|
if uploaded_file: |
|
with st.spinner("Extracting information from the uploaded file..."): |
|
if uploaded_file.type == "application/pdf": |
|
bill_text = extract_pdf_text(uploaded_file) |
|
else: |
|
bill_text = extract_image_text(uploaded_file) |
|
|
|
st.text_area("Extracted Text", bill_text) |
|
sanction_load = extract_sanction_load_from_text(bill_text) |
|
if sanction_load: |
|
st.success(f"Sanction Load Extracted: {sanction_load} kW") |
|
else: |
|
st.error("Unable to extract sanction load.") |
|
|
|
location = extract_location_from_pdf(bill_text) |
|
if location: |
|
location = clean_address(location) |
|
st.success(f"Location Extracted: {location}") |
|
else: |
|
st.error("Unable to extract location.") |
|
|
|
|
|
st.markdown("### Step 2: Enter the City Information") |
|
city = st.text_input("Enter the city of your location (required):") |
|
if not city: |
|
st.warning("City is required to proceed.") |
|
return |
|
|
|
|
|
full_address = f"{location}, {city}, Pakistan" if location else f"{city}, Pakistan" |
|
|
|
|
|
st.markdown("### Step 3: Geolocation of Your Address") |
|
lat, lon = geocode_address_nominatim(full_address) |
|
if lat and lon: |
|
st.success(f"Location Found: Latitude {lat}, Longitude {lon}") |
|
map_ = folium.Map(location=[lat, lon], zoom_start=12) |
|
folium.Marker([lat, lon], popup=f"Location: {full_address}").add_to(map_) |
|
st_folium(map_, width=700, height=500) |
|
else: |
|
st.error(f"Unable to find the location: {full_address}.") |
|
|
|
|
|
st.markdown("### Step 4: Extract Solar Size from Bill") |
|
values = extract_values_from_lines(bill_text, start_line=53, end_line=60) |
|
if values: |
|
suggested_solar_size = get_suggested_solar_size(values) |
|
if suggested_solar_size: |
|
st.success(f"Suggested Solar System Size as per Utilization: {suggested_solar_size} kW") |
|
else: |
|
st.error("Unable to determine suggested solar size from the extracted values.") |
|
else: |
|
st.error("No numeric values found in lines 53-60.") |
|
|
|
|
|
st.markdown("### Step 5: Solar System Calculation") |
|
panel_rating = st.number_input("Enter Panel Rating (in W):", min_value=1, step=1) |
|
inverter_type = st.selectbox("Select Inverter Type", ["On-Grid", "Off-Grid", "Hybrid"]) |
|
backup_time = 0 |
|
if inverter_type in ["Off-Grid", "Hybrid"]: |
|
backup_time = st.number_input("Enter Backup Time (hours):", min_value=1, step=1) |
|
|
|
area_sqft = st.number_input("Enter roof area (square feet):", min_value=1, step=1) |
|
if st.button("Calculate Solar System"): |
|
if area_sqft > 0 and panel_rating > 0: |
|
results = calculate_solar_system(area_sqft, panel_rating, inverter_type, backup_time) |
|
st.write("### Solar System Sizing Results") |
|
for key, value in results.items(): |
|
st.write(f"- **{key}**: {value}") |
|
st.markdown("### Solar Panel Layout") |
|
for _ in range(results["Strings in Parallel"]): |
|
st.text("โ".join(["๐"] * results["Panels in Series"])) |
|
else: |
|
st.error("Please provide valid inputs for panel rating, roof area, and inverter type.") |
|
|
|
|
|
st.markdown("### Step 6: ROI Calculation") |
|
total_installation_cost = st.number_input("Enter total installation cost (in PKR):", min_value=1, step=1000) |
|
electricity_charges = st.number_input("Enter electricity charges (in PKR per kWh):", min_value=1, step=1) |
|
|
|
|
|
solar_generation_per_month_kWh = st.number_input("Enter solar generation per month (in kWh):", min_value=1, step=10) |
|
|
|
if st.button("Calculate ROI"): |
|
if total_installation_cost > 0 and electricity_charges > 0 and solar_generation_per_month_kWh > 0: |
|
|
|
monthly_savings = solar_generation_per_month_kWh * electricity_charges |
|
|
|
|
|
roi_months = total_installation_cost / monthly_savings |
|
roi_years = roi_months / 12 |
|
|
|
st.write("### ROI Calculation Results") |
|
st.write(f"- **Monthly Savings**: {monthly_savings} PKR") |
|
st.write(f"- **ROI (Months)**: {roi_months:.2f} months") |
|
st.write(f"- **ROI (Years)**: {roi_years:.2f} years") |
|
else: |
|
st.error("Please provide valid inputs for installation cost, electricity charges, and solar generation.") |
|
|
|
if __name__ == "__main__": |
|
main() |