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