From aec43ed6fd0279ca132b929f894a645b1611b03c Mon Sep 17 00:00:00 2001 From: asmi880 <120006467+Asmi880@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:31:25 +1100 Subject: [PATCH] Add files via upload --- app.py | 74 ++++++ pages/00_Subscription.py | 82 +++++++ pages/08_Healthy_Habits_Streaks.py | 120 ++++++++++ pages/09_Cognitive_Lifestyle_Support.py | 100 +++++++++ pages/10_Streak_Summary.py | 9 + pages/11_Social_Connection.py | 77 +++++++ pages/12_Family_Support.py | 212 ++++++++++++++++++ pages/1_Home.py | 106 +++++++++ pages/2_Data_Explorer.py | 80 +++++++ pages/3_Fairness.py | 51 +++++ pages/4_Insights.py | 83 +++++++ pages/5_Predictor.py | 90 ++++++++ pages/6_Caregiver_Engine.py | 80 +++++++ pages/7_Privacy.py | 92 ++++++++ pages/7_Resource_Hub.py | 142 ++++++++++++ pages/__init__.py | 0 pages/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 178 bytes .../__pycache__/data_explorer.cpython-311.pyc | Bin 0 -> 3019 bytes pages/__pycache__/fairness.cpython-311.pyc | Bin 0 -> 821 bytes pages/__pycache__/home.cpython-311.pyc | Bin 0 -> 870 bytes pages/__pycache__/insights.cpython-311.pyc | Bin 0 -> 4725 bytes pages/__pycache__/predictor.cpython-311.pyc | Bin 0 -> 5015 bytes .../__pycache__/language_map.cpython-311.pyc | Bin 0 -> 316 bytes utils/__pycache__/translator.cpython-311.pyc | Bin 0 -> 1020 bytes utils/__pycache__/translator.cpython-312.pyc | Bin 0 -> 863 bytes utils/__pycache__/voice.cpython-311.pyc | Bin 0 -> 1154 bytes utils/language_map.py | 8 + utils/translator.py | 22 ++ utils/voice.py | 18 ++ 29 files changed, 1446 insertions(+) create mode 100644 app.py create mode 100644 pages/00_Subscription.py create mode 100644 pages/08_Healthy_Habits_Streaks.py create mode 100644 pages/09_Cognitive_Lifestyle_Support.py create mode 100644 pages/10_Streak_Summary.py create mode 100644 pages/11_Social_Connection.py create mode 100644 pages/12_Family_Support.py create mode 100644 pages/1_Home.py create mode 100644 pages/2_Data_Explorer.py create mode 100644 pages/3_Fairness.py create mode 100644 pages/4_Insights.py create mode 100644 pages/5_Predictor.py create mode 100644 pages/6_Caregiver_Engine.py create mode 100644 pages/7_Privacy.py create mode 100644 pages/7_Resource_Hub.py create mode 100644 pages/__init__.py create mode 100644 pages/__pycache__/__init__.cpython-311.pyc create mode 100644 pages/__pycache__/data_explorer.cpython-311.pyc create mode 100644 pages/__pycache__/fairness.cpython-311.pyc create mode 100644 pages/__pycache__/home.cpython-311.pyc create mode 100644 pages/__pycache__/insights.cpython-311.pyc create mode 100644 pages/__pycache__/predictor.cpython-311.pyc create mode 100644 utils/__pycache__/language_map.cpython-311.pyc create mode 100644 utils/__pycache__/translator.cpython-311.pyc create mode 100644 utils/__pycache__/translator.cpython-312.pyc create mode 100644 utils/__pycache__/voice.cpython-311.pyc create mode 100644 utils/language_map.py create mode 100644 utils/translator.py create mode 100644 utils/voice.py diff --git a/app.py b/app.py new file mode 100644 index 00000000..101f81ef --- /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 00000000..adc6ee55 --- /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 00000000..ca8e6fec --- /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 00000000..d57081f7 --- /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 00000000..101a02c7 --- /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 00000000..81b08a79 --- /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 00000000..3fe507b7 --- /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 00000000..be946770 --- /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 00000000..4f5cfe48 --- /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 00000000..a3af9ae1 --- /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 00000000..ad1dcad6 --- /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 00000000..149158c9 --- /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 00000000..3b583c7d --- /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 00000000..f3f716da --- /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 00000000..4163518d --- /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 00000000..e69de29b diff --git a/pages/__pycache__/__init__.cpython-311.pyc b/pages/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4edfa7e766e3bbde14dd2b8204fa336329db8ab6 GIT binary patch literal 178 zcmZ3^%ge<81n2j*W`O9&AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFIWB0;?$yI z{lwzj%=E;fbbXi9;_Q0OheQKL7v# literal 0 HcmV?d00001 diff --git a/pages/__pycache__/data_explorer.cpython-311.pyc b/pages/__pycache__/data_explorer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9e74a1a0f75b58e351b1b7a84742f2cc77990d3 GIT binary patch literal 3019 zcmb_eTTC3+8J^iYFiTjxF=Q9p)6m%FQdY&6VyJA)Vz4P-TO_IKwpz{Z9N2-`na#`@ zZd#Eun=f7gcH={m_TJbq0-ubtEJ!RUh)yjoiHWss9-mc8B`0 zN3-YrxAWi5cXsCEXtbVz@zFnim9Gym%s(mP4&Nd1>^u-3Fd`$eGINi0R^L6pvjz&m zdm)w)eeW>$!Xi7xbojrf+a_~|o1G;BvmbuX9tNl<%vP1NN3_Up0=<32vtBzl8;B+P zcLJNhZy(84m=d$@(@x=Vjc15;FiMQq^9t+9An4kO!4g}BF8uG<1kv`9*c&x)_amVB?cN@ljsCB^WZQ)N>RQ&W-k9aaTAfRa^*aq--z$E6#?>;+Y?DiSz5NXOuo74amV)J& zYf-zF{Ikt25A+F_7P+11CZKJbBF-65ORyYwx!P51^k(K**7Utg+g=)OoK6>qjnVKqx;D+Ybg&Z+uYryfOqe)yLQU86JI`VCo)ss|Bus0 zwkKa?OYJ*aCJhRy%A?=>f*%&Jz~5Lb%BqI6W>3#Rt=cHY@^Q`MwpRl$PH4+$Ed&I=mu=id}$1Mz9q zP_X_wWK$>TIi!e^GP4|Rzi_?p^2i8CgAhp7mSdAfA&WGc+6@J364D8qdCv07O~aB7 zeAd8{s!;U|M27M78VP|rj5KiKn-`2S29&F_tAmKcp6EK>Arskld5H&}8C}ia$=jRm_ zgH*wiVyK4R(bI8;_;gG{c}Ns#Bswk0C}TJn0oGR(Nrcvr0bG#af+k@^0@Rk&9mLP7 zizIw-I;ziY+s+VQ(@Pdp9BzhT;fD!U(jY3-*Qr>y8XJ9=)_w;!orizbCBNxE&i`4Lxm$u1>G@+YQN$ z#odN;X2ZF^T-ee+J-f5Kmm1tn4O*!oJ2hl=rR}ctR~{(u zj;^NHo1buv?~UFcT_3mOXSX=}{J`TYtD_b-VRI8EH$k-%_b1H6_bjf*=6X!7=TNk9 z_EFEno~;vGIV;g?Cwli1eY=T1E75N!`Ymq2<_1h|fZC4VA2(l_+#300^3RiBTzKra zx`*xV;l1v$-R?1~d))4(YIrQ}w$0r(x!e1#-zj(7uU@iRFWaq`D-0XG;!N)c*4g#% zKWS?IApGm_2D_2|ZMYo!V|t7I{m36iwiegJR#Ttd)Mq*mZP2at+`E%&ldF^e0%okj z_*cjttHdoX=|zM?@8CE*1hugVlbx6BaI-thyxBVr6y#7W z?G&u66!9PMPq2(&airAB%1ErP^39q(#5^|N?#z7eH(zG=eXrLQT$esSPv1sDd=G=V zcwE)uJl|dmC7y{#LPhsQ+(?#7)vd`Iev1(c`ywTbcp@H)KwK@=c*pY}&(@;gY^?f( zP|g3HO7QNjy&u6{US|3B=GD(nFJW71mm?8egDs!xgk+!jUEq3rYF)(26vG5y)6bws*OsmRYN;M z;&j8aqaPGR_YEB@Mw zr>!_l?H=#W#rnneFR^~E^E(I+|CI%k#9ka#><_|)uidp-PTMy#t4f2nT==}JZLjjf QyctE&ywQ!Av?^8q9}Se+s{jB1 literal 0 HcmV?d00001 diff --git a/pages/__pycache__/home.cpython-311.pyc b/pages/__pycache__/home.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66dc485dafb75375871fdcd8ced1dc9f887a00a4 GIT binary patch literal 870 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 literal 0 HcmV?d00001 diff --git a/pages/__pycache__/predictor.cpython-311.pyc b/pages/__pycache__/predictor.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4426531fa98a9c3ab5b442fdc4991c23b10a0a8 GIT binary patch literal 5015 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 literal 0 HcmV?d00001 diff --git a/utils/__pycache__/translator.cpython-312.pyc b/utils/__pycache__/translator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..606bb4f9a1903b9b950d86352737cd605af0580b GIT binary patch literal 863 zcmYjPy>HV%6o2QB)M-duwNX*FKr4iYl8y)g5>gQxOJS%NwawdG@nr1MX> z-x0Ad84vK>nV1;qsN-j74xk8f)*Zqpax~30VFhH44_1LM?-8liW)f;cay0D{*h0j6 z#8cO0E7G->f+KcI=kMrtNJ>PSaR z%W6^AZ&5E`Ax#Mt>d1P1@~H7tP%aw2*o|5~Z#6ckcrIDe2-;B`wiydO)}$hm126PN zyUBbWHo7v3MMKV5xs?pkN{dSGUx}A+f2Cp_%_|sbhFLwcYlmwm_WH54etA}^UVw%* zt9)F!e)!h zH>=<6+BbXUi@kEV@zr*YE$4Ei5mjD#Q+ObEuud!YPzb!AAQN>K6r*RL2#ARQWPa{Q*%D&%FQu literal 0 HcmV?d00001 diff --git a/utils/__pycache__/voice.cpython-311.pyc b/utils/__pycache__/voice.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22fd1abaaafd9fa2d6199899df7449e81736e4f0 GIT binary patch literal 1154 zcmZux%}d-s6n~R!*7&jASgLra-Bc}QL3SyvCtHhR3x(E$VnHuUOorXGF|(bSXjc?G z^q{b(QqZF(rTAA^`Uk8_I&BCAz!V_RZv%H*bFPKIW0^rBWS$at%I| zJ`;d%(V`*BrE+=$l`U|ekBoAd`>6P-xf&3hP-O( zyJ*^Pm|^WIs}UEjvPNKy$hPJo%w`w;?KgcAScRQLYrmzf{>5BVuofJ<7-_#5jey@d z2=_oFmX){a94r$8b3kykN1nBozm~SDT+LxkT~%jb5M+x2uc8}?We9j8^64luR~gN! znhf-^3>R5s6H_Z)&s`y@@A}1DF1Io;!#J-Do6k&}1)k&Im1f{OlnZJ3jwwr)%&iPx za%72`g7PKTrlu=QJK&u9(yQPJYsqpwYr&(rtR57Tu!7i}^_^UQ1Ta+a=d?N{l%Lwm^j|D+R|Ar3Gej zCm%@H6Zs{E<>ktiu*bH_GT^ijTM9N?T*NEk-54Y4pZJNe4kvm?HMN6w|7?i zH#5~t9Te^M_R|wRwc#J?{YQF#P47Q8y0`TA`gZT3VICRghE~^*6;osQ6&?B=LCf|( z?dcqT=_MipF>HD>K;%@bE zWFKq6ar#j$o^=)N7*u4j-n~2Hed