import streamlit as st st.set_page_config( page_title="Smart Construction App: IoT-Based Real-Time Material Tracking, Quantification, and Pricing", layout="wide" # This removes the sidebar and uses a wide layout ) import pandas as pd import numpy as np import plotly.graph_objects as go from google.oauth2.service_account import Credentials from googleapiclient.discovery import build import json import requests from datetime import datetime, timedelta import pytz # For timezone adjustments import io import time # Google Sheets Constants SPREADSHEET_ID = "1NxEFF5xQFTgDC2exGbb37RixtVjlDJ9H20nyQcJMZhw" SHEET_NAME = "Weighing_Sheet" RANGE = "A1:D1000" # Blynk Token and API URL BLYNK_AUTH_TOKEN = "AGlSvlX_z72VFvwZBgpHTZOKKDudEtjz" BLYNK_API_URL = f"https://blynk.cloud/external/api/get?token={BLYNK_AUTH_TOKEN}&V4" # Google Credentials from secrets json_data = st.secrets["GOOGLE_CREDENTIALS_JSON"] credentials = Credentials.from_service_account_info(json.loads(json_data)) # Set your local timezone (adjust as needed) LOCAL_TZ = pytz.timezone("Asia/Karachi") def fetch_google_sheet(): """Fetch data from Google Sheets.""" try: service = build('sheets', 'v4', credentials=credentials) sheet = service.spreadsheets() result = sheet.values().get(spreadsheetId=SPREADSHEET_ID, range=f"{SHEET_NAME}!{RANGE}").execute() values = result.get('values', []) if not values: st.warning("No data found in the Google Sheet.") return pd.DataFrame() return pd.DataFrame(values[1:], columns=values[0]) except Exception as e: st.error(f"Error reading Google Sheets: {e}") return pd.DataFrame() def fetch_live_blynk_data(): """Fetch live weight data from Blynk.""" try: response = requests.get(BLYNK_API_URL) if response.status_code == 200: return float(response.text) else: st.error(f"Failed to fetch data from Blynk: {response.status_code}") return 0.0 except Exception as e: st.error(f"Error fetching Blynk data: {e}") return 0.0 def main(): # Create Tabs tabs = st.tabs(["Home", "Weight Monitoring", "Reports & Summaries"]) # Home Tab Layout with tabs[0]: # Adjust layout: Title, Subheader, Tagline on the left, Image on the right col1, col2 = st.columns([0.5, 0.5]) # Adjust column widths as needed with col1: st.title("Smart Construction App") st.subheader("IoT-Based Real-Time Material Tracking, Quantification, and Pricing") st.markdown(""" **Your Trusted Partner for Reliable, Real-Time, and Innovative Construction Material Quantity Monitoring, Quantification, and Costing Solutions. 🚀🤝** """) # Add Project Overview below st.subheader("Project Overview") st.markdown(""" - Track construction materials in real time - Ensure accurate costing and quantification - Leverage IoT-based solutions for enhanced efficiency - Reduce material wastage and improve accountability """) with col2: try: st.image("placeholder_logo.jpg", use_container_width=True) except Exception: st.warning("Image not found. Please upload 'placeholder_logo.jpg'.") # Benefits and Problems Section Side-by-Side st.subheader("Problems & Benefits") col1, col2 = st.columns(2) # Create two equal-width columns # Benefits Section (Left Column) with col1: st.subheader("Problems") col3, col4 = st.columns([0.2, 0.8]) # Adjust logo and text widths as needed with col3: st.image("problems_logo.png", use_container_width=True) with col4: st.markdown(""" - Manual tracking errors - Lack of real-time updates - Inefficient resource management - Overrun costs """) # Problems Section (Right Column) with col2: st.subheader("Benefits") col5, col6 = st.columns([0.2, 0.8]) # Adjust logo and text widths as needed with col5: st.image("benefits_logo.png", use_container_width=True) with col6: st.markdown(""" - Real-time monitoring - Improved accuracy - Enhanced productivity - Cost-effective solutions """) # Weight Monitoring Tab with tabs[1]: st.header("Real-Time Weight Monitoring") with st.container(): material_type = st.selectbox("Select Material Type", ["Sand", "Crush", "Aggregate", "Pan", "Soil", "Other"]) # Placeholders for live updates weight_gauge_placeholder = st.empty() weight_time_plot_placeholder = st.empty() if "time_series" not in st.session_state: st.session_state.time_series = [] st.session_state.weight_series = [] if st.button("Start Monitoring"): while True: # Fetch live data live_weight = fetch_live_blynk_data() # Update Time and Weight Series (Keep data for the last 2 hours) current_time = datetime.now(LOCAL_TZ) st.session_state.time_series.append(current_time) st.session_state.weight_series.append(live_weight) two_hours_ago = current_time - timedelta(hours=2) st.session_state.time_series = [t for t in st.session_state.time_series if t >= two_hours_ago] st.session_state.weight_series = st.session_state.weight_series[-len(st.session_state.time_series):] # Update Weight Gauge gauge_fig = go.Figure(go.Indicator( mode="gauge+number", value=live_weight, title={'text': "Weight (Kg)"}, gauge={ 'axis': {'range': [0, 20000]}, 'bar': {'color': "darkblue"}, } )) weight_gauge_placeholder.plotly_chart(gauge_fig, use_container_width=True, key=f"gauge_{len(st.session_state.time_series)}") # Update Weight vs Time Plot weight_time_fig = go.Figure() weight_time_fig.add_trace(go.Scatter( x=[t.strftime("%H:%M:%S") for t in st.session_state.time_series], y=st.session_state.weight_series, mode='lines+markers', name='Weight')) weight_time_fig.update_layout(title="Weight vs. Time", xaxis_title="Time", yaxis_title="Weight (Kg)") weight_time_plot_placeholder.plotly_chart(weight_time_fig, use_container_width=True, key=f"time_{len(st.session_state.time_series)}") time.sleep(30) st.experimental_rerun() # Reports & Summaries Tab with tabs[2]: st.header("Reports and Summaries") # User Inputs for Material Type and Rate in the Reports & Summaries Tab material_type = st.selectbox("Select Material Type", ["Sand", "Crush", "Aggregate", "Pan", "Soil", "Other"], key="material_type_summary") material_rate = st.number_input("Rate per Cubic Feet (PKR)", min_value=0.0, step=0.1, key="rate_summary") # Fetch Google Sheets data data = fetch_google_sheet() if not data.empty: # Ensure numeric columns are converted properly data['Loading Weight'] = pd.to_numeric(data['Loading Weight'], errors='coerce').fillna(0) data['Unloading Weight'] = pd.to_numeric(data['Unloading Weight'], errors='coerce').fillna(0) # Add 'Type of Material' column if not already present if 'Type of Material' not in data.columns: data['Type of Material'] = material_type # Assign user-selected material type # Compute additional columns data['Difference in Weight'] = data['Loading Weight'] - data['Unloading Weight'].shift(-1, fill_value=0) data['Received Weight'] = data['Difference in Weight'] data['Cummulative Wt'] = data['Received Weight'].cumsum() data['Kg to Cft'] = data['Received Weight'] / 40 data['Qty in Cft'] = data['Kg to Cft'] data['Rate of Material'] = material_rate # Add the Rate of Material column data['Price per Load'] = data['Qty in Cft'] * material_rate data['Cummulative Price'] = data['Price per Load'].cumsum() # Display the updated DataFrame st.dataframe(data) # Generate Summary if st.button("Generate Summary Report"): summary = data.groupby('Type of Material').agg({ 'Qty in Cft': 'sum', 'Rate of Material': 'first', # Show the rate of material 'Price per Load': 'sum' }).reset_index() # Add a Total Row total_row = pd.DataFrame({ 'Type of Material': ['Total'], 'Qty in Cft': [summary['Qty in Cft'].sum()], 'Rate of Material': [None], # Total row does not need a specific rate 'Price per Load': [summary['Price per Load'].sum()] }) summary = pd.concat([summary, total_row], ignore_index=True) st.write("Summary Report:") st.dataframe(summary) # Allow users to download the summary as an Excel file buffer = io.BytesIO() with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer: data.to_excel(writer, index=False, sheet_name='Detailed Report') summary.to_excel(writer, index=False, sheet_name='Summary') st.download_button("Download Report", data=buffer.getvalue(), file_name="Material_Summary_Report.xlsx") # Footer st.markdown("

Created by Abeer Ahmed Jadoon

", unsafe_allow_html=True) if __name__ == "__main__": main()