File size: 11,816 Bytes
eba7c64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92bfe31
eba7c64
92bfe31
eba7c64
 
 
 
 
92bfe31
eba7c64
 
 
 
 
 
 
92bfe31
eba7c64
 
 
 
 
92bfe31
eba7c64
 
 
 
 
 
 
 
 
92bfe31
eba7c64
 
 
 
 
 
 
 
 
92bfe31
eba7c64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
"""
Curriculum Service - Firestore-backed curriculum data.

Fetches subjects, topics, and modules from Firestore.
Falls back to static data if Firestore is unavailable.
"""

import logging
import os
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

# Static curriculum data as fallback
_STATIC_SUBJECTS = [
    {
        "id": "gen-math",
        "code": "GEN MATH",
        "name": "General Mathematics",
        "gradeLevel": "Grade 11",
        "semester": "1st Semester",
        "color": "from-blue-500 to-cyan-500",
        "pdfAvailable": True,
        "topics": [
            {"id": "gen-math-001", "name": "Patterns and Real-Life Relationships", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-002", "name": "Functions as Mathematical Models", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-003", "name": "Function Notation and Evaluation", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-004", "name": "Domain and Range of Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-005", "name": "Operations on Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-006", "name": "Composite Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-007", "name": "Inverse Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-008", "name": "Graphs of Rational Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-009", "name": "Graphs of Exponential Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-010", "name": "Graphs of Logarithmic Functions", "unit": "Patterns, Relations, and Functions"},
            {"id": "gen-math-011", "name": "Simple and Compound Interest", "unit": "Financial Mathematics"},
            {"id": "gen-math-012", "name": "Simple and General Annuities", "unit": "Financial Mathematics"},
            {"id": "gen-math-013", "name": "Present and Future Value", "unit": "Financial Mathematics"},
            {"id": "gen-math-014", "name": "Loans, Amortization, and Sinking Funds", "unit": "Financial Mathematics"},
            {"id": "gen-math-015", "name": "Stocks, Bonds, and Market Indices", "unit": "Financial Mathematics"},
            {"id": "gen-math-016", "name": "Business Decision-Making with Mathematical Models", "unit": "Financial Mathematics"},
            {"id": "gen-math-017", "name": "Propositions and Logical Connectives", "unit": "Logic and Mathematical Reasoning"},
            {"id": "gen-math-018", "name": "Truth Values and Truth Tables", "unit": "Logic and Mathematical Reasoning"},
            {"id": "gen-math-019", "name": "Logical Equivalence and Implication", "unit": "Logic and Mathematical Reasoning"},
            {"id": "gen-math-020", "name": "Quantifiers and Negation", "unit": "Logic and Mathematical Reasoning"},
            {"id": "gen-math-021", "name": "Validity of Arguments", "unit": "Logic and Mathematical Reasoning"},
        ]
    },
    {
        "id": "stats-prob",
        "code": "STAT&PROB",
        "name": "Statistics and Probability",
        "gradeLevel": "Grade 11",
        "semester": "2nd Semester",
        "color": "from-sky-500 to-cyan-500",
        "pdfAvailable": True,
        "topics": [
            {"id": "stat-001", "name": "Random Variables", "unit": "Random Variables"},
            {"id": "stat-002", "name": "Discrete Probability Distributions", "unit": "Random Variables"},
            {"id": "stat-003", "name": "Mean and Variance of Discrete RV", "unit": "Random Variables"},
            {"id": "stat-004", "name": "Normal Distribution", "unit": "Normal Distribution"},
            {"id": "stat-005", "name": "Standard Normal Distribution and Z-scores", "unit": "Normal Distribution"},
            {"id": "stat-006", "name": "Areas Under the Normal Curve", "unit": "Normal Distribution"},
            {"id": "stat-007", "name": "Sampling Distributions", "unit": "Sampling and Estimation"},
            {"id": "stat-008", "name": "Central Limit Theorem", "unit": "Sampling and Estimation"},
            {"id": "stat-009", "name": "Point Estimation", "unit": "Sampling and Estimation"},
            {"id": "stat-010", "name": "Confidence Intervals", "unit": "Sampling and Estimation"},
            {"id": "stat-011", "name": "Hypothesis Testing Concepts", "unit": "Hypothesis Testing"},
            {"id": "stat-012", "name": "T-test", "unit": "Hypothesis Testing"},
            {"id": "stat-013", "name": "Z-test", "unit": "Hypothesis Testing"},
            {"id": "stat-014", "name": "Correlation and Regression", "unit": "Correlation and Regression"},
        ]
    },
    {
        "id": "pre-calc",
        "code": "PRE-CALC",
        "name": "Pre-Calculus",
        "gradeLevel": "Grade 12",
        "semester": "1st Semester",
        "color": "from-orange-500 to-red-500",
        "pdfAvailable": False,
        "topics": [
            {"id": "pre-calc-001", "name": "Conic Sections - Parabola", "unit": "Analytic Geometry"},
            {"id": "pre-calc-002", "name": "Conic Sections - Ellipse", "unit": "Analytic Geometry"},
            {"id": "pre-calc-003", "name": "Conic Sections - Hyperbola", "unit": "Analytic Geometry"},
            {"id": "pre-calc-004", "name": "Conic Sections - Circle", "unit": "Analytic Geometry"},
            {"id": "pre-calc-005", "name": "Systems of Nonlinear Equations", "unit": "Analytic Geometry"},
            {"id": "pre-calc-006", "name": "Sequences and Series", "unit": "Series and Induction"},
            {"id": "pre-calc-007", "name": "Arithmetic Sequences", "unit": "Series and Induction"},
            {"id": "pre-calc-008", "name": "Geometric Sequences", "unit": "Series and Induction"},
            {"id": "pre-calc-009", "name": "Mathematical Induction", "unit": "Series and Induction"},
            {"id": "pre-calc-010", "name": "Binomial Theorem", "unit": "Series and Induction"},
            {"id": "pre-calc-011", "name": "Angles and Unit Circle", "unit": "Trigonometry"},
            {"id": "pre-calc-012", "name": "Trigonometric Functions", "unit": "Trigonometry"},
            {"id": "pre-calc-013", "name": "Trigonometric Identities", "unit": "Trigonometry"},
            {"id": "pre-calc-014", "name": "Sum and Difference Formulas", "unit": "Trigonometry"},
            {"id": "pre-calc-015", "name": "Inverse Trigonometric Functions", "unit": "Trigonometry"},
            {"id": "pre-calc-016", "name": "Polar Coordinates", "unit": "Trigonometry"},
        ]
    },
    {
        "id": "basic-calc",
        "code": "BASIC CALC",
        "name": "Basic Calculus",
        "gradeLevel": "Grade 12",
        "semester": "2nd Semester",
        "color": "from-green-500 to-teal-500",
        "pdfAvailable": True,
        "topics": [
            {"id": "calc-001", "name": "Limits of Functions", "unit": "Limits"},
            {"id": "calc-002", "name": "Limit Theorems", "unit": "Limits"},
            {"id": "calc-003", "name": "One-Sided Limits", "unit": "Limits"},
            {"id": "calc-004", "name": "Infinite Limits and Limits at Infinity", "unit": "Limits"},
            {"id": "calc-005", "name": "Continuity of Functions", "unit": "Limits"},
            {"id": "calc-006", "name": "Definition of the Derivative", "unit": "Derivatives"},
            {"id": "calc-007", "name": "Differentiation Rules", "unit": "Derivatives"},
            {"id": "calc-008", "name": "Chain Rule", "unit": "Derivatives"},
            {"id": "calc-009", "name": "Implicit Differentiation", "unit": "Derivatives"},
            {"id": "calc-010", "name": "Higher-Order Derivatives", "unit": "Derivatives"},
            {"id": "calc-011", "name": "Related Rates", "unit": "Derivatives"},
            {"id": "calc-012", "name": "Extrema and the First Derivative Test", "unit": "Derivatives"},
            {"id": "calc-013", "name": "Concavity and the Second Derivative Test", "unit": "Derivatives"},
            {"id": "calc-014", "name": "Optimization Problems", "unit": "Derivatives"},
            {"id": "calc-015", "name": "Antiderivatives and Indefinite Integrals", "unit": "Integration"},
            {"id": "calc-016", "name": "Definite Integrals and the FTC", "unit": "Integration"},
            {"id": "calc-017", "name": "Integration by Substitution", "unit": "Integration"},
            {"id": "calc-018", "name": "Area Under a Curve", "unit": "Integration"},
        ]
    },
]

_firestore_db = None


def _get_firestore_db():
    """Initialize Firestore client."""
    global _firestore_db
    if _firestore_db is not None:
        return _firestore_db

    try:
        import firebase_admin
        from firebase_admin import firestore
        if not firebase_admin._apps:
            # Try service account from env or default credentials
            import json
            svc_account = os.getenv("FIREBASE_SERVICE_ACCOUNT_JSON")
            if svc_account:
                sa_creds = json.loads(svc_account)
                firebase_admin.initialize_app(firebase_admin.Certificate(sa_creds))
            else:
                firebase_admin.initialize_app()
        _firestore_db = firestore.client()
        return _firestore_db
    except Exception as e:
        logger.warning(f"Could not initialize Firestore: {e}")
        return None


def get_subjects(grade_level: Optional[str] = None) -> List[Dict[str, Any]]:
    """
    Fetch all subjects from Firestore.
    Falls back to static data if Firestore unavailable.
    Defaults to Grade 11 (SHS) if no grade specified.
    """
    # Default to Grade 11 (SHS) - only serve Grade 11 students for now
    if grade_level is None:
        grade_level = "Grade 11"
    
    db = _get_firestore_db()
    
    if db is not None:
        try:
            subjects_ref = db.collection("subjects")
            if grade_level:
                subjects_ref = subjects_ref.where("gradeLevel", "==", grade_level)
            
            docs = subjects_ref.stream()
            subjects = []
            for doc in docs:
                data = doc.to_dict()
                if data:
                    data["id"] = doc.id
                    subjects.append(data)
            
            if subjects:
                logger.info(f"Loaded {len(subjects)} subjects from Firestore")
                return subjects
        except Exception as e:
            logger.warning(f"Firestore fetch failed, using static data: {e}")
    
    # Static fallback
    if grade_level:
        return [s for s in _STATIC_SUBJECTS if s.get("gradeLevel") == grade_level]
    return list(_STATIC_SUBJECTS)


def get_subject(subject_id: str) -> Optional[Dict[str, Any]]:
    """Fetch a single subject by ID."""
    db = _get_firestore_db()
    
    if db is not None:
        try:
            doc = db.collection("subjects").document(subject_id).get()
            if doc.exists:
                data = doc.to_dict()
                data["id"] = doc.id
                return data
        except Exception as e:
            logger.warning(f"Firestore fetch failed for {subject_id}: {e}")
    
    # Static fallback
    for subject in _STATIC_SUBJECTS:
        if subject["id"] == subject_id:
            return dict(subject)
    return None


def get_topics(subject_id: str) -> List[Dict[str, Any]]:
    """Fetch all topics for a subject."""
    subject = get_subject(subject_id)
    if subject:
        return subject.get("topics", [])
    return []


def get_topic(subject_id: str, topic_id: str) -> Optional[Dict[str, Any]]:
    """Fetch a single topic."""
    topics = get_topics(subject_id)
    for topic in topics:
        if topic["id"] == topic_id:
            return topic
    return None