| import streamlit as st |
|
|
| st.set_page_config( |
| page_title="Smart Construction App: IoT-Based Real-Time Material Tracking, Quantification, and Pricing", |
| layout="wide" |
| ) |
| 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 |
| import io |
| import time |
|
|
| |
| SPREADSHEET_ID = "1NxEFF5xQFTgDC2exGbb37RixtVjlDJ9H20nyQcJMZhw" |
| SHEET_NAME = "Weighing_Sheet" |
| RANGE = "A1:D1000" |
|
|
| |
| BLYNK_AUTH_TOKEN = "AGlSvlX_z72VFvwZBgpHTZOKKDudEtjz" |
| BLYNK_API_URL = f"https://blynk.cloud/external/api/get?token={BLYNK_AUTH_TOKEN}&V4" |
|
|
| |
| json_data = st.secrets["GOOGLE_CREDENTIALS_JSON"] |
| credentials = Credentials.from_service_account_info(json.loads(json_data)) |
|
|
| |
| 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(): |
| |
| |
| tabs = st.tabs(["Home", "Weight Monitoring", "Reports & Summaries"]) |
| |
| |
| with tabs[0]: |
| |
| col1, col2 = st.columns([0.5, 0.5]) |
| 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. 🚀🤝** |
| """) |
| |
| 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'.") |
| |
| |
|
|
| |
| st.subheader("Problems & Benefits") |
| |
| col1, col2 = st.columns(2) |
| |
| |
| with col1: |
| st.subheader("Problems") |
| col3, col4 = st.columns([0.2, 0.8]) |
| 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 |
| """) |
| |
| |
| with col2: |
| st.subheader("Benefits") |
| col5, col6 = st.columns([0.2, 0.8]) |
| 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 |
| """) |
|
|
|
|
| |
| |
| 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"]) |
|
|
| |
| 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: |
| |
| live_weight = fetch_live_blynk_data() |
|
|
| |
| 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):] |
|
|
| |
| 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)}") |
|
|
| |
| 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() |
|
|
| |
| with tabs[2]: |
| st.header("Reports and Summaries") |
| |
| |
| 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") |
| |
| |
| data = fetch_google_sheet() |
| |
| if not data.empty: |
| |
| 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) |
| |
| |
| if 'Type of Material' not in data.columns: |
| data['Type of Material'] = material_type |
| |
| |
| 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 |
| data['Price per Load'] = data['Qty in Cft'] * material_rate |
| data['Cummulative Price'] = data['Price per Load'].cumsum() |
| |
| |
| st.dataframe(data) |
| |
| |
| if st.button("Generate Summary Report"): |
| summary = data.groupby('Type of Material').agg({ |
| 'Qty in Cft': 'sum', |
| 'Rate of Material': 'first', |
| 'Price per Load': 'sum' |
| }).reset_index() |
| |
| |
| total_row = pd.DataFrame({ |
| 'Type of Material': ['Total'], |
| 'Qty in Cft': [summary['Qty in Cft'].sum()], |
| 'Rate of Material': [None], |
| 'Price per Load': [summary['Price per Load'].sum()] |
| }) |
| summary = pd.concat([summary, total_row], ignore_index=True) |
| |
| st.write("Summary Report:") |
| st.dataframe(summary) |
| |
| |
| 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") |
|
|
| |
| st.markdown("<p style='text-align: right; color: gray;'>Created by Abeer Ahmed Jadoon</p>", unsafe_allow_html=True) |
|
|
| if __name__ == "__main__": |
| main() |
|
|