import streamlit as st import fitz # PyMuPDF from PIL import Image import pytesseract import folium from streamlit_folium import st_folium import re import tempfile import requests # Helper Functions 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) # Replace multiple spaces with one 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') # Extract lines 53 to 60 (0-indexed, so 52 to 59 in the list) relevant_lines = lines[start_line-1:end_line] values = [] for line in relevant_lines: match = re.search(r"(\d+\.?\d*)", line) # Match numeric values (including decimals) if match: values.append(float(match.group(1))) # Convert matched value to float return values def get_suggested_solar_size(values): """Suggest the solar size based on the maximum value found.""" if values: max_value = max(values) # Divide the maximum value by 360 to get the suggested solar size in kW suggested_size = max_value / 360 return round(suggested_size, 2) # Round to 2 decimal places for kW 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) # Convert panel rating to kW num_panels = area_sqft // panel_area_sqft inverter_type = inverter_type.lower() # Default parameters battery_capacity = 0 cable_length = system_capacity_kw * 1.5 breaker_rating = system_capacity_kw * 1.25 earthing = system_capacity_kw * 0.75 # Inverter type based logic if inverter_type == "off-grid" or inverter_type == "hybrid": # Calculate battery capacity based on backup time (for Off-Grid and Hybrid only) if backup_time > 0: battery_capacity = system_capacity_kw * backup_time # Assuming 1 hour of battery per kW system size 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 and strings in parallel calculations 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, } # Streamlit UI def main(): st.set_page_config(page_title="🌞 EasySun: Solar Planner for All", layout="wide") # Header Section st.markdown( """
Simplified Solar Planning for Pakistan
""", unsafe_allow_html=True) # File Upload Section 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.") # City Input Section 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 Generation full_address = f"{location}, {city}, Pakistan" if location else f"{city}, Pakistan" # Geocode Address Section 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}.") # Extract Values and Suggested Solar Size Section 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.") # Solar System Calculation Section 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.") # ROI Calculation Section 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) # Assuming monthly solar generation (this value can be adjusted based on location or other factors) 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: # Calculate monthly savings (based on solar generation and electricity charges) monthly_savings = solar_generation_per_month_kWh * electricity_charges # Calculate the ROI in months and years 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()