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}12lp42;GI#^9V=ZF38g{X<1}EcPNW`<
zmvJO5Br%imAhnV~&)?pTX+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