diff --git a/mental_health_hub (James Nardella)/ROOT_README.md b/mental_health_hub (James Nardella)/ROOT_README.md new file mode 100644 index 0000000..a6a3d7f --- /dev/null +++ b/mental_health_hub (James Nardella)/ROOT_README.md @@ -0,0 +1,47 @@ +# Mental Health Hub - Individual Contributions (James) + +This repository contains my individual contributions to the **Wearables for Seniors - Mental Health Hub** project. +The work here demonstrates different security, integration, and prototype development tasks completed during the trimester. + +Each subfolder contains its own project with relevant code, documentation, and (where necessary) a detailed `README.md` for setup and usage. + +--- + +## Repository Structure + +- **logging_demo/** + Demonstration of Python logging for better debugging, auditing, and system monitoring. + → See the folder `README.md` for details and usage. + +- **incident_response_plan/** + Written documentation outlining an Incident Response Plan for the project. + +- **flask_jwt_demo/** + Prototype Flask application showcasing authentication with JWT (JSON Web Tokens). + → Setup and run instructions in the folder `README.md`. + +- **streamlit_hub_app/** + Streamlit prototype of the Mental Health Hub user interface. + → Setup and run instructions in the folder `README.md`. + +- **threat_model/** + Threat modeling documentation using STRIDE to evaluate security risks. + +- **Collaborative Confidence Model Summary.pdf** + Summary document outlining the Collaborative Confidence Model research. + +- **website_integration_plan.pdf** + Technical integration and deployment roadmap for moving from prototype to production. + +--- + +## Getting Started + +To explore any of the technical demos (e.g., Streamlit app, Flask JWT), please open the corresponding folder and follow its `README.md` setup guide. +All documentation-based contributions (e.g., plans, summaries) are in PDF or markdown format for easy review. + +--- + +## Author + +This work was completed individually by **James** as part of the *Wearables for Seniors - Mental Health Hub* project. \ No newline at end of file diff --git a/mental_health_hub (James Nardella)/docs/Collaborative Confidence Model Summary.pdf b/mental_health_hub (James Nardella)/docs/Collaborative Confidence Model Summary.pdf new file mode 100644 index 0000000..fb5e29a Binary files /dev/null and b/mental_health_hub (James Nardella)/docs/Collaborative Confidence Model Summary.pdf differ diff --git a/mental_health_hub (James Nardella)/docs/website integration plan.pdf b/mental_health_hub (James Nardella)/docs/website integration plan.pdf new file mode 100644 index 0000000..0daf842 Binary files /dev/null and b/mental_health_hub (James Nardella)/docs/website integration plan.pdf differ diff --git a/mental_health_hub (James Nardella)/flask_jwt_demo/Token-Based Authentication (JWT).py b/mental_health_hub (James Nardella)/flask_jwt_demo/Token-Based Authentication (JWT).py new file mode 100644 index 0000000..a5c3c09 --- /dev/null +++ b/mental_health_hub (James Nardella)/flask_jwt_demo/Token-Based Authentication (JWT).py @@ -0,0 +1,110 @@ +from flask import Flask, request, jsonify #web framework to create routes like /login, /register +import bcrypt #hashes the passwords +import jwt #creates and verifies token +import datetime #helps set token expiry times + +app = Flask(__name__) #creates the flask app +app.config['SECRET_KEY'] = "super-secret-key" # + + + +users_db = {} #testing database + + + +def hash_password(password: str) -> bytes: + """Securely hash a plaintext password using bcrypt.""" + return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) + +def verify_password(password: str, hashed: bytes) -> bool: + """Verify that a given plaintext password matches the stored bcrypt hash.""" + return bcrypt.checkpw(password.encode('utf-8'), hashed) + + + +def generate_token(username: str) -> str: + #create the payload + payload = { + 'user': username, #store the username + 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) #set expiration time (30 minutes) + } + #encode the password into a JWT token, signed with the secret key + token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm="HS256") + return token + +def verify_token(token: str): + try: + #decode the token and check the signature and expiry + payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + return payload + except jwt.ExpiredSignatureError: + # If token is invalid or expired, return None + return None + except jwt.InvalidTokenError: + return None + + +#user registration +@app.route('/register', methods=['POST']) +def register(): + data = request.get_json() + username = data.get("username") + password = data.get("password") + + if username in users_db: + return jsonify({"error": "User already exists"}), 400 + + #save the user with a hashed password + hashed_pw = hash_password(password) + users_db[username] = hashed_pw + + return jsonify({"message": f"User {username} registered successfully"}), 201 + +#user login +@app.route('/login', methods=['POST']) +def login(): + data = request.get_json() + username = data.get("username") + password = data.get("password") + +#check if user exists + if username not in users_db: + return jsonify({"error": "Invalid credentials"}), 401 + + #check if password matches the hashed one + stored_pw = users_db[username] + if not verify_password(password, stored_pw): + return jsonify({"error": "Invalid credentials"}), 401 + + #if password is correct then generate token + token = generate_token(username) + return jsonify({"message": f"Welcome, {username}!", "token": token}), 200 + +@app.route('/protected', methods=['GET']) +def protected(): + #read the authorization" header (format: Bearer ) + auth_header = request.headers.get("Authorization") +#if no header or wrong format then deny access + if not auth_header or not auth_header.startswith("Bearer "): + return jsonify({"error": "Missing or invalid token"}), 401 + #extract the token + token = auth_header.split(" ")[1] + payload = verify_token(token) + #if token is invalid/expired then deny access + if not payload: + return jsonify({"error": "Invalid or expired token"}), 401 +# if token is valid then user gets access + + username = payload["user"] + return jsonify({"message": f"Hello {username}, you accessed a protected route!"}) + +@app.route('/logout', methods=['POST']) +def logout(): + + #logout is just deleting the token from the client side + + return jsonify({"message": "Logout by deleting token on client-side"}), 200 + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/mental_health_hub (James Nardella)/flask_jwt_demo/readme.md b/mental_health_hub (James Nardella)/flask_jwt_demo/readme.md new file mode 100644 index 0000000..0d2cbae --- /dev/null +++ b/mental_health_hub (James Nardella)/flask_jwt_demo/readme.md @@ -0,0 +1,65 @@ +# Flask JWT Authentication Demo + +## Overview +This project demonstrates a **token-based authentication system** using **Flask** and **JWT (JSON Web Tokens)**. +It includes: + +- **User Registration** - securely hashes and stores passwords with `bcrypt` +- **User Login** - verifies credentials and returns a signed JWT +- **Protected Routes** - accessible only with a valid token in the `Authorization` header +- **Logout** - client-side token invalidation concept +- **API Test Script** - demonstrates how to interact with the Flask app using `requests` + +--- + +## Requirements +- Python 3.8+ + +Install dependencies with: + +pip install flask bcrypt pyjwt requests + + +## Running the Application + +1. Start the Flask app: +python "Token-Based Authentication (JWT).py" + +This will start the server at: +http://127.0.0.1:5000 + + +2. Test the API using the provided script: +python test_api.py + + +## Files + +Token-Based Authentication (JWT).py - Flask app with JWT authentication system +test_api.py - Example script to register, login, and access protected routes + + +## Example API Flow + +1. Register a user: +POST /register → { "username": "alice", "password": "mypassword" } + +2. Login to receive JWT: +POST /login → returns { "token": "" } + +3. Access protected route with token: +GET /protected + header → Authorization: Bearer + +4. Logout: +POST /logout → instructs client to delete token + + +## Notes + +Tokens expire after 30 minutes. + +Passwords are never stored in plaintext - they are hashed with bcrypt. + +The demo uses an in-memory dictionary (users_db) as a mock database (resets when the server restarts). + + diff --git a/mental_health_hub (James Nardella)/flask_jwt_demo/test_api.py b/mental_health_hub (James Nardella)/flask_jwt_demo/test_api.py new file mode 100644 index 0000000..a21cdbd --- /dev/null +++ b/mental_health_hub (James Nardella)/flask_jwt_demo/test_api.py @@ -0,0 +1,24 @@ +import requests + +#register +r = requests.post("http://127.0.0.1:5000/register", json={ + "username": "alice", + "password": "mypassword" +}) +print(r.json()) + +#login +r = requests.post("http://127.0.0.1:5000/login", json={ + "username": "alice", + "password": "mypassword" +}) +data = r.json() +print(data) + +token = data.get("token") + +#access protected route +r = requests.get("http://127.0.0.1:5000/protected", headers={ + "Authorization": f"Bearer {token}" +}) +print(r.json()) diff --git a/mental_health_hub (James Nardella)/incident_response_plan/Incident Response Plan.pdf b/mental_health_hub (James Nardella)/incident_response_plan/Incident Response Plan.pdf new file mode 100644 index 0000000..a18330a Binary files /dev/null and b/mental_health_hub (James Nardella)/incident_response_plan/Incident Response Plan.pdf differ diff --git a/mental_health_hub (James Nardella)/logging_demo/Logging & Monitoring.pdf b/mental_health_hub (James Nardella)/logging_demo/Logging & Monitoring.pdf new file mode 100644 index 0000000..9d01ae1 Binary files /dev/null and b/mental_health_hub (James Nardella)/logging_demo/Logging & Monitoring.pdf differ diff --git a/mental_health_hub (James Nardella)/logging_demo/README.md b/mental_health_hub (James Nardella)/logging_demo/README.md new file mode 100644 index 0000000..355f0f7 --- /dev/null +++ b/mental_health_hub (James Nardella)/logging_demo/README.md @@ -0,0 +1,50 @@ +# Logging Demo + +## Overview +This project demonstrates a basic Python-based **logging and monitoring system**. +It simulates user activity on a website, capturing events such as: + +- Successful and failed logins +- File uploads (with validation for safe extensions) +- Chat messages (with spam detection and abuse flags) +- Administrative log viewing and CSV export + +This was designed as a prototype to illustrate how user actions can be tracked for **security, auditing, and incident response**. + +--- + +## Requirements +- Python 3.8+ +- No external libraries required (uses only built-in modules: `datetime`, `random`, `csv`) + +--- + +## Usage +Run the script from inside the `logging_demo` folder: + + +python activity_logger.py + +Valid usernames: user_102, user_877, user_324, user_3424, admin342 + +Any password is accepted. + +The admin342 account allows: Viewing all logs, Exporting logs to a .csv file + +--- + +## Files + +activity_logger.py - Python script implementing the logging system. + +Logging & Monitoring.pdf - Documentation explaining the system design, features, and demonstration. + +--- + +## Notes + +File uploads accept only safe extensions (.png, .jpg, .jpeg, .pdf, .txt). + +Sending 5+ chat messages in one session results in a spam flag and auto-logout. + +Logs can be exported to CSV with dynamically generated headers for audit purposes. \ No newline at end of file diff --git a/mental_health_hub (James Nardella)/logging_demo/python activity_logger.py b/mental_health_hub (James Nardella)/logging_demo/python activity_logger.py new file mode 100644 index 0000000..a6c0217 --- /dev/null +++ b/mental_health_hub (James Nardella)/logging_demo/python activity_logger.py @@ -0,0 +1,160 @@ +#import the required modules +import datetime #for timestamp functionality +import random #to generate random IP addresses +import csv #for CSV export functionality + +#generate a random IP address in the 192.168.1.x range +def generate_ip(): + return f"192.168.1.{random.randint(2, 254)}" + +#return current UTC timestamp in ISO 8601 format +def current_timestamp(): + return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + +#list of valid usernames +valid_users = ["user_102", "user_877", "user_324", "user_3424", "admin342"] + +#list to store user activity logs +activity_logs = [] + +#dictionary to track the number of chat messages per user (for spam detection) +chat_tracker = {} + +#list of allowed file extensions for uploads +safe_extensions = [".png", ".jpg", ".jpeg", ".pdf", ".txt"] + +#start the main application loop +while True: + #prompt the user to log in + print ("(valid usernames are: user_102, user_877, user_324, user_3424, admin342):") + username = input("Enter username: ") + password = input("Enter password (any password will work): ") + ip = generate_ip() #generate a simulated IP address for the session + + #handle invalid username attempts + if username not in valid_users: + print("Invalid username. Please try again.") + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "login_attempt", + "status": "failed", + "reason": "Invalid username", + "ip_address": ip + }) + continue #restart login loop + + #admin user functionality: view or export activity logs + if username == "admin342": + view_logs = input("Would you like to see the logs? (yes/no)").lower() + if view_logs == "yes": + #display all stored logs + print("\n--- Activity Logs ---") + for log in activity_logs: + print(log) + print("--- End of Logs ---\n") + + #offer option to export logs to CSV + export = input("Would you like to export logs to a CSV file? (yes/no): ").lower() + if export == "yes": + #collect all unique keys used across all log entries + all_keys = set() + for entry in activity_logs: + all_keys.update(entry.keys()) + all_keys = sorted(all_keys) #sort for consistent CSV headers + + #create a filename using current timestamp + csv_filename = f"activity_logs_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + #write logs to CSV + with open(csv_filename, mode="w", newline="") as file: + writer = csv.DictWriter(file, fieldnames=all_keys) + writer.writeheader() + for entry in activity_logs: + #ensure all keys exist in the row (fill missing with empty string) + complete_entry = {key: entry.get(key, "") for key in all_keys} + writer.writerow(complete_entry) + print(f"Logs exported to {csv_filename}") + continue #go back to login after admin actions + + #log successful user login + print(f"Welcome, {username}!") + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "login_attempt", + "status": "success", + "ip_address": ip + }) + + #begin user session actions + while True: + print("\nWhat would you like to do?") + print("(1) Upload a file or document") + print("(2) Send a chat message") + choice = input("Enter 1 or 2: ") + + if choice == "1": + #handle file upload + filename = input("What is the filename?: ") + extension = input(f"What is the extension? (include the dot, allowed: {', '.join(safe_extensions)}): ") + full_file = filename + extension + + #check file extension safety + if extension.lower() not in safe_extensions: + print(f"{full_file} denied due to unsafe file type.") + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "file_upload", + "filename": full_file, + "status": "denied", + "reason": "Unsafe file type", + "ip_address": ip + }) + else: + print(f"{full_file} accepted and uploaded.") + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "file_upload", + "filename": full_file, + "status": "accepted", + "ip_address": ip + }) + + elif choice == "2": + #handle chat messages with spam detection + if username not in chat_tracker: + chat_tracker[username] = 0 #initialize count + + print("You may send up to 5 messages. Sending too many will result in a spam flag.") + for i in range(5): + msg = input("Enter chat message: ") + chat_tracker[username] += 1 + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "chat_message", + "message": msg, + "status": "received", + "ip_address": ip + }) + + #if user exceeds 5 messages, flag them for spamming and log out + if chat_tracker[username] >= 5: + print("Too many messages sent. You are flagged for spamming and will be logged out.") + activity_logs.append({ + "timestamp": current_timestamp(), + "user_id": username, + "event": "chat_abuse", + "status": "spamming_detected", + "ip_address": ip + }) + break + break #end session after chat + + #ask user if they want to perform another action or log out + print("Type 'yes' to continue or 'no' to return to login") + back = input("Would you like to go back to options?: ").lower() + if back == "no": + break #return to login loop diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/README.md b/mental_health_hub (James Nardella)/streamlit_hub_app/README.md new file mode 100644 index 0000000..c64d19a --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/README.md @@ -0,0 +1,45 @@ +# Streamlit Hub Application + +## Overview +This module integrates all individual features into a single cohesive demo platform using Streamlit. +It acts as the central hub for authentication, logging, mental health dashboard, mood prediction, and collaborative storytelling. + + +## Files in this module + +- **streamlit_hub_app.py** → Main entry point that ties everything together into a navigable multi-page app. +- **auth_module.py** → Implements user registration, login, logout with JWT authentication. +- **logging_module.py** → Centralised event logging with severity levels. +- **dashboard_module.py** → Data generation and visualisations for mental health trends. +- **mood_module.py** → Simple ML-based mood prediction model using scikit-learn. +- **storytelling_module.py** → Collaborative writing and illustrating workflow. + + +## Running the Application + +1. Open a terminal in this folder. + +2. Install required dependencies: +pip install streamlit bcrypt PyJWT pandas numpy matplotlib scikit-learn + +3. Run the Streamlit app: + +streamlit run streamlit_hub_app.py + +4. The app will open automatically in your browser at http://localhost:8501. + + +## Features + +- Authentication: Register/login with hashed passwords and JWT tokens. +- Logging: Tracks actions (logins, uploads, mood predictions, story entries). +- Dashboard: Displays simulated mental health data trends with interactive charts. +- Mood Prediction: Predicts a user's mood category based on demo inputs. +- Collaborative Storytelling: Writers and illustrators co-create stories in a shared workflow. + +## Notes + +- This hub is designed for demonstration and learning purposes. +- Session state is used instead of a persistent database. +- The modular structure makes it easy to extend or replace individual components. +- The Mood Predictor and Dashboard modules are based on code and contributions from collaborator **Bhanu Pratap Singh Mehar**, integrated here into the hub application. diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/auth_module.py b/mental_health_hub (James Nardella)/streamlit_hub_app/auth_module.py new file mode 100644 index 0000000..7552efe --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/auth_module.py @@ -0,0 +1,108 @@ +import streamlit as st +import bcrypt # Import bcrypt and jwt functionality +import jwt +import datetime +from logging_module import log_event # Import log_event function + +# Secret key for signing JWTs +SECRET_KEY = "super-secret-key" + +# In-memory "database" for demo +if "users_db" not in st.session_state: + st.session_state.users_db = {} + +# Session state for authentication +if "token" not in st.session_state: + st.session_state.token = None +if "user" not in st.session_state: + st.session_state.user = None + + +# Hash password, bcrypt, token generation, verification functionality +def hash_password(password: str) -> bytes: + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) + +def verify_password(password: str, hashed: bytes) -> bool: + return bcrypt.checkpw(password.encode("utf-8"), hashed) + +def generate_token(username: str) -> str: + payload = { + "user": username, + "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30) + } + token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") + return token + +def verify_token(token: str): + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) + return payload + except jwt.ExpiredSignatureError: + return None + except jwt.InvalidTokenError: + return None + + +# Streamlit User-Interface +def run_auth(): + st.title("🔑 User Authentication (JWT)") + + choice = st.radio("Choose Action:", ["Login", "Register", "Logout"]) + + # Register functionality + if choice == "Register": + st.subheader("Create a new account") + username = st.text_input("Username (register)") + password = st.text_input("Password (register)", type="password") + + if st.button("Register"): + if username in st.session_state.users_db: + st.error("User already exists.") + log_event(f"Failed registration attempt (user exists): {username}", 'ERROR') # Log failed registration + else: + st.session_state.users_db[username] = hash_password(password) + st.success(f"User {username} registered successfully!") + log_event(f"User successfully registered: {username}", 'INFO') # Log the successful registration event + + # Login Functionality + elif choice == "Login": + st.subheader("Login") + username = st.text_input("Username (login)") + password = st.text_input("Password (login)", type="password") + + if st.button("Login"): + if username not in st.session_state.users_db: + st.error("Invalid credentials.") + log_event(f"Failed login attempt (user does not exist): {username}", 'ERROR') # Log failed login + else: + stored_pw = st.session_state.users_db[username] + if verify_password(password, stored_pw): + token = generate_token(username) + st.session_state.token = token + st.session_state.user = username + st.success(f"Welcome, {username}!") + log_event(f"User successfuly logged in: {username}", 'INFO') # Log successful login + else: + st.error("Invalid credentials.") + log_event(f"Failed login attempt (incorrect password): {username}", 'ERROR') # Log failed login + + # Logout functionality + elif choice == "Logout": + if st.session_state.token: + log_event(f"User logged out: {st.session_state.user}", 'INFO') # Log the logout event + st.session_state.token = None + st.session_state.user = None + st.success("You have successfully been logged out.") + else: + st.info("You are not logged in.") + + # Protected Area + if st.session_state.token: + payload = verify_token(st.session_state.token) + if payload: + st.info(f"🔒 Protected: Hello {payload['user']}!") + else: + st.error("Your session expired. Please login again.") + log_event(f"Session expired for: {st.session_state.user}", 'ERROR') # Log session expiry event + st.session_state.token = None + st.session_state.user = None diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/dashboard_module.py b/mental_health_hub (James Nardella)/streamlit_hub_app/dashboard_module.py new file mode 100644 index 0000000..eb88ed1 --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/dashboard_module.py @@ -0,0 +1,101 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import streamlit as st + +# 1. Generate mock mental health trends data +def generate_dashboard_data(): + np.random.seed(42) + years = np.arange(2015, 2026) + states = ['NSW', 'VIC', 'QLD', 'WA', 'SA', 'TAS'] + age_groups = ['18-29', '30-44', '45-59', '60+'] + activity_levels = ['Low', 'Medium', 'High'] + + # Create a cartesian product of all categories and years + records = [] + for year in years: + for state in states: + for age in age_groups: + for activity in activity_levels: + # Simulate average distress score (10-50 scale) + base = 30 - (0.5 * activity_levels.index(activity)) # more activity lowers distress + age_adj = 0.1 * age_groups.index(age) # older groups slightly higher distress + trend = (year - 2015) * 0.2 # slight upward trend + score = base + age_adj + trend + np.random.normal(0, 1) + records.append({ + 'Year': year, + 'State': state, + 'AgeGroup': age, + 'ActivityLevel': activity, + 'AvgDistressScore': round(score, 1) + }) + + df = pd.DataFrame(records) + return df + +# 2. Plotting function for displaying graphs +def plot_trends_by_state(df): + states = df['State'].unique() + plt.figure(figsize=(8, 5)) + for state in states: + subset = df[df['State'] == state].groupby('Year')['AvgDistressScore'].mean() + plt.plot(subset.index, subset.values, label=state) + plt.title('Avg Distress Score by State (2015–2025)') + plt.xlabel('Year') + plt.ylabel('Average Distress Score') + plt.legend() + plt.tight_layout() + st.pyplot(plt) + +def plot_trends_by_age_group(df): + age_groups = df['AgeGroup'].unique() + plt.figure(figsize=(8, 5)) + for age in age_groups: + subset = df[df['AgeGroup'] == age].groupby('Year')['AvgDistressScore'].mean() + plt.plot(subset.index, subset.values, label=age) + plt.title('Avg Distress Score by Age Group (2015–2025)') + plt.xlabel('Year') + plt.ylabel('Average Distress Score') + plt.legend() + plt.tight_layout() + st.pyplot(plt) + +def plot_activity_level_bar_chart(df): + latest = df[df['Year'] == df['Year'].max()] + activity_avg = latest.groupby('ActivityLevel')['AvgDistressScore'].mean() + plt.figure(figsize=(6, 4)) + activity_avg.plot(kind='bar') + plt.title(f'Avg Distress by Activity Level in {df["Year"].max()}') + plt.xlabel('Activity Level') + plt.ylabel('Average Distress Score') + plt.tight_layout() + st.pyplot(plt) + +# 3. Display the data and plots in Streamlit +def run_dashboard(): + st.title("Dashboard: Mental Health Trends") + + df = generate_dashboard_data() + + # Show sample data + st.write("### Sample of Generated Data") + st.write(df.head(10)) + + # Display the graphs + st.write("### Trends by State") + plot_trends_by_state(df) + + st.write("### Trends by Age Group") + plot_trends_by_age_group(df) + + st.write("### Distress by Activity Level") + plot_activity_level_bar_chart(df) + + # Sample pandas filtering & grouping logic + st.write("### NSW Average Distress by Year") + nsw_avg = df[df['State']=='NSW'].groupby('Year')['AvgDistressScore'].mean() + st.write(nsw_avg) + + st.write("### 18-29 Age Group with High Activity") + young_high = df[(df['AgeGroup']=='18-29') & (df['ActivityLevel']=='High')] + st.write(young_high.head()) diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/logging_module.py b/mental_health_hub (James Nardella)/streamlit_hub_app/logging_module.py new file mode 100644 index 0000000..025fefc --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/logging_module.py @@ -0,0 +1,49 @@ +import datetime +import json +import streamlit as st +import pandas as pd + +# Session Initialization +def init_logging(): + if 'logs' not in st.session_state: + st.session_state.logs = [] + +# Initialize session state +init_logging() + +# Logging Functionality +def log_event(action, severity='INFO', details=None): + """Log only significant events like login, registration, etc.""" + entry = { + 'timestamp': datetime.datetime.utcnow().isoformat() + 'Z', + 'action': action, + 'severity': severity, + 'details': details + } + st.session_state.logs.append(entry) + +# Run the logging page +def run_logging(): + st.title("Activity Logs") + + # Check if there are any logs in session + logs = st.session_state.logs + + # Display the logs in a table format using st.dataframe + if logs: + severity_filter = st.selectbox('Filter by severity', ['ALL', 'INFO', 'WARNING', 'ERROR']) + filtered_logs = logs[::-1] # latest first + + # Apply filter if selected + if severity_filter != 'ALL': + filtered_logs = [log for log in filtered_logs if log['severity'] == severity_filter] + + # Display logs in a table format + st.dataframe(pd.DataFrame(filtered_logs)) # Display logs as a dataframe + + # Add export functionality + if st.button("Export Logs as JSON"): + logs_json = json.dumps(filtered_logs, indent=2) + st.download_button(label="Download JSON", data=logs_json, file_name="logs.json", mime="application/json") + else: + st.info("No logs to display yet.") diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/mood_module.py b/mental_health_hub (James Nardella)/streamlit_hub_app/mood_module.py new file mode 100644 index 0000000..203ee9f --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/mood_module.py @@ -0,0 +1,131 @@ +import pandas as pd +import numpy as np +from sklearn.tree import DecisionTreeClassifier +from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score +from sklearn.metrics import classification_report, confusion_matrix +from sklearn.utils import resample +import matplotlib.pyplot as plt +import streamlit as st + +# 1. Generate mock ABS-style data with six inputs +def generate_mood_data(): + np.random.seed(42) + data = pd.DataFrame({ + 'Sleep': np.random.uniform(4, 9, size=300), # Hours per night + 'Activity': np.random.uniform(0, 7, size=300), # Exercise hours per week + 'Age': np.random.randint(18, 65, size=300), # Age in years + 'K10': np.random.randint(10, 50, size=300), # Distress score (10–50) + 'Health': np.random.randint(1, 6, size=300), # Self-rated health (1–5) + 'Contacts': np.random.randint(0, 10, size=300) # Weekly close contacts + }) + return data + +# 2. Define rule-based mood function +def predict_mood_rule(row): + if row['Sleep'] <= 5 and row['K10'] >= 35: + return 'Low' # Extreme distress + poor sleep + if (row['Sleep'] >= 7 or row['Activity'] >= 5) and row['Contacts'] >= 3: + return 'High' # Good sleep/exercise + social support + return None # Others defer to regression + +# 3. Compute continuous MoodScore via regression formula +def compute_mood_score(row): + S, A = row['Sleep'], row['Activity'] + D, K = row['Age']/10, row['K10']/10 + H, C = row['Health'], row['Contacts'] + return 0.25*S + 0.20*A - 0.15*D - 0.30*K + 0.10*H + 0.10*C + 2.0 + +# 4. Combine rule-based and regression thresholds +def predict_mood(row): + label = predict_mood_rule(row) + if label: + return label + score = compute_mood_score(row) + if score >= 7: + return 'High' + if score <= 4: + return 'Low' + return 'Medium' + +# 5. Balance 'Medium' class via oversampling +def balance_data(data): + major = data[data['Mood'] != 'Medium'] + med = data[data['Mood'] == 'Medium'] + med_up = resample(med, replace=True, n_samples=major['Mood'].value_counts().max(), random_state=42) + balanced = pd.concat([major, med_up]) + return balanced + +# 6. Train Decision Tree model +def train_model(data): + features = ['Sleep', 'Activity', 'Age', 'K10', 'Health', 'Contacts'] + X = data[features] + y = data['Mood'] + + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) + + param_grid = {'max_depth': [3, 4, 5], 'min_samples_leaf': [1, 5, 10]} + grid = GridSearchCV(DecisionTreeClassifier(random_state=42), param_grid, cv=5) + grid.fit(X_train, y_train) + + best_clf = grid.best_estimator_ + cv_scores = cross_val_score(best_clf, X_train, y_train, cv=5) + + return best_clf, cv_scores, X_test, y_test + +# 7. Plot Confusion Matrix +def plot_confusion_matrix(y_test, y_pred): + cm = confusion_matrix(y_test, y_pred, labels=['High', 'Medium', 'Low']) + plt.figure(figsize=(5, 5)) + plt.imshow(cm, cmap='Blues', interpolation='nearest') + plt.title('Confusion Matrix Heatmap') + plt.colorbar() + cls = ['High', 'Medium', 'Low'] + ticks = np.arange(len(cls)) + plt.xticks(ticks, cls, rotation=45) + plt.yticks(ticks, cls) + for i in range(len(cls)): + for j in range(len(cls)): + plt.text(j, i, cm[i, j], ha='center', va='center') + plt.xlabel('Predicted') + plt.ylabel('True') + plt.tight_layout() + st.pyplot(plt) + +# 8. Plot Feature Importances +def plot_feature_importances(best_clf, features): + importances = best_clf.feature_importances_ + plt.figure(figsize=(8, 5)) + plt.barh(features, importances, edgecolor='black') + plt.title('Mood Prediction – Feature Importances') + plt.xlabel('Importance Score') + plt.tight_layout() + st.pyplot(plt) + +# 9. Streamlit function to run the model +def run_mood(): + st.title("Mood Prediction Model") + + # Generate mock data and predict mood + data = generate_mood_data() + data['Mood'] = data.apply(predict_mood, axis=1) + balanced_data = balance_data(data) + + # Train the model + best_clf, cv_scores, X_test, y_test = train_model(balanced_data) + + # Display results + st.write(f"Mean Cross-Validation Accuracy: {cv_scores.mean():.2f}") + + # Make predictions on the test set + y_pred = best_clf.predict(X_test) + + # Display the classification report, confusion matrix, and feature importance + st.write("### Classification Report") + st.text(classification_report(y_test, y_pred)) + + st.write("### Confusion Matrix") + plot_confusion_matrix(y_test, y_pred) + + st.write("### Feature Importances") + plot_feature_importances(best_clf, ['Sleep', 'Activity', 'Age', 'K10', 'Health', 'Contacts']) + diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/storytelling_module.py b/mental_health_hub (James Nardella)/streamlit_hub_app/storytelling_module.py new file mode 100644 index 0000000..1311b99 --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/storytelling_module.py @@ -0,0 +1,138 @@ +#import the neccessary libraries +import streamlit as st + +def run_storytelling(): + st.title("📖 Collaborative Storytelling") + + #initialize a dictionary in session state to store all chapter info + #each chapter is stored with keys: title, writer, story, illustrator, and art + if "chapters" not in st.session_state: + st.session_state.chapters = {} # {chapter_number: {"title": str, "writer": str, "story": str, "illustrator": str, "art": file}} + #create four tabs: Write, Illustrate, Story, Contributors + tabs = st.tabs(["✍️ Write", "🎨 Illustrate", "📚 Story", "👥 Contributors"]) + + # WRITE TAB + with tabs[0]: + st.subheader("Write a Chapter") + #ask for the writers name + name = st.text_input("Your name (or alias)") + #ask which chapter number (1–10) they want to write + chapter_number = st.selectbox("Select a Chapter Number", [f"Chapter {i}" for i in range(1, 11)]) + #writer chooses a title for the chapter + chapter_title = st.text_input("Select a Chapter Title") + #writer inputs their short story directly (or via upload) + story = st.text_area("Your short story (max 500 words)", max_chars=3000) + #allow file upload as alternative story submission (.txt or .md) + file = st.file_uploader("Or upload a .txt/.md file", type=["txt", "md"]) + #handle the submit + if st.button("Submit Story ✍️"): + if not name.strip(): + st.error("Please enter your name or alias before submitting.") + elif not chapter_title.strip(): + st.error("Please enter a chapter title before submitting.") + elif not story.strip() and not file: + st.error("Please write a story or upload a file before submitting.") + else: + # Handle optional file text + if file: + story = file.read().decode("utf-8") + + st.session_state.chapters[chapter_number] = { + "title": chapter_title.strip(), + "writer": name.strip(), + "story": story.strip(), + "illustrator": None, + "art": None + } + st.success(f"✅ Chapter saved: [{chapter_number}] - {chapter_title} by {name}") + + # ILLUSTRATE TAB + with tabs[1]: + st.subheader("Illustrate a Chapter") + + #build dropdown options: show only chapters that already have a story + available_chapters = { + f"[{num}] - {info['title']}": num + for num, info in st.session_state.chapters.items() + if info.get("story") + } + + if available_chapters: + #select which chapter to illustrate + choice = st.selectbox("Choose a Chapter to Illustrate", list(available_chapters.keys())) + selected_number = available_chapters[choice] + chapter_info = st.session_state.chapters[selected_number] + + #display the story text so illustrators know the content + st.write(f"### {selected_number}: {chapter_info['title']}") + st.write(chapter_info["story"]) + + #mandatory illustrator name + illustrator_name = st.text_input("Your name (or alias) as illustrator") + #allow artwork upload (optional) + art = st.file_uploader("Upload Your Artwork", type=["png", "jpg", "jpeg"]) + + if st.button("Submit Artwork 🎨"): + if not illustrator_name.strip(): + st.error("Please enter your name or alias before submitting.") + else: + chapter_info["illustrator"] = illustrator_name.strip() + if art: #only save if the art is uploaded + chapter_info["art"] = art + st.success(f"✅ Artwork submitted for {choice} by {illustrator_name} (with artwork)") + else: + st.success(f"✅ Artwork submitted for {choice} by {illustrator_name} (no artwork uploaded)") + + + else: + st.info("No chapters available yet. Writers need to submit first.") + + + + # STORY TAB + with tabs[2]: + st.subheader("Our Shared Story") + #filter only completed chapters (story + illustrator required) + completed = [ + (num, info) for num, info in st.session_state.chapters.items() + if info.get("story") and info.get("illustrator") + ] + if completed: + for num, info in completed: + #show chapter number, title, and story + st.write(f"## {num}: {info['title']}") + st.write(info["story"]) + #show artwork if uploaded, otherwise only note illustrator’s name + if info.get("art"): + st.image(info["art"], caption=f"Illustration by {info['illustrator']}") + else: + st.info(f"(No artwork uploaded yet — illustrated by {info['illustrator']})") + st.markdown("---") + else: + st.info("No completed chapters yet. Stories and illustrators need to be submitted first.") + + + # CONTRIBUTORS TAB + with tabs[3]: + st.subheader("Contributors") + #separate contributors into writers and illustrators lists + writers = [] + illustrators = [] + for num, info in st.session_state.chapters.items(): + if info.get("writer"): + writers.append(f"✍️ {info['writer']} – {num}: {info['title']}") + if info.get("illustrator"): + illustrators.append(f"🎨 {info['illustrator']} – {num}: {info['title']}") + #display writers section if any + if writers: + st.write("### Writers") + for w in writers: + st.write(w) + #display illustrators section if any + if illustrators: + st.write("### Illustrators") + for i in illustrators: + st.write(i) + #show placeholder message if no contributors exist + if not writers and not illustrators: + st.info("No contributors yet.") diff --git a/mental_health_hub (James Nardella)/streamlit_hub_app/streamlit_hub_app.py b/mental_health_hub (James Nardella)/streamlit_hub_app/streamlit_hub_app.py new file mode 100644 index 0000000..9d6672c --- /dev/null +++ b/mental_health_hub (James Nardella)/streamlit_hub_app/streamlit_hub_app.py @@ -0,0 +1,29 @@ +# Import all necessary libraries and functions here +import streamlit as st +from auth_module import run_auth +from dashboard_module import run_dashboard +from mood_module import run_mood +from logging_module import run_logging, init_logging +from storytelling_module import run_storytelling # ⬅️ NEW + + +# Page Directory for Streamlit +PAGES = { + "Authentication": run_auth, # Add Authentication Page + "Dashboard": run_dashboard, # Add Dashboard page + "Mood Predictor": run_mood, # Add Mood Predictor Page + "Activity Logging": run_logging, # Add Logging page + "Storytelling": run_storytelling, # Add Storytelling Page +} + +# Initialises logging session when the app starts +init_logging() + +# Main Functionality +def main(): + st.sidebar.title("Mental Health Hub") + choice = st.sidebar.radio("Go to:", list(PAGES.keys())) + PAGES[choice]() # run the selected page + +if __name__ == "__main__": + main() diff --git a/mental_health_hub (James Nardella)/threat_model/Threat Model & Security Architecture.pdf b/mental_health_hub (James Nardella)/threat_model/Threat Model & Security Architecture.pdf new file mode 100644 index 0000000..49f4c2b Binary files /dev/null and b/mental_health_hub (James Nardella)/threat_model/Threat Model & Security Architecture.pdf differ