import streamlit as st from typing import List, Dict, Any, Optional, Tuple import re import os from utils import db as dbapi from utils import api as backend_api # unified backend client from phase.Student_view import quiz as quiz_page USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" FALLBACK_TAG = "" # --- Load external CSS (optional) --- def load_css(file_name: str): try: with open(file_name, "r", encoding="utf-8") as f: st.markdown(f"", unsafe_allow_html=True) except FileNotFoundError: st.warning("โ ๏ธ Stylesheet not found. Please ensure 'assets/styles.css' exists.") # --------------------------------------------- # Page state helpers # --------------------------------------------- _SS_DEFAULTS = { "level": "beginner", # beginner | intermediate | advanced "module_id": None, # int (1-based) "topic_idx": 0, # 0-based within module "mode": "catalog", # catalog | lesson | quiz | results "topics_cache": {}, # {(level, module_id): [(title, text), ...]} "show_quiz_prompt": False, # <-- controls the post-module popup "selected_quiz": None, } # Works on Streamlit versions that support st.dialog if hasattr(st, "dialog"): @st.dialog("Ready for a quick check-in?") def _quiz_prompt_dialog(): st.write("Would you like to do **Quiz 1** to strengthen your knowledge?") c1, c2 = st.columns(2) if c1.button("Yes, start Quiz 1", key="dlg_yes"): st.session_state.show_quiz_prompt = False st.session_state.mode = "quiz" st.session_state.selected_quiz = 1 st.session_state.current_q = 0 st.session_state.answers = {} st.rerun() if c2.button("Maybe later", key="dlg_no"): st.session_state.show_quiz_prompt = False st.session_state.mode = "catalog" st.session_state.module_id = None st.session_state.topic_idx = 0 st.rerun() else: # Fallback: inline banner if st.dialog isn't available def _quiz_prompt_dialog(): st.info("Would you like to do **Quiz 1** to strengthen your knowledge?") c1, c2 = st.columns(2) if c1.button("Yes, start Quiz 1", key="inline_yes"): st.session_state.mode = "quiz" st.session_state.selected_quiz = 1 st.session_state.current_q = 0 st.session_state.answers = {} st.rerun() if c2.button("Maybe later", key="inline_no"): st.session_state.mode = "catalog" st.session_state.module_id = None st.session_state.topic_idx = 0 st.rerun() def _ensure_state(): for k, v in _SS_DEFAULTS.items(): if k not in st.session_state: st.session_state[k] = v def _fetch_assigned_lesson(lesson_id: int) -> dict: """ Returns {"lesson": {...}, "sections": [...]} Falls back to backend_api if you're not using the local DB. """ if USE_LOCAL_DB and hasattr(dbapi, "get_lesson"): return dbapi.get_lesson(lesson_id) or {} try: return backend_api.get_lesson(lesson_id) or {} except Exception: return {} # --------------------------------------------- # Content metadata (UI only) # --------------------------------------------- MODULES_META: Dict[str, List[Dict[str, Any]]] = { "beginner": [ { "id": 1, "title": "Understanding Money", "description": "Learn the basics of what money is, its uses, and how to manage it.", "duration": "20 min", "completed": False, "locked": False, "difficulty": "Easy", "topics": [ "What is Money?", "Needs vs. Wants", "Earning Money", "Saving Money", "Spending Wisely", "Play: Money Match", "Quiz", "Summary: My Money Plan" ] }, { "id": 2, "title": "Basic Budgeting", "description": "Start building the habit of planning and managing money through budgeting.", "duration": "20 min", "completed": False, "locked": False, "difficulty": "Easy", "topics": [ "What is a Budget?", "Income and Expenses", "Profit and Loss", "Saving Goals", "Making Choices", "Play: Budget Builder", "Quiz", "Summary: My First Budget" ] }, { "id": 3, "title": "Money in Action", "description": "Learn how money is used in everyday transactions and its role in society.", "duration": "20 min", "completed": False, "locked": False, "difficulty": "Easy", "topics": [ "Paying for Things", "Keeping Track of Money", "What Are Taxes?", "Giving and Sharing", "Money Safety", "Play: Piggy Bank Challenge", "Quiz", "Summary: Money Journal" ] }, { "id": 4, "title": "Simple Business Ideas", "description": "Explore the basics of starting a small business and earning profit.", "duration": "20 min", "completed": False, "locked": False, "difficulty": "Easy", "topics": [ "What is a Business?", "Costs in a Business", "Revenue in a Business", "Profit in a Business", "Advertising Basics", "Play: Smart Shopper", "Quiz", "Summary: My Business Plan" ] } ], "intermediate": [], "advanced": [], } def _topic_plan(level: str, module_id: int): """ Returns a list of (title, backend_ordinal) after filtering: - drop any 'Play:' topic - drop 'Quiz' - keep first five + the Summary (6 total) backend_ordinal is the 1-based index in the original metadata (so backend files line up). """ mod = next(m for m in MODULES_META[level] if m["id"] == module_id) raw = (mod.get("topics") or mod.get("topic_labels") or []) plan = [] for i, t in enumerate(raw, start=1): tl = t.strip().lower() if tl == "quiz" or tl.startswith("play:"): continue plan.append((t, i)) # Ensure at most 6 topics: first five + Summary if present if len(plan) > 6: summary_pos = next((idx for idx, (title, _) in enumerate(plan) if title.strip().lower().startswith("summary")), None) if summary_pos is not None: plan = plan[:5] + [plan[summary_pos]] else: plan = plan[:6] return plan def _topic_titles(level: str, module_id: int): return [t for (t, _) in _topic_plan(level, module_id)] # --------------------------------------------- # Backend integrations # --------------------------------------------- @st.cache_data(show_spinner=False, ttl=300) def _fetch_topic_from_backend(level: str, module_id: int, topic_idx: int) -> Tuple[str, str]: """ Returns (ui_title, content). Calls the FastAPI /lesson endpoint via utils.api.fetch_lesson_content. The backend expects files at /app/lessons/lesson_{module_id}/topic_{ordinal}.txt """ plan = _topic_plan(level, module_id) ui_title, backend_ordinal = plan[topic_idx] # 1-based ordinal in original list try: content = backend_api.fetch_lesson_content( lesson=f"lesson_{module_id}", module=str(module_id), topic=str(backend_ordinal), ) content = (content or "").strip() except Exception as e: st.warning(f"Lesson fetch failed for lesson_{module_id}/topic_{backend_ordinal}: {e}") content = "" return ui_title, content def _fallback_text(title: str, module_id: int, topic_ordinal: int) -> str: """ Minimal on-brand copy if the backend has not supplied a topic file yet. Tailored to beginner modules so the UI stays useful. """ t = title.strip().lower() if "what is money" in t or "money" == t: return ("Money is a tool we use to trade for goods and services. " "In Jamaica we use JMD coins and notes, and many people also pay digitally. " "You can spend, save, or share money, but it is limited, so plan how you use it.") + "\n" + FALLBACK_TAG if "need" in t and "want" in t: return ("Needs keep you safe and healthy, like food, clothes, and school supplies. " "Wants are nice to have, like toys or snacks. Cover needs first, then plan for wants.") + "\n" + FALLBACK_TAG if "earn" in t: return ("You earn money by doing work or providing value. Small jobs add up. " "Earnings give you choices to spend, save, or share, and teach the value of effort.") + "\n" + FALLBACK_TAG if "sav" in t: return ("Saving means putting aside some money now for future needs or goals. " "Start small and be consistent. A jar, a partner plan, or a bank account all help.") + "\n" + FALLBACK_TAG if "spend" in t or "wisely" in t: return ("Spending wisely means comparing prices, making a simple budget, and avoiding impulse buys. " "Aim for best value so your goals stay on track.") + "\n" + FALLBACK_TAG if "summary" in t or "journal" in t or "plan" in t: return ("Quick recap: cover needs first, set a small saving goal, and make one spending rule for the week. " "Write one action you will try before the next lesson.") + "\n" + FALLBACK_TAG if "quiz" in t: return ("Take a short quiz to check your understanding of this lesson.") + "\n" + FALLBACK_TAG # Generic fallback return (f"This unit will be populated from lesson_{module_id}/topic_{topic_ordinal}.txt. " "For now, review the key idea and write one example from daily life.") + "\n" + FALLBACK_TAG # --------------------------------------------- # UI building blocks # --------------------------------------------- def _render_catalog(): st.header("Financial Education") st.caption("Build your financial knowledge with structured paths for every skill level.") # Custom CSS for card styling st.markdown(""" """, unsafe_allow_html=True) level = st.session_state.get("level", "beginner") cols = st.columns(2, gap="large") for i, mod in enumerate(MODULES_META[level]): with cols[i % 2]: with st.container(): st.markdown(f"""
{mod['description']}
Content coming soon.
"}