import streamlit as st import pandas as pd import numpy as np import smtplib import re import os from dotenv import load_dotenv # <--- Import this from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders # --- Configuration --- load_dotenv() # <--- This loads the variables from .env # Now we read the secrets using os.getenv SENDER_EMAIL = os.getenv("SENDER_EMAIL") SENDER_PASSWORD = os.getenv("SENDER_PASSWORD") if not SENDER_EMAIL or not SENDER_PASSWORD: st.error("Error: Email credentials not found. Please check your .env file.") st.stop() # --- Helper Functions --- def validate_email(email): pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) def send_email(receiver_email, result_df, filename="result.csv"): try: msg = MIMEMultipart() msg['From'] = SENDER_EMAIL msg['To'] = receiver_email msg['Subject'] = "Your TOPSIS Result is Ready" body = "Hello,\n\nPlease find the attached result file for your TOPSIS calculation.\n\nBest,\nTOPSIS Web Service" msg.attach(MIMEText(body, 'plain')) # Convert DataFrame to CSV string csv_data = result_df.to_csv(index=False) # Attachment part = MIMEBase('application', 'octet-stream') part.set_payload(csv_data) encoders.encode_base64(part) part.add_header('Content-Disposition', f"attachment; filename= {filename}") msg.attach(part) # SMTP Server Setup (For Gmail) - USING SSL PORT 465 # Note: SMTP_SSL does not need server.starttls() server = smtplib.SMTP_SSL('smtp.gmail.com', 465) server.login(SENDER_EMAIL, SENDER_PASSWORD) text = msg.as_string() server.sendmail(SENDER_EMAIL, receiver_email, text) server.quit() return True, "Email sent successfully!" except Exception as e: return False, str(e) def calculate_topsis(df, weights, impacts): # This logic is adapted from your CLI package try: # Preprocessing: Drop first column (Object Names) and convert to float data = df.iloc[:, 1:].values.astype(float) # Normalization rss = np.sqrt(np.sum(data**2, axis=0)) normalized_data = data / rss # Weighting weighted_data = normalized_data * weights # Ideal Best and Worst ideal_best = [] ideal_worst = [] for i in range(len(impacts)): if impacts[i] == '+': ideal_best.append(np.max(weighted_data[:, i])) ideal_worst.append(np.min(weighted_data[:, i])) else: ideal_best.append(np.min(weighted_data[:, i])) ideal_worst.append(np.max(weighted_data[:, i])) # Euclidean Distance s_best = np.sqrt(np.sum((weighted_data - ideal_best)**2, axis=1)) s_worst = np.sqrt(np.sum((weighted_data - ideal_worst)**2, axis=1)) # Topsis Score topsis_score = s_worst / (s_best + s_worst) # Add to DF df['Topsis Score'] = topsis_score df['Rank'] = df['Topsis Score'].rank(ascending=False).astype(int) return df except Exception as e: st.error(f"Error in calculation: {e}") return None # --- Main App UI --- st.title("Topsis Web Service") st.write("Upload your data, define weights/impacts, and get results via email.") # 1. Inputs email_id = st.text_input("Email ID (to receive results)", placeholder="name@example.com") uploaded_file = st.file_uploader("Upload Input File (CSV)", type=["csv"]) weights_input = st.text_input("Weights (comma-separated)", placeholder="1,1,1,2") impacts_input = st.text_input("Impacts (comma-separated, + or -)", placeholder="+,+,-,+") # 2. Submit Button if st.button("Submit"): # --- Validations --- if not uploaded_file: st.error("Please upload a CSV file.") elif not email_id or not validate_email(email_id): st.error("Please enter a valid email address.") elif not weights_input: st.error("Please enter weights.") elif not impacts_input: st.error("Please enter impacts.") else: # Process Inputs try: df = pd.read_csv(uploaded_file) # Parse Weights weights = [float(w) for w in weights_input.split(',')] # Parse Impacts impacts = impacts_input.split(',') # Validation: Column Count num_cols = df.shape[1] - 1 # Exclude first column if len(weights) != num_cols or len(impacts) != num_cols: st.error(f"Count Mismatch! Input file has {num_cols} numerical columns, but you provided {len(weights)} weights and {len(impacts)} impacts.") elif not all(i in ['+', '-'] for i in impacts): st.error("Impacts must be either '+' or '-'.") else: # --- Run Logic --- st.info("Calculating TOPSIS Score...") result_df = calculate_topsis(df, weights, impacts) if result_df is not None: # --- Send Email --- st.info(f"Sending result to {email_id}...") success, message = send_email(email_id, result_df) if success: st.success("Success! Result file has been sent to your email.") st.dataframe(result_df) # Show preview else: st.error(f"Failed to send email: {message}") except ValueError: st.error("Weights must be numeric values separated by commas.") except Exception as e: st.error(f"An unexpected error occurred: {e}")