diff --git a/app.py b/app.py new file mode 100644 index 0000000..101f81e --- /dev/null +++ b/app.py @@ -0,0 +1,74 @@ +# app.py + +import streamlit as st +from utils.translator import get_translator +import os + +# ----------------------------- +# Configure app layout +# ----------------------------- +st.set_page_config( + page_title="Childhood Obesity Risk Dashboard", + layout="wide", + initial_sidebar_state="expanded" +) + +# ----------------------------- +# Translator setup (default English) +# ----------------------------- +if 'translator' not in st.session_state: + st.session_state['translator'] = get_translator('en') # default language + +# Language selector +lang = st.selectbox( + "๐ŸŒ Select Language", + ['English', 'Italiano', 'ฮ•ฮปฮปฮทฮฝฮนฮบฮฌ', 'ไธญๆ–‡'], + key="language_select" +) + +if lang == 'English': + st.session_state['translator'] = get_translator('en') +elif lang == 'Italiano': + st.session_state['translator'] = get_translator('it') +elif lang == 'ฮ•ฮปฮปฮทฮฝฮนฮบฮฌ': + st.session_state['translator'] = get_translator('el') +elif lang == 'ไธญๆ–‡': + st.session_state['translator'] = get_translator('zh-cn') + +_ = st.session_state['translator'].translate + +# ----------------------------- +# Homepage content +# ----------------------------- +st.title(_("๐Ÿ  Welcome to the Childhood Obesity Risk Dashboard")) + +st.markdown(_(""" +This dashboard uses publicly available Australian health data to predict childhood obesity risk +based on lifestyle and demographic factors. + +Navigate using the sidebar to explore key features: +- ๐Ÿ“Š Data Explorer +- โš–๏ธ Fairness & Ethics +- ๐Ÿ” Insights +- ๐ŸŽฏ Predictor +- ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ Caregiver Engine +- ๐Ÿ”’ Privacy +- ๐ŸŽฎ Healthy Habits Streaks (NEW!) +- ๐Ÿ“ Resource Hub + +**Note:** This is an educational prototype โ€” no personal data is collected or stored. +""")) + +st.info(_("Tip: Start with the Predictor or Healthy Habits Streaks to try out the core features.")) + +# ----------------------------- +# Sidebar navigation +# ----------------------------- +with st.sidebar: + st.header("Go to pages") + st.page_link("pages/5_Predictor.py", label="๐ŸŽฏ Predictor") + st.page_link("pages/7_Resource_Hub.py", label="๐Ÿ“ Resource Hub") + if os.path.exists("pages/08_Healthy_Habits_Streaks.py"): + st.page_link("pages/08_Healthy_Habits_Streaks.py", label="๐ŸŽฎ Healthy Habits Streaks") + else: + st.caption("โš ๏ธ Streaks page not found. Expected: pages/08_Healthy_Habits_Streaks.py") diff --git a/pages/00_Subscription.py b/pages/00_Subscription.py new file mode 100644 index 0000000..adc6ee5 --- /dev/null +++ b/pages/00_Subscription.py @@ -0,0 +1,82 @@ +import streamlit as st +import json, os + +st.set_page_config(page_title="Subscription Plans", page_icon="๐Ÿ’ณ") + +st.title("๐Ÿ’ณ Subscription Plans") +st.caption("Choose a plan that works best for you or your family.") + +# ----------------------------- +# 1. Subscription Plans +# ----------------------------- +plans = { + "free": { + "name": "Free Plan", + "price": "$0 / week", + "features": [ + "โœ… Track personal streaks (Activity, Sleep, Memory Aids)", + "โœ… Daily habit reminders", + "โŒ No social or family sharing" + ] + }, + "standard": { + "name": "Standard Plan", + "price": "$20.99 / week", + "features": [ + "โœ… Track streaks (Activity, Sleep, Memory Aids)", + "โœ… Social Sharing with community", + "โœ… Connect with other dashboard users", + "โŒ No family sharing features" + ] + }, + "premium": { + "name": "Premium Plan", + "price": "$79.99 / week", + "features": [ + "โœ… Everything in Free + Standard", + "โœ… Family Sharing Portal", + "โœ… Family Support & Messaging", + "โœ… Social Connections", + "โœ… Weekly Reports & Alerts", + "โœ… Caregiver monitoring" + ] + } +} + +# ----------------------------- +# 2. Display Plan Options +# ----------------------------- +cols = st.columns(3) + +for i, key in enumerate(plans.keys()): + plan = plans[key] + with cols[i]: + st.subheader(plan["name"]) + st.metric("Price", plan["price"]) + for f in plan["features"]: + st.write(f) + if st.button(f"Subscribe to {plan['name']}", key=key): + st.session_state["active_plan"] = key + st.success(f"โœ… You have subscribed to {plan['name']}") + +# ----------------------------- +# 3. Persist Subscription Choice +# ----------------------------- +FILE = "subscription.json" + +if "active_plan" not in st.session_state: + # Load from file if exists + if os.path.exists(FILE): + with open(FILE, "r") as f: + data = json.load(f) + st.session_state["active_plan"] = data.get("plan", "free") + else: + st.session_state["active_plan"] = "free" + +# Save selection when changed +if "active_plan" in st.session_state: + with open(FILE, "w") as f: + json.dump({"plan": st.session_state["active_plan"]}, f) + +st.info(f"Your current plan: **{plans[st.session_state['active_plan']]['name']}**") + diff --git a/pages/08_Healthy_Habits_Streaks.py b/pages/08_Healthy_Habits_Streaks.py new file mode 100644 index 0000000..ca8e6fe --- /dev/null +++ b/pages/08_Healthy_Habits_Streaks.py @@ -0,0 +1,120 @@ +import streamlit as st +import datetime as dt +import json, os + +st.title("๐ŸŽฎ Healthy Habits Streaks") + +# -------------------------- +# Step 1: Setup JSON persistence +# -------------------------- +FILE = "streaks.json" + +# Load streaks from file if exists +if os.path.exists(FILE): + with open(FILE, "r") as f: + streaks = json.load(f) +else: + streaks = { + "screen_time_ok": 0, + "activity_done": 0, + "sleep_ok": 0, + "memory_ok": 0, + "last_date": None, + "logged_today": [], + } + +# Put into session_state +if "streaks" not in st.session_state: + st.session_state.streaks = streaks + +today = dt.date.today() +last_date = ( + dt.date.fromisoformat(st.session_state.streaks["last_date"]) + if st.session_state.streaks["last_date"] + else None +) + +# -------------------------- +# Step 2: Reset if day skipped +# -------------------------- +if last_date and (today - last_date).days > 1: + for k in ("screen_time_ok", "activity_done", "sleep_ok", "memory_ok"): + st.session_state.streaks[k] = 0 + st.session_state.streaks["logged_today"] = [] + +# -------------------------- +# Step 3: Habit logging +# -------------------------- +col1, col2, col3 = st.columns(3) + +# Screen time +with col1: + st.subheader("๐Ÿ“ฑ Screen time") + ok = st.toggle("โ‰ค 2 hrs today?", key="screen_time") + if ok and "screen_time_ok" not in st.session_state.streaks["logged_today"]: + st.session_state.streaks["screen_time_ok"] += 1 + st.session_state.streaks["logged_today"].append("screen_time_ok") + st.session_state.streaks["last_date"] = str(today) + with open(FILE, "w") as f: + json.dump(st.session_state.streaks, f) + +# Activity +with col2: + st.subheader("๐Ÿƒ Activity") + done = st.toggle("โ‰ฅ 60 mins activity?", key="activity") + if done and "activity_done" not in st.session_state.streaks["logged_today"]: + st.session_state.streaks["activity_done"] += 1 + st.session_state.streaks["logged_today"].append("activity_done") + st.session_state.streaks["last_date"] = str(today) + with open(FILE, "w") as f: + json.dump(st.session_state.streaks, f) + +# Sleep +with col3: + st.subheader("๐Ÿ›Œ Sleep") + good = st.toggle("7โ€“9 hrs sleep?", key="sleep") + if good and "sleep_ok" not in st.session_state.streaks["logged_today"]: + st.session_state.streaks["sleep_ok"] += 1 + st.session_state.streaks["logged_today"].append("sleep_ok") + st.session_state.streaks["last_date"] = str(today) + with open(FILE, "w") as f: + json.dump(st.session_state.streaks, f) + +st.divider() + +# -------------------------- +# Step 4: Metrics +# -------------------------- +c1, c2, c3, c4 = st.columns(4) +with c1: + st.metric("Screen-time streak", st.session_state.streaks["screen_time_ok"]) +with c2: + st.metric("Activity streak", st.session_state.streaks["activity_done"]) +with c3: + st.metric("Sleep streak", st.session_state.streaks["sleep_ok"]) +with c4: + st.metric("Memory streak", st.session_state.streaks["memory_ok"]) + +st.info("Tip: streaks reset if you miss a day. Each habit only counts once per day.") + +# -------------------------- +# Step 5: Memory Aids +# -------------------------- +st.header("๐Ÿ“ Memory Aids") + +reminders = [ + "Donโ€™t forget your morning walk ๐Ÿƒ", + "Drink a glass of water ๐Ÿ’ง now", + "Stretch for 5 minutes ๐Ÿ™†", +] + +today_idx = today.day % len(reminders) +st.info(reminders[today_idx]) + +if st.button("โœ… I did it! (Memory Aid)") and "memory_ok" not in st.session_state.streaks["logged_today"]: + st.session_state.streaks["memory_ok"] += 1 + st.session_state.streaks["logged_today"].append("memory_ok") + st.session_state.streaks["last_date"] = str(today) + with open(FILE, "w") as f: + json.dump(st.session_state.streaks, f) + st.success(f"Great job! Your memory streak is now {st.session_state.streaks['memory_ok']} ๐Ÿ”ฅ") diff --git a/pages/09_Cognitive_Lifestyle_Support.py b/pages/09_Cognitive_Lifestyle_Support.py new file mode 100644 index 0000000..d57081f --- /dev/null +++ b/pages/09_Cognitive_Lifestyle_Support.py @@ -0,0 +1,100 @@ +import streamlit as st +import datetime +import time + +st.set_page_config(page_title="Cognitive & Lifestyle Support", page_icon="๐Ÿง ") + +st.title("๐Ÿง  Cognitive & Lifestyle Support") +st.markdown("Tools to support memory, lifestyle habits, and relaxation โ€” designed for both children & elders.") + +# Initialize streaks in session_state +if "memory_streak" not in st.session_state: + st.session_state.memory_streak = 0 +if "sleep_streak" not in st.session_state: + st.session_state.sleep_streak = 0 + +today = datetime.date.today() + +# ------------------------- +# Section 1: Memory Aids +# ------------------------- +st.header("๐Ÿ“ Memory Aids") + +reminders = [ + "Donโ€™t forget your morning walk ๐Ÿƒ", + "Drink a glass of water ๐Ÿ’ง now", + "Stretch for 5 minutes ๐Ÿ™†", + "Call a friend or family member โ˜Ž๏ธ", + "Do a quick brain puzzle ๐Ÿงฉ" +] + +today_idx = today.day % len(reminders) +st.info(reminders[today_idx]) + +if st.button("โœ… I did it! (Memory Aid)"): + st.session_state.memory_streak += 1 + st.success(f"Great job! Your memory streak is now {st.session_state.memory_streak} ๐Ÿ”ฅ") + +st.metric("Memory Streak", st.session_state.memory_streak) + +st.divider() + +# ------------------------- +# Section 2: Educational Tips +# ------------------------- +st.header("๐Ÿ“˜ Daily Health Tip") + +tips = [ + "Stretch while watching TV ๐Ÿ“บ", + "Choose fruit instead of sugary snacks ๐ŸŽ", + "Go for a 10-min walk after meals ๐Ÿšถ", + "Keep a water bottle nearby ๐Ÿ’ง", + "Practice gratitude before bed ๐Ÿ™", + "Limit caffeine in the evening โ˜•" +] + +today_tip = tips[today.day % len(tips)] +st.success(today_tip) + +st.divider() + +# ------------------------- +# Section 3: Relaxation Guide +# ------------------------- +st.header("๐ŸŒ™ Relaxation & Sleep Tracking") + +st.markdown("Follow a simple breathing exercise to relax:") + +if st.button("Start Breathing Exercise"): + for i in range(3): + st.write("๐ŸŒฌ๏ธ Breathe in...") + time.sleep(3) + st.write("๐Ÿ˜Œ Hold...") + time.sleep(2) + st.write("๐ŸŽˆ Breathe out...") + time.sleep(4) + st.success("Well done! Feeling calmer already ๐Ÿ˜Œ") + +# Sleep logging +sleep_hours = st.slider("How many hours did you sleep last night?", 0, 12, 7) +st.write(f"You logged **{sleep_hours} hours** of sleep ๐Ÿ˜ด") + +if sleep_hours >= 7: + if st.button("โœ… Mark Sleep as Healthy"): + st.session_state.sleep_streak += 1 + st.success(f"Awesome! Your sleep streak is {st.session_state.sleep_streak} ๐ŸŒŸ") +else: + st.warning("Try to get at least 7โ€“8 hours for better health ๐Ÿ’ก") + +st.metric("Sleep Streak", st.session_state.sleep_streak) + +st.divider() + +# ------------------------- +# Section 4: Quick Tips Panel +# ------------------------- +st.header("๐Ÿ’ก Quick Lifestyle Reminders") +st.write("- Stay hydrated ๐Ÿ’ง") +st.write("- Move every hour ๐Ÿ•’") +st.write("- Keep a consistent bedtime ๐Ÿ›Œ") +st.write("- Connect socially with friends/family ๐Ÿค") diff --git a/pages/10_Streak_Summary.py b/pages/10_Streak_Summary.py new file mode 100644 index 0000000..101a02c --- /dev/null +++ b/pages/10_Streak_Summary.py @@ -0,0 +1,9 @@ +import streamlit as st + +st.title("๐Ÿ”ฅ Overall Streak Summary") + +st.write("Hereโ€™s how youโ€™re doing across all lifestyle habits:") + +st.metric("Memory Aids Streak", st.session_state.get("memory_streak", 0)) +st.metric("Sleep Streak", st.session_state.get("sleep_streak", 0)) +st.metric("Healthy Habits Streak (from page 08)", st.session_state.get("healthy_streak", 0)) diff --git a/pages/11_Social_Connection.py b/pages/11_Social_Connection.py new file mode 100644 index 0000000..81b08a7 --- /dev/null +++ b/pages/11_Social_Connection.py @@ -0,0 +1,77 @@ +import streamlit as st +import json, os, datetime as dt + +st.set_page_config(page_title="Social Connection", page_icon="๐Ÿค") + +st.title("๐Ÿค Social Connection") +st.caption("Connect with other dashboard users, share updates, and support each other.") + +# ----------------------------- +# 1. Load subscription +# ----------------------------- +if os.path.exists("subscription.json"): + with open("subscription.json", "r") as f: + active_plan = json.load(f).get("plan", "free") +else: + active_plan = "free" + +if active_plan == "free": + st.warning("๐Ÿ”’ Upgrade to Standard or Premium to unlock Social Connections.") + st.stop() + +# ----------------------------- +# 2. Community Feed (JSON file) +# ----------------------------- +FEED_FILE = "community_feed.json" + +if os.path.exists(FEED_FILE): + with open(FEED_FILE, "r") as f: + feed = json.load(f) +else: + feed = [] + +# ----------------------------- +# 3. Post an Update +# ----------------------------- +st.subheader("โœ๏ธ Share Your Update") + +username = st.text_input("Your Name (for community display):", "") +new_post = st.text_area("Write your update (e.g., health tip, progress, encouragement):") + +if st.button("๐Ÿ“ข Post to Community"): + if username.strip() and new_post.strip(): + post = { + "user": username.strip(), + "text": new_post.strip(), + "date": str(dt.date.today()), + "likes": 0 + } + feed.append(post) + + with open(FEED_FILE, "w") as f: + json.dump(feed, f, indent=2) + + st.success("โœ… Your post has been added to the community feed!") + else: + st.warning("Please enter your name and a message.") + +st.divider() + +# ----------------------------- +# 4. Community Feed Display +# ----------------------------- +st.subheader("๐ŸŒ Community Feed") + +if not feed: + st.info("No community posts yet. Be the first to share!") +else: + for i, post in enumerate(reversed(feed[-10:])): # show last 10 + st.write(f"**{post['user']}** ({post['date']}): {post['text']}") + cols = st.columns([1, 4]) + with cols[0]: + if st.button(f"๐Ÿ‘ {post['likes']}", key=f"like_{i}"): + feed[-(i+1)]["likes"] += 1 + with open(FEED_FILE, "w") as f: + json.dump(feed, f, indent=2) + st.experimental_rerun() + st.markdown("---") diff --git a/pages/12_Family_Support.py b/pages/12_Family_Support.py new file mode 100644 index 0000000..3fe507b --- /dev/null +++ b/pages/12_Family_Support.py @@ -0,0 +1,212 @@ +import streamlit as st +import json, os, datetime as dt, random + +# ----------------------------- +# Page config +# ----------------------------- +st.set_page_config(page_title="Family Support", page_icon="๐ŸŒ") +st.title("๐ŸŒ Family Support Portal") +st.caption("View loved oneโ€™s progress and send encouragement messages.") + +# ----------------------------- +# PIN setup (safe fallback) +# ----------------------------- +DEFAULT_PIN = "1234" # fallback PIN + +try: + PIN = st.secrets["portal_pin"] # read from secrets.toml +except Exception: + PIN = DEFAULT_PIN # fallback if not found + +with st.expander("๐Ÿ”’ Family Access"): + entered = st.text_input("Enter family PIN", type="password") + st.caption("Tip: set a secure PIN in `.streamlit/secrets.toml` like:\n\nportal_pin = \"your-pin\"") + +if entered != PIN: + st.info("Enter the PIN to view family support features.") + st.stop() + +# ----------------------------- +# Helper loaders +# ----------------------------- +def load_json(path, default): + if os.path.exists(path): + try: + with open(path, "r") as f: + return json.load(f) + except Exception: + return default + return default + +# File paths +STREAKS_FILE = "streaks.json" +WEEKLY_FILE = "weekly_report.json" +DIARY_FILE = "diary.json" +NOTES_FILE = "family_messages.json" # elder โ†’ family +ENCOURAGE_FILE = "family_encouragement.json" # family โ†’ elder + +# Load data +streaks = load_json(STREAKS_FILE, { + "screen_time_ok": 0, "activity_done": 0, + "sleep_ok": 0, "memory_ok": 0, "last_date": None +}) +weekly = load_json(WEEKLY_FILE, {"date": None, "report": "No weekly report saved yet."}) +diary = load_json(DIARY_FILE, []) +notes = load_json(NOTES_FILE, []) +encs = load_json(ENCOURAGE_FILE, []) + +today = dt.date.today() + +# ----------------------------- +# Subscription gate +# ----------------------------- +if os.path.exists("subscription.json"): + with open("subscription.json", "r") as f: + active_plan = json.load(f).get("plan", "free") +else: + active_plan = "free" + +if active_plan == "free": + st.warning("๐Ÿ”’ Upgrade to Standard or Premium to unlock Family Support features.") + st.stop() +elif active_plan == "standard": + st.success("โœ… You are on Standard Plan (basic social features).") + st.stop() +elif active_plan == "premium": + st.success("๐ŸŒŸ You are on Premium Plan (all family support features unlocked).") + +st.divider() + +# ----------------------------- +# Alerts / Safety signals +# ----------------------------- +days_since_log = None +if streaks.get("last_date"): + try: + last_dt = dt.date.fromisoformat(streaks["last_date"]) + days_since_log = (today - last_dt).days + except Exception: + days_since_log = None + +alert_msgs = [] +if days_since_log is None: + alert_msgs.append("No last activity date available yet.") +else: + if days_since_log >= 3: + alert_msgs.append(f"โš ๏ธ No new habit logs for **{days_since_log} days**.") + elif days_since_log == 2: + alert_msgs.append("โš ๏ธ No new habit logs for **2 days** (please check in).") + +if streaks.get("sleep_ok", 0) == 0: + alert_msgs.append("โ„น๏ธ Sleep streak is at 0 days.") + +st.subheader("๐Ÿ‘€ Current Status") +if alert_msgs: + for a in alert_msgs: + st.warning(a) +else: + st.success("โœ… All goodโ€”recent activity detected.") + +st.divider() + +# ----------------------------- +# Streak summary +# ----------------------------- +st.subheader("๐Ÿ“Š Streak Summary") +c1, c2, c3, c4 = st.columns(4) +c1.metric("Activity streak", streaks.get("activity_done", 0)) +c2.metric("Sleep streak", streaks.get("sleep_ok", 0)) +c3.metric("Memory aids", streaks.get("memory_ok", 0)) +c4.metric("Screen-time OK", streaks.get("screen_time_ok", 0)) + +last_str = streaks.get("last_date") or "โ€”" +st.caption(f"Last habit log date: **{last_str}**") + +summary_text = ( + f"Health update ({today.isoformat()}):\n" + f"- Activity streak: {streaks.get('activity_done',0)} days\n" + f"- Sleep streak: {streaks.get('sleep_ok',0)} days\n" + f"- Memory aids streak: {streaks.get('memory_ok',0)} days\n" + f"- Screen-time OK streak: {streaks.get('screen_time_ok',0)} days\n" + f"Last habit log date: {last_str}" +) +st.download_button("โฌ‡๏ธ Download summary (txt)", summary_text, file_name="family_summary.txt", mime="text/plain") + +st.divider() + +# ----------------------------- +# Weekly report +# ----------------------------- +st.subheader("๐Ÿ—“๏ธ Weekly Report") +st.write(weekly.get("report", "No weekly report saved yet.")) +st.caption(f"Report date: {weekly.get('date') or 'โ€”'}") + +wr_text = f"Date: {weekly.get('date')}\n\n{weekly.get('report','')}" +st.download_button("โฌ‡๏ธ Download weekly report (txt)", wr_text, file_name="weekly_report.txt", mime="text/plain") + +st.divider() + +# ----------------------------- +# Diary (last 5 entries) +# ----------------------------- +st.subheader("๐Ÿ“– Recent Diary Notes") +if diary: + for entry in reversed(diary[-5:]): + st.write(f"**{entry.get('date','โ€”')}** โ€” {entry.get('text','')}") +else: + st.info("No diary notes yet.") + +st.divider() + +# ----------------------------- +# Elder โ†’ family notes +# ----------------------------- +st.subheader("๐Ÿ’Œ Messages from Elder") +if notes: + for m in reversed(notes[-5:]): + st.write(f"**{m.get('date','โ€”')}** โ€” {m.get('text','')}") +else: + st.info("No saved messages yet.") + +st.divider() + +# ----------------------------- +# Family โ†’ elder encouragement (display) +# ----------------------------- +st.subheader("๐ŸŒŸ Family Encouragement") +if encs: + for e in reversed(encs[-5:]): # show last 5 + if isinstance(e, dict): + st.success(f"๐Ÿ’Œ {e.get('from','Family')} ({e.get('date','โ€”')}): {e.get('text','')}") + else: + st.success(f"๐Ÿ’Œ {e}") # legacy string-only format +else: + st.info("No encouragement messages yet.") + +st.divider() + +# ----------------------------- +# Family โ†’ elder encouragement (write new) +# ----------------------------- +st.subheader("โœ๏ธ Write a Message to Your Loved One") +sender = st.text_input("Your Name (Family Member):", "") +new_msg = st.text_area("Type your encouragement:") + +if st.button("๐Ÿ“จ Send Message"): + if sender.strip() and new_msg.strip(): + msg = {"from": sender.strip(), "text": new_msg.strip(), "date": str(dt.date.today())} + + if os.path.exists(ENCOURAGE_FILE): + with open(ENCOURAGE_FILE, "r") as f: + encs = json.load(f) + else: + encs = [] + + encs.append(msg) + + with open(ENCOURAGE_FILE, "w") as f: + json.dump(encs, f, indent=2) + + st.success("โœ… Your message has been saved and will appear in elderโ€™s dashboard.") + else: + st.warning("Please enter your name and a message.") diff --git a/pages/1_Home.py b/pages/1_Home.py new file mode 100644 index 0000000..be94677 --- /dev/null +++ b/pages/1_Home.py @@ -0,0 +1,106 @@ +# pages/1_Home.py +import speech_recognition +import translate +import plotly +import pywaffle +import joblib +import streamlit as st +import base64 + +# Translation helper +def _(text): + return st.session_state["translator"].translate(text) + +st.set_page_config(page_title="Home", page_icon="๐Ÿ ") + +# Optional: Function to play local audio guide +def play_audio(file_path): + try: + with open(file_path, "rb") as audio_file: + audio_bytes = audio_file.read() + audio_base64 = base64.b64encode(audio_bytes).decode() + audio_html = f""" + + """ + st.markdown(audio_html, unsafe_allow_html=True) + except FileNotFoundError: + st.warning(_("Audio guide not found. Please add 'help_guide.mp3' to the assets folder.")) + +def show_home(): + st.title(_("๐Ÿ  CHILDHOOD OBESITY RISK DASHBOARD")) + st.caption(_("SUPPORTING HEALTHIER ROUTINES FOR CHILDREN AND CAREGIVERS")) + + st.markdown("---") + + st.subheader(_("WHO IS THIS FOR?")) + st.markdown(_(""" + Designed for **caregivers, health workers, and families** to: + + - Spot early obesity risks + - Support healthier sleep and meals + - Start meaningful health conversations + """)) + + st.markdown("---") + + with st.expander(_("WHY OBESITY RISK MATTERS"), expanded=False): + st.markdown(_(""" + Childhood obesity is more than just weight. It affects: + + - Mood and energy + - Sleep quality + - Long-term health outcomes + + > Prevention starts with awareness โ€” and you, the caregiver, make that possible. + """)) + + with st.expander(_("HOW TO USE THIS DASHBOARD"), expanded=False): + st.markdown(_(""" + 1. Upload or explore a dataset + 2. Run the prediction tool + 3. View insights from lifestyle patterns + 4. Review fairness and ethical safeguards + """)) + + with st.expander(_("ELDERLY + WEARABLES IN PREVENTION"), expanded=False): + st.markdown(_(""" + Senior caregivers using smartwatches or bands can help by: + + - Tracking children's activity + - Monitoring sleep during overnight care + - Reducing prolonged screen exposure + + Wearable monitoring bridges child health and elderly caregiving โ€” a shared path to well-being. + """)) + + with st.expander(_("NEED HELP? EMERGENCY GUIDE"), expanded=False): + st.markdown(_("### Quick Walkthrough")) + + st.markdown(_(""" + - **Home Tab:** Learn who this dashboard is for and its purpose. + - **Data Explorer:** Upload a CSV and explore lifestyle data. + - **Predictor:** Run obesity risk predictions. + - **Caregiver Engine:** View practical tips tailored to each child. + - **Privacy Tab:** Export data locally, delete session memory. + - **Fairness Tab:** Understand how ethical design protects users. + """)) + + st.markdown(_("Tip: Ask someone to assist if you need help uploading files or navigating tabs.")) + + if st.checkbox(_("Play Audio Help Guide")): + play_audio("assets/help_guide.mp3") # Ensure this file exists + + st.markdown("---") + + st.subheader(_("DISCLAIMER")) + st.info(_(""" + This is an educational prototype. + It does not collect personal data or offer medical advice. + Always consult healthcare professionals for real-world health support. + """)) + +# Display the Home page +show_home() diff --git a/pages/2_Data_Explorer.py b/pages/2_Data_Explorer.py new file mode 100644 index 0000000..4f5cfe4 --- /dev/null +++ b/pages/2_Data_Explorer.py @@ -0,0 +1,80 @@ +import streamlit as st +import pandas as pd +import plotly.express as px +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +# Page configuration +st.set_page_config(page_title="Data Explorer", page_icon="๐Ÿ“‚", layout="wide") + +# Styling for elderly users (large fonts and bold labels) +st.markdown(""" + +""", unsafe_allow_html=True) + +def show_data_explorer(): + st.markdown("## ๐Ÿ—‚๏ธ Data Explorer") + st.markdown("Upload a **CSV file** with lifestyle or health-related data (e.g., LSAC/ABS datasets).") + + uploaded_file = st.file_uploader("๐Ÿ“ Drag and drop or browse your file here", type="csv") + + if uploaded_file: + try: + df = pd.read_csv(uploaded_file) + except Exception as e: + st.error(f"โŒ Failed to read CSV file: {e}") + return + + st.success("โœ… Preview of Your Dataset") + st.dataframe(df.head(), use_container_width=True) + + column = st.selectbox("๐Ÿ” Select a column to explore", df.columns) + + if df[column].dtype == 'object' or df[column].nunique() <= 20: + st.markdown("### ๐Ÿ“Š Value Counts Bar Chart") + value_counts = df[column].value_counts().reset_index() + value_counts.columns = [column, 'Count'] + + fig = px.bar( + value_counts, + x='Count', + y=column, + orientation='h', + color='Count', + color_continuous_scale='Blues', + labels={column: column.replace('_', ' ').title(), 'Count': 'Number of Entries'}, + title=f"Distribution of {column.replace('_', ' ').title()}" + ) + fig.update_layout( + height=500, + xaxis_title="Number of Entries", + yaxis_title=column.replace('_', ' ').title(), + font=dict(size=18), + ) + st.plotly_chart(fig, use_container_width=True) + + st.info("๐Ÿง  **Tip:** Interactive bar charts help older adults understand and explore data more easily.") + else: + st.warning("โš ๏ธ Please select a column with categorical or limited unique values (not continuous).") + +# Run the explorer +show_data_explorer() + +st.markdown(""" +๐Ÿงพ **What does "Number of Entries" mean?** + +This tells you how many times each category appears in the data. +For example, if โ€œHigh Screen Timeโ€ shows 12 entries, it means 12 children had high screen time. +""") + diff --git a/pages/3_Fairness.py b/pages/3_Fairness.py new file mode 100644 index 0000000..a3af9ae --- /dev/null +++ b/pages/3_Fairness.py @@ -0,0 +1,51 @@ +# pages/5_Fairness.py + +import streamlit as st +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +st.set_page_config(page_title="Fairness", page_icon="โš–๏ธ") + +def show_fairness(): + st.header("โš–๏ธ FAIRNESS & ETHICS") + st.markdown("---") + + st.markdown("## ๐Ÿ›ก๏ธ 1. PRIVACY, SECURITY & DATA ETHICS") + st.markdown(""" + - **Privacy by Design**: This app does not collect any real-time personal data. Data minimisation is built-in. + - **Clear Consent & Control**: Only essential information is requested, and all data remains local on your device. + - **Minimal Risk Operation**: Undo and reset options are visible where available. The UI avoids hidden actions or confusing flows. + """) + + st.markdown("## ๐Ÿ“ฃ 2. VOICE & PARTICIPATION: RESPECT THROUGH CO-DESIGN") + st.markdown(""" + - **Participatory Design**: Older adults and caregivers contributed feedback to improve usability. + - **Respectful Language**: Clear, non-patronising labels and microcopy support dignity and understanding. + - **Polite, Efficient Interactions**: Layouts are decluttered, with simple instructions and minimal steps to reduce cognitive load. + """) + + st.markdown("## โ™ฟ 3. INCLUSIVE DESIGN THAT REDUCES EXCLUSION") + st.markdown(""" + - **Universal Design**: The dashboard uses high contrast, large fonts, and clear buttons to suit users with varying needs. + - **Accessibility Compliance**: The layout follows WCAG guidelinesโ€”scalable text, tap-friendly elements, and keyboard navigation. + - **No Special Modes**: The experience is consistent for all users without separate โ€œseniorโ€ settings. + """) + + st.markdown("## ๐Ÿ’ฌ 4. FAIRNESS & ETHICAL TRANSPARENCY") + st.markdown(""" + - **Equitable Feature Access**: Any feature (like font size or color adjustments) works equally well across views. + - **Clear System Feedback**: Visual updates, button highlights, and messages confirm user actions. + - **Bias Prevention**: Models are evaluated for fairness (e.g., demographic parity), and PCA reduces hidden bias. + - **No Assumptions**: The design avoids stereotypes about tech skills, offering autonomy without oversimplification. + """) + + st.markdown("## ๐ŸŽ“ NOTE") + st.markdown(""" + All outputs are **simulated for educational purposes only**. This tool is not intended for medical decision-making. + """) + +show_fairness() diff --git a/pages/4_Insights.py b/pages/4_Insights.py new file mode 100644 index 0000000..ad1dcad --- /dev/null +++ b/pages/4_Insights.py @@ -0,0 +1,83 @@ +# pages/4_Insights.py + +import streamlit as st +import pandas as pd +import matplotlib.pyplot as plt +from pywaffle import Waffle +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +st.set_page_config(page_title="Insights", page_icon="๐Ÿ“ˆ") + +def show_insights(): + st.header("๐Ÿ“ˆ Key Insights & Proportions") + + uploaded_file = st.file_uploader("Upload processed dataset (CSV)", type="csv") + + if uploaded_file: + df = pd.read_csv(uploaded_file) + st.write("โœ… **Dataset Preview:**", df.head()) + + # =============================== + # ๐Ÿ”ท 1. Icon Array โ€“ Waffle Chart + # =============================== + if 'obesity_risk' not in df.columns: + st.error("โŒ 'obesity_risk' column not found in the dataset.") + return + + risk_counts = df['obesity_risk'].value_counts() + categories = {str(k): v for k, v in risk_counts.items()} + + st.markdown("### ๐Ÿงฑ Icon Array: Childhood Obesity Risk Distribution") + + fig1 = plt.figure( + FigureClass=Waffle, + rows=5, + values=categories, + colors=["#4CAF50", "#FFC107", "#F44336"], # Green, Yellow, Red + icons='child', + icon_size=25, + icon_legend=True, + legend={'loc': 'upper left', 'bbox_to_anchor': (1.05, 1)} + ) + st.pyplot(fig1) + + st.caption(""" + ๐Ÿง  **Why this chart?** + Icon arrays (waffle charts) use human-friendly icons to show proportions. + Each ๐Ÿ‘ง represents a child, making it easy to see how many are at risk. + """) + + # =============================== + # ๐Ÿ”ท 2. Line Chart โ€“ Time Trends + # =============================== + st.markdown("### ๐Ÿ“‰ Time Trend: Screen Time / Activity Over Weeks") + + time_candidates = [col for col in df.columns if 'week' in col.lower() or 'date' in col.lower()] + if not time_candidates: + st.warning("โš ๏ธ No date or week column found. Please upload data with a time column.") + else: + time_column = st.selectbox("๐Ÿ—“๏ธ Select Time Column", time_candidates) + y_column = st.selectbox("๐Ÿ“ˆ Select Variable to Trend", ['screen_time', 'physical_activity']) + + if time_column in df.columns and y_column in df.columns: + df_sorted = df.sort_values(by=time_column) + fig2, ax = plt.subplots() + ax.plot(df_sorted[time_column], df_sorted[y_column], marker='o', color="#FB5607") + ax.set_xlabel(time_column.replace('_', ' ').title()) + ax.set_ylabel(y_column.replace('_', ' ').title()) + ax.set_title(f"{y_column.replace('_', ' ').title()} Over Time") + ax.grid(True) + st.pyplot(fig2) + + st.caption(""" + ๐Ÿง  **Why this chart?** + Simple line charts help elderly caregivers spot trends โ€” like increasing screen time or decreasing activity โ€” with clear markers. + """) + +# ๐Ÿ” Show page immediately +show_insights() diff --git a/pages/5_Predictor.py b/pages/5_Predictor.py new file mode 100644 index 0000000..149158c --- /dev/null +++ b/pages/5_Predictor.py @@ -0,0 +1,90 @@ +# pages/3_Predictor.py + +import streamlit as st +import joblib +import numpy as np +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +st.set_page_config(page_title="Predictor", page_icon="๐Ÿง ") + +def show_predictor(): + st.header("๐Ÿ“Š Obesity Risk Prediction (Caregiver Friendly)") + + st.markdown(""" + _Please enter the childโ€™s lifestyle details. Weโ€™ll estimate the risk and suggest simple actions._ + """) + + # Inputs + screen_time = st.slider("๐Ÿ“ฑ Daily screen time (hours)", 0, 12, 4) + physical_activity = st.slider("๐Ÿƒ Physical activity (mins/day)", 0, 180, 30) + sleep_duration = st.slider("๐Ÿ˜ด Sleep duration (hours)", 0, 12, 8) + diet_quality = st.selectbox("๐Ÿฝ๏ธ Diet quality", ["Poor", "Average", "Good"]) + income = st.selectbox("๐Ÿ’ฐ Household income", ["Low", "Medium", "High"]) + location = st.selectbox("๐Ÿ“ Location", ["Urban", "Regional", "Remote"]) + + # Encode inputs + diet_map = {"Poor": 1, "Average": 2, "Good": 3} + income_map = {"Low": 0, "Medium": 1, "High": 2} + location_map = {"Urban": 0, "Regional": 1, "Remote": 2} + + input_data = [ + screen_time, + physical_activity, + sleep_duration, + diet_map[diet_quality], + income_map[income], + location_map[location] + ] + + # Predict + if st.button("๐Ÿ” Predict Risk"): + try: + model = joblib.load("models/classifier.pkl") + features = joblib.load("models/feature_names.pkl") + weights = joblib.load("models/feature_weights.pkl") + + prediction = model.predict([input_data])[0] + probas = model.predict_proba([input_data])[0] + + class_map = {0: "Low", 1: "Medium", 2: "High"} + st.success(f"**Predicted Risk: {class_map[prediction]}**") + st.progress(int(probas[prediction] * 100)) + + # Show top contributors + st.markdown("### ๐Ÿ” Top 3 Risk Contributors") + top3 = np.argsort(weights)[::-1][:3] + explanations = { + "screen_time": "High screen time is linked with inactivity and weight gain.", + "physical_activity": "Exercise helps burn calories and maintain fitness.", + "sleep_duration": "Poor sleep affects hormones that control appetite.", + "diet_quality": "Low-nutrient foods can lead to unhealthy weight gain.", + "income": "Income may affect access to healthy food or sports.", + "location": "Environment can influence diet and exercise options." + } + for i in top3: + name = features[i] + st.markdown(f"- **{name.replace('_',' ').title()}**: {round(weights[i]*100, 2)}% โ€” {explanations.get(name, '')}") + + # Friendly tips + st.markdown("### ๐Ÿ“ข Simple Caregiver Tips") + if screen_time > 4 and physical_activity < 30: + st.warning("โš ๏ธ Try reducing screen time and encouraging outdoor play.") + if diet_map[diet_quality] == 1: + st.warning("๐Ÿญ Swap sugary snacks for fruits or home-cooked food.") + if sleep_duration < 7: + st.warning("๐Ÿ›Œ Encourage a consistent 8โ€“10 hr sleep routine.") + if prediction == 2: + st.error("๐Ÿšจ High risk detected. Please consult a pediatrician if possible.") + + st.info("โœ… This tool provides general tips. Always consult healthcare professionals for tailored advice.") + + except Exception as e: + st.error(f"โš ๏ธ An error occurred: {str(e)}") + +# ๐Ÿ” Show page immediately +show_predictor() diff --git a/pages/6_Caregiver_Engine.py b/pages/6_Caregiver_Engine.py new file mode 100644 index 0000000..3b583c7 --- /dev/null +++ b/pages/6_Caregiver_Engine.py @@ -0,0 +1,80 @@ +import streamlit as st +import pandas as pd +import plotly.express as px +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +st.set_page_config(page_title="Caregiver Engine", page_icon="๐Ÿง ") + +def generate_recommendation(row): + activity = row.get("physical_activity", 0) + screen_time = row.get("screen_time", 0) + snacks = row.get("snacking", "Unknown") + + recommendations = [] + + if activity < 3: + recommendations.append("๐Ÿง Consider adding a short morning walk or chair yoga session.") + elif activity < 6: + recommendations.append("๐Ÿšถ Keep up moderate movementโ€”light stretching in the evening helps too.") + else: + recommendations.append("๐Ÿ’ช Great job staying active! Keep it consistent.") + + if screen_time > 6: + recommendations.append("๐Ÿ“ต Try limiting screen time in the evenings and add screen breaks during the day.") + elif screen_time > 3: + recommendations.append("๐Ÿ“บ Moderate screen use detectedโ€”add off-screen hobbies like reading or puzzles.") + else: + recommendations.append("๐ŸŽฏ Balanced screen time! Keep it up.") + + if "snacking" in row and isinstance(row["snacking"], str) and "sugary" in row["snacking"].lower(): + recommendations.append("๐ŸŽ Replace sugary snacks with fruits or nuts in the evening.") + + return " ".join(recommendations) + +def show_caregiver_engine(): + st.header("๐Ÿง  Caregiver Recommendation Engine") + st.markdown(""" + Upload a dataset to get personalized lifestyle suggestions based on activity and screen time. + + **Columns required**: `physical_activity`, `screen_time` + """) + + uploaded_file = st.file_uploader("Upload CSV with at least 'physical_activity' and 'screen_time' columns", type=["csv"]) + + if uploaded_file is not None: + df = pd.read_csv(uploaded_file) + st.subheader("๐Ÿ“Š Data Preview") + st.dataframe(df.head()) + + st.subheader("๐Ÿ“ˆ Obesity Risk Distribution") + if 'obesity_risk' in df.columns: + chart = px.histogram(df, x='obesity_risk', color='obesity_risk', title='Obesity Risk Levels', + category_orders={"obesity_risk": ["Low", "Medium", "High"]}) + st.plotly_chart(chart) + + st.subheader("๐Ÿ“ Recommendations") + if 'physical_activity' in df.columns and 'screen_time' in df.columns: + df_rec = df[['physical_activity', 'screen_time']].copy() + df_rec['Caregiver_Advice'] = df.apply(generate_recommendation, axis=1) + + for i, row in df_rec.iterrows(): + with st.expander(f"Child #{i+1} Recommendation"): + st.write(f"**Physical Activity:** {row['physical_activity']}") + st.write(f"**Screen Time:** {row['screen_time']}") + st.markdown(f"**Advice:** {row['Caregiver_Advice']}") + + st.download_button("๐Ÿ“ฅ Download CSV with Recommendations", data=df_rec.to_csv(index=False), + file_name="caregiver_recommendations.csv", mime="text/csv") + + # Optional: Link to Predictor output if available + if 'obesity_risk' in df.columns: + st.subheader("๐Ÿค Linked with Risk Prediction") + st.markdown("This engine complements the Predictor module. Use the predicted 'obesity_risk' to inform lifestyle guidance.") + +# Show page immediately +show_caregiver_engine() diff --git a/pages/7_Privacy.py b/pages/7_Privacy.py new file mode 100644 index 0000000..f3f716d --- /dev/null +++ b/pages/7_Privacy.py @@ -0,0 +1,92 @@ +# pages/7_Privacy.py + +import streamlit as st +import pandas as pd +from io import BytesIO +from datetime import datetime +from reportlab.platypus import SimpleDocTemplate, Paragraph, Image +from reportlab.lib.styles import getSampleStyleSheet +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + + +st.set_page_config(page_title="Privacy & Data Control", page_icon="๐Ÿ”") + +# Function to clear session data +def clear_session_data(): + for key in list(st.session_state.keys()): + del st.session_state[key] + +# Function to generate PDF report with logo and timestamp +def generate_pdf(dataframe): + buffer = BytesIO() + doc = SimpleDocTemplate(buffer) + styles = getSampleStyleSheet() + flowables = [] + + # Add Deakin or custom project logo + logo_path = "data/deakin_logo.png" # Ensure this path is correct + try: + flowables.append(Image(logo_path, width=150, height=60)) + except Exception as e: + flowables.append(Paragraph("(Logo could not be loaded)", styles['Normal'])) + + flowables.append(Paragraph("Childhood Obesity Dashboard โ€“ Data Snapshot", styles['Title'])) + flowables.append(Paragraph("This is a locally generated summary. No cloud data storage is involved.", styles['Normal'])) + + df_str = dataframe.to_string(index=False) + for line in df_str.split('\n'): + flowables.append(Paragraph(line, styles['Code'])) + + doc.build(flowables) + buffer.seek(0) + return buffer + +# Main display function +def show_privacy_page(): + st.header("Privacy-First Design") + st.markdown(""" + This dashboard follows strict privacy practices tailored for elderly caregivers: + + - No personal data is stored online or sent to external servers. + - All user data is stored temporarily in session memory only. + - You can export your data to a PDF file for offline use. + - You can delete your data from memory at any time. + """) + + st.caption("Upload a CSV file to preview privacy features") + + uploaded_file = st.file_uploader("๐Ÿ“ Upload CSV", type="csv") + + if uploaded_file: + df = pd.read_csv(uploaded_file) + st.session_state['local_data'] = df + + st.success("โœ… Data stored locally in this session only.") + st.dataframe(df) + + # Export to timestamped PDF + if st.button("๐Ÿ“„ Export to PDF"): + pdf_file = generate_pdf(df) + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + filename = f"data_summary_{timestamp}.pdf" + st.download_button( + label="โฌ‡๏ธ Download PDF", + data=pdf_file, + file_name=filename, + mime="application/pdf" + ) + + # Clear session data + if st.button("๐Ÿงน Delete Session Data"): + clear_session_data() + st.warning("Session data deleted. Please refresh to confirm.") + + else: + st.info("โ„น๏ธ Upload a file to preview privacy features.") + +# Show page +show_privacy_page() diff --git a/pages/7_Resource_Hub.py b/pages/7_Resource_Hub.py new file mode 100644 index 0000000..4163518 --- /dev/null +++ b/pages/7_Resource_Hub.py @@ -0,0 +1,142 @@ +# 7_Resource_Hub.py +import io +import pandas as pd +import streamlit as st + +st.set_page_config(page_title="Resource Hub / Local Services Finder", layout="wide") + +st.title("๐Ÿ“ Resource Hub / Local Services Finder") +st.caption("Find nearby health and activity supports for families and caregivers. Data stays local to this session.") + +# --- Expected schema --- +REQUIRED_COLS = [ + "name","type","tags","address","suburb","postcode","state", + "lat","lon","phone","website","hours","cost","eligibility" +] + +with st.expander("Expected columns (in any order)", expanded=False): + st.code(", ".join(REQUIRED_COLS)) + +# ---------- Robust CSV loader ---------- +def load_services_df(uploaded_file: bytes | None) -> pd.DataFrame: + """Read user CSV safely. Auto-detect delimiter, handle encodings, validate columns.""" + if not uploaded_file: + return pd.DataFrame(columns=REQUIRED_COLS) + + raw = uploaded_file.read() if hasattr(uploaded_file, "read") else uploaded_file + attempts = [] + + # 1) Try pandas sniffing with different encodings/engines + for enc in ("utf-8", "utf-8-sig", "cp1252"): + for engine in ("python", "c"): + try: + buf = io.BytesIO(raw) + df = pd.read_csv( + buf, + sep=None, # auto-detect , ; | \t + engine=engine, + encoding=enc, + dtype=str, + on_bad_lines="skip" # skip malformed rows instead of raising + ) + df.columns = [c.strip().lower() for c in df.columns] + + # Check columns + missing = [c for c in REQUIRED_COLS if c not in df.columns] + if missing: + raise ValueError(f"Missing columns: {missing}") + + # Types and cleaning + for c in ("lat", "lon"): + df[c] = pd.to_numeric(df[c], errors="coerce") + df["postcode"] = df["postcode"].astype(str).str.replace(r"\.0$", "", regex=True) + # Reorder/select + return df[REQUIRED_COLS] + except Exception as e: + attempts.append(f"{enc}/{engine}: {type(e).__name__}: {e}") + + # 2) Fallback: explicit regex of common separators + try: + buf = io.BytesIO(raw) + df = pd.read_csv( + buf, sep=r"[,\t|;]", engine="python", encoding="utf-8", dtype=str, on_bad_lines="skip" + ) + df.columns = [c.strip().lower() for c in df.columns] + missing = [c for c in REQUIRED_COLS if c not in df.columns] + if missing: + raise ValueError(f"Missing columns after fallback: {missing}") + for c in ("lat", "lon"): + df[c] = pd.to_numeric(df[c], errors="coerce") + df["postcode"] = df["postcode"].astype(str).str.replace(r"\.0$", "", regex=True) + return df[REQUIRED_COLS] + except Exception as e: + attempts.append(f"fallback: {type(e).__name__}: {e}") + + st.error("Could not read CSV. Please check the header and delimiters.") + st.caption("Attempts โ†’ " + " | ".join(attempts)) + return pd.DataFrame(columns=REQUIRED_COLS) + +# ---------- Demo data toggle ---------- +demo_rows = [ + ["Burwood Community Health Centre","GP Clinic","bulk-billing,family","2 Warrigal Rd","Burwood","3125","VIC",-37.8497,145.1127,"(03) 9800 1111","https://www.burwoodhealth.org","Monโ€“Fri 8โ€“6; Sat 9โ€“1","Bulk-billing available","All ages; Medicare card"], + ["Deakin University Health & Wellbeing","Nutrition Workshop","students,free,workshop","221 Burwood Hwy","Burwood","3125","VIC",-37.847,145.114,"(03) 9244 6100","https://www.deakin.edu.au","Monโ€“Fri 9โ€“5","Free for students","Students & staff"], + ["Burwood Neighbourhood House","Activity Program","kids,after-school,community","1 Church St","Burwood","3125","VIC",-37.852,145.099,"(03) 9808 6292","https://www.burwoodneighbourhoodhouse.org.au","Monโ€“Fri 9โ€“6","Low-cost","Children & families"], + ["Eastern Mental Health Service","Mental Health","counselling,youth,adults","34 Station St","Burwood","3125","VIC",-37.854,145.105,"(03) 9887 1234","https://www.easternhealth.org.au","24/7","Free","All residents"], + ["Whitehorse Council Recreation Centre","Council Recreation","sports,swimming,fitness","42 Burwood Hwy","Burwood","3125","VIC",-37.85,145.12,"(03) 9262 6333","https://www.whitehorse.vic.gov.au","Monโ€“Sun 6โ€“9","Membership fees","Open to all"], +] + +demo_df = pd.DataFrame(demo_rows, columns=REQUIRED_COLS) + +col_left, col_right = st.columns([1,1]) +with col_left: + uploaded = st.file_uploader("Upload services file (CSV)", type=["csv"]) +with col_right: + use_demo = st.toggle("Use built-in Burwood demo data", value=(uploaded is None)) + +if uploaded and not use_demo: + df = load_services_df(uploaded) +else: + df = demo_df.copy() + +# ---------- Filters ---------- +st.subheader("๐Ÿ”Ž Search & Filters") +c1, c2, c3, c4 = st.columns([1,1,1,1]) + +with c1: + suburb = st.selectbox("Suburb", ["(All)"] + sorted(df["suburb"].dropna().unique().tolist())) +with c2: + types = st.multiselect("Type", sorted(df["type"].dropna().unique().tolist())) +with c3: + tag_query = st.text_input("Tags contains (comma-separated)", placeholder="free, kids, evening") +with c4: + only_free = st.checkbox("Show free/low-cost") + +filtered = df.copy() +if suburb and suburb != "(All)": + filtered = filtered[filtered["suburb"].str.lower() == suburb.lower()] +if types: + filtered = filtered[filtered["type"].isin(types)] +if tag_query.strip(): + terms = [t.strip().lower() for t in tag_query.split(",") if t.strip()] + filtered = filtered[filtered["tags"].str.lower().fillna("").apply(lambda x: any(t in x for t in terms))] +if only_free: + filtered = filtered[filtered["cost"].str.lower().str.contains("free|low", na=False)] + +st.write(f"**Results:** {len(filtered)} services") +st.dataframe(filtered, use_container_width=True, hide_index=True) + +# ---------- Map ---------- +st.subheader("๐Ÿ—บ๏ธ Map") +map_df = filtered.dropna(subset=["lat","lon"])[["lat","lon","name","type","suburb"]] +if not map_df.empty: + st.map(map_df, latitude="lat", longitude="lon", size=80, color="#22c55e") +else: + st.info("No mappable rows (missing lat/lon).") + +# ---------- Download filtered data ---------- +st.download_button( + "Download filtered CSV", + data=filtered.to_csv(index=False).encode("utf-8"), + file_name="services_filtered.csv", + mime="text/csv" +) diff --git a/pages/__init__.py b/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/__pycache__/__init__.cpython-311.pyc b/pages/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..4edfa7e Binary files /dev/null and b/pages/__pycache__/__init__.cpython-311.pyc differ diff --git a/pages/__pycache__/data_explorer.cpython-311.pyc b/pages/__pycache__/data_explorer.cpython-311.pyc new file mode 100644 index 0000000..e9e74a1 Binary files /dev/null and b/pages/__pycache__/data_explorer.cpython-311.pyc differ diff --git a/pages/__pycache__/fairness.cpython-311.pyc b/pages/__pycache__/fairness.cpython-311.pyc new file mode 100644 index 0000000..b93adaa Binary files /dev/null and b/pages/__pycache__/fairness.cpython-311.pyc differ diff --git a/pages/__pycache__/home.cpython-311.pyc b/pages/__pycache__/home.cpython-311.pyc new file mode 100644 index 0000000..66dc485 Binary files /dev/null and b/pages/__pycache__/home.cpython-311.pyc differ diff --git a/pages/__pycache__/insights.cpython-311.pyc b/pages/__pycache__/insights.cpython-311.pyc new file mode 100644 index 0000000..0c2953b Binary files /dev/null and b/pages/__pycache__/insights.cpython-311.pyc differ diff --git a/pages/__pycache__/predictor.cpython-311.pyc b/pages/__pycache__/predictor.cpython-311.pyc new file mode 100644 index 0000000..f442653 Binary files /dev/null and b/pages/__pycache__/predictor.cpython-311.pyc differ diff --git a/utils/__pycache__/language_map.cpython-311.pyc b/utils/__pycache__/language_map.cpython-311.pyc new file mode 100644 index 0000000..b491533 Binary files /dev/null and b/utils/__pycache__/language_map.cpython-311.pyc differ diff --git a/utils/__pycache__/translator.cpython-311.pyc b/utils/__pycache__/translator.cpython-311.pyc new file mode 100644 index 0000000..4bcaa97 Binary files /dev/null and b/utils/__pycache__/translator.cpython-311.pyc differ diff --git a/utils/__pycache__/translator.cpython-312.pyc b/utils/__pycache__/translator.cpython-312.pyc new file mode 100644 index 0000000..606bb4f Binary files /dev/null and b/utils/__pycache__/translator.cpython-312.pyc differ diff --git a/utils/__pycache__/voice.cpython-311.pyc b/utils/__pycache__/voice.cpython-311.pyc new file mode 100644 index 0000000..22fd1ab Binary files /dev/null and b/utils/__pycache__/voice.cpython-311.pyc differ diff --git a/utils/language_map.py b/utils/language_map.py new file mode 100644 index 0000000..2839043 --- /dev/null +++ b/utils/language_map.py @@ -0,0 +1,8 @@ +# utils/language_map.py + +LANGUAGE_CODES = { + "English": "en", + "Italiano": "it", + "ฮ•ฮปฮปฮทฮฝฮนฮบฮฌ (Greek)": "el", + "ไธญๆ–‡ (Chinese)": "zh" +} diff --git a/utils/translator.py b/utils/translator.py new file mode 100644 index 0000000..4ac0d1e --- /dev/null +++ b/utils/translator.py @@ -0,0 +1,22 @@ +# utils/translator.py +import speech_recognition +import translate +import plotly +import pywaffle +import joblib + +from translate import Translator + + +def get_translator(language_code): + try: + return Translator(to_lang=language_code) + except Exception as e: + print("Translation error:", e) + return None + +def translate_text(translator, text): + try: + return translator.translate(text) + except: + return text # fallback if translation fails diff --git a/utils/voice.py b/utils/voice.py new file mode 100644 index 0000000..2a77551 --- /dev/null +++ b/utils/voice.py @@ -0,0 +1,18 @@ +# utils/voice.py + +import speech_recognition as sr + +def capture_voice_command(): + r = sr.Recognizer() + + with sr.Microphone() as source: + print("Listening...") + audio = r.listen(source) + + try: + query = r.recognize_google(audio) + return query + except sr.UnknownValueError: + return "Sorry, I couldn't understand that." + except sr.RequestError: + return "Sorry, the service is currently unavailable."