github-actions[bot] commited on
Commit
34c9b8e
·
1 Parent(s): d68e358

deploy: backend from 154b129

Browse files
main.py CHANGED
@@ -261,7 +261,7 @@ HF_TOKEN = os.environ.get(
261
  os.environ.get("HUGGING_FACE_API_TOKEN", os.environ.get("HUGGINGFACE_API_TOKEN", "")),
262
  ) # Kept for HF Space deployment / dataset push only; AI inference uses DEEPSEEK_API_KEY
263
 
264
- # Grade 11-12 tutoring default model. Can be overridden via INFERENCE_MODEL_ID or INFERENCE_CHAT_MODEL_ID.
265
  HF_MATH_MODEL_ID = os.getenv("INFERENCE_CHAT_MODEL_ID") or os.getenv("INFERENCE_MODEL_ID") or os.getenv("HF_MATH_MODEL_ID", "deepseek-chat")
266
 
267
  # Alias kept so automation_engine.py (which imports CHAT_MODEL) keeps working.
@@ -1589,7 +1589,6 @@ _MATH_SCOPE_KEYWORDS: Set[str] = {
1589
  "calculus",
1590
  "statistics", "statistic",
1591
  "probability",
1592
- "precalculus", "pre-calculus",
1593
  "discrete math", "discrete mathematics",
1594
  "linear algebra",
1595
  "number theory",
@@ -1778,8 +1777,6 @@ _MATH_SCOPE_KEYWORDS: Set[str] = {
1778
  "stem", "stem math",
1779
  "business math",
1780
  "general math",
1781
- "basic calculus",
1782
- "pre-calculus",
1783
  "probability and statistics",
1784
  }
1785
 
@@ -2119,9 +2116,9 @@ def build_math_tutor_prompt(question: str) -> str:
2119
  return f"""SYSTEM:
2120
  You are MathPulse Tutor, a precise and patient math tutor for Filipino senior high school STEM students.
2121
  Your job is to:
2122
- 1) Understand the student's math question (algebra, functions, graphs, trigonometry, analytic geometry, basic calculus, statistics, or word problems).
2123
  2) Solve the problem step by step, explaining each transformation in simple language.
2124
- 3) Show all important equations clearly and avoid skipping algebra steps unless obvious to a Grade 11–12 STEM student.
2125
  4) At the end, restate the final answer explicitly (e.g., "Final answer: x = 3").
2126
  5) If the question is ambiguous or missing information, ask a short clarifying question first instead of guessing.
2127
  6) If the student makes a mistake, point it out gently, explain why it is wrong, and show the correct method.
@@ -3605,7 +3602,7 @@ async def _generate_risk_recommendations_llm(data: EnhancedRiskRequest, result:
3605
  "role": "system",
3606
  "content": (
3607
  "You are a student success specialist. Provide practical, measurable, and age-appropriate "
3608
- "interventions for Grade 11-12 students."
3609
  ),
3610
  },
3611
  {"role": "user", "content": prompt},
@@ -9201,13 +9198,6 @@ DEPD_ED_COMPETENCY_DOMAINS: Dict[str, Dict[str, List[str]]] = {
9201
  "Business Mathematics - Loans and Credit",
9202
  "Business Mathematics - Data Presentation",
9203
  ],
9204
- "Grade 12": [
9205
- "Business Mathematics - Business Reports",
9206
- "Business Mathematics - Financial Analysis",
9207
- "Business Mathematics - Investment Decisions",
9208
- "Business Mathematics - Taxation",
9209
- "Business Mathematics - Asset Depreciation",
9210
- ],
9211
  },
9212
  "STEM": {
9213
  "Grade 11": [
@@ -9227,16 +9217,6 @@ DEPD_ED_COMPETENCY_DOMAINS: Dict[str, Dict[str, List[str]]] = {
9227
  "Statistics - Sampling",
9228
  "Statistics - Hypothesis Testing",
9229
  ],
9230
- "Grade 12": [
9231
- "General Mathematics - Financial Math",
9232
- "General Mathematics - Compound Interest",
9233
- "General Mathematics - Annuities",
9234
- "General Mathematics - Amortization",
9235
- "General Mathematics - Logical Propositions",
9236
- "Statistics - Confidence Intervals",
9237
- "Statistics - Correlation",
9238
- "Statistics - Regression",
9239
- ],
9240
  },
9241
  "HUMSS": {
9242
  "Grade 11": [
@@ -9246,11 +9226,6 @@ DEPD_ED_COMPETENCY_DOMAINS: Dict[str, Dict[str, List[str]]] = {
9246
  "General Mathematics - Data Analysis",
9247
  "General Mathematics - Probability",
9248
  ],
9249
- "Grade 12": [
9250
- "General Mathematics - Financial Math",
9251
- "General Mathematics - Logical Reasoning",
9252
- "Statistics - Statistical Inference",
9253
- ],
9254
  },
9255
  "GAS": {
9256
  "Grade 11": [
@@ -9258,10 +9233,6 @@ DEPD_ED_COMPETENCY_DOMAINS: Dict[str, Dict[str, List[str]]] = {
9258
  "General Mathematics - Functions",
9259
  "General Mathematics - Statistics Basics",
9260
  ],
9261
- "Grade 12": [
9262
- "General Mathematics - Financial Math",
9263
- "General Mathematics - Logical Reasoning",
9264
- ],
9265
  },
9266
  "TVL": {
9267
  "Grade 11": [
@@ -9270,11 +9241,6 @@ DEPD_ED_COMPETENCY_DOMAINS: Dict[str, Dict[str, List[str]]] = {
9270
  "Applied Mathematics - Data Interpretation",
9271
  "Applied Mathematics - Problem Solving",
9272
  ],
9273
- "Grade 12": [
9274
- "Applied Mathematics - Business Math",
9275
- "Applied Mathematics - Consumer Math",
9276
- "Applied Mathematics - Technical Math",
9277
- ],
9278
  },
9279
  }
9280
 
@@ -9307,7 +9273,7 @@ def _csv_escape(value: Any) -> str:
9307
  return '"' + text.replace('"', '""') + '"'
9308
 
9309
 
9310
- # ─── Quiz Topics Database (SHS Grade 11-12 Only) ─────────────
9311
 
9312
  MATH_TOPICS_BY_GRADE: Dict[str, Dict[str, List[str]]] = {
9313
  "Grade 11": {
@@ -9346,37 +9312,6 @@ MATH_TOPICS_BY_GRADE: Dict[str, Dict[str, List[str]]] = {
9346
  "Correlation and Regression",
9347
  ],
9348
  },
9349
- "Grade 12": {
9350
- "Pre-Calculus - Analytic Geometry": [
9351
- "Conic Sections - Parabola", "Conic Sections - Ellipse",
9352
- "Conic Sections - Hyperbola", "Conic Sections - Circle",
9353
- "Systems of Nonlinear Equations",
9354
- ],
9355
- "Pre-Calculus - Series and Induction": [
9356
- "Sequences and Series", "Arithmetic Sequences", "Geometric Sequences",
9357
- "Mathematical Induction", "Binomial Theorem",
9358
- ],
9359
- "Pre-Calculus - Trigonometry": [
9360
- "Angles and Unit Circle", "Trigonometric Functions",
9361
- "Trigonometric Identities", "Sum and Difference Formulas",
9362
- "Inverse Trigonometric Functions", "Polar Coordinates",
9363
- ],
9364
- "Basic Calculus - Limits": [
9365
- "Limits of Functions", "Limit Theorems", "One-Sided Limits",
9366
- "Infinite Limits and Limits at Infinity", "Continuity of Functions",
9367
- ],
9368
- "Basic Calculus - Derivatives": [
9369
- "Definition of the Derivative", "Differentiation Rules", "Chain Rule",
9370
- "Implicit Differentiation", "Higher-Order Derivatives", "Related Rates",
9371
- "Extrema and the First Derivative Test",
9372
- "Concavity and the Second Derivative Test", "Optimization Problems",
9373
- ],
9374
- "Basic Calculus - Integration": [
9375
- "Antiderivatives and Indefinite Integrals",
9376
- "Definite Integrals and the FTC",
9377
- "Integration by Substitution", "Area Under a Curve",
9378
- ],
9379
- },
9380
  }
9381
 
9382
 
@@ -9430,7 +9365,8 @@ def _resolve_grade_level_key(grade_level: Optional[str]) -> Optional[str]:
9430
  if normalized in {"11", "grade11", "grade 11", "g11"}:
9431
  return "Grade 11"
9432
  if normalized in {"12", "grade12", "grade 12", "g12"}:
9433
- return "Grade 12"
 
9434
 
9435
  for key in MATH_TOPICS_BY_GRADE.keys():
9436
  if key.lower() == normalized:
@@ -11742,7 +11678,7 @@ async def get_hf_monitoring(http_request: Request):
11742
  async def get_quiz_topics(response: Response, gradeLevel: Optional[str] = None):
11743
  """
11744
  Return structured list of SHS math topics organised by grade level.
11745
- Only Grade 11 and Grade 12 are supported.
11746
  If gradeLevel is provided, return topics for that grade only.
11747
  """
11748
  response.headers["Cache-Control"] = "public, max-age=300, stale-while-revalidate=900"
@@ -12822,50 +12758,6 @@ _SHS_TOPICS = {
12822
  ("Correlation and Regression", "Correlation and Regression"),
12823
  ],
12824
  },
12825
- "pre-calc": {
12826
- "name": "Pre-Calculus",
12827
- "topics": [
12828
- ("Conic Sections - Parabola", "Analytic Geometry"),
12829
- ("Conic Sections - Ellipse", "Analytic Geometry"),
12830
- ("Conic Sections - Hyperbola", "Analytic Geometry"),
12831
- ("Conic Sections - Circle", "Analytic Geometry"),
12832
- ("Systems of Nonlinear Equations", "Analytic Geometry"),
12833
- ("Sequences and Series", "Series and Induction"),
12834
- ("Arithmetic Sequences", "Series and Induction"),
12835
- ("Geometric Sequences", "Series and Induction"),
12836
- ("Mathematical Induction", "Series and Induction"),
12837
- ("Binomial Theorem", "Series and Induction"),
12838
- ("Angles and Unit Circle", "Trigonometry"),
12839
- ("Trigonometric Functions", "Trigonometry"),
12840
- ("Trigonometric Identities", "Trigonometry"),
12841
- ("Sum and Difference Formulas", "Trigonometry"),
12842
- ("Inverse Trigonometric Functions", "Trigonometry"),
12843
- ("Polar Coordinates", "Trigonometry"),
12844
- ],
12845
- },
12846
- "basic-calc": {
12847
- "name": "Basic Calculus",
12848
- "topics": [
12849
- ("Limits of Functions", "Limits"),
12850
- ("Limit Theorems", "Limits"),
12851
- ("One-Sided Limits", "Limits"),
12852
- ("Infinite Limits and Limits at Infinity", "Limits"),
12853
- ("Continuity of Functions", "Limits"),
12854
- ("Definition of the Derivative", "Derivatives"),
12855
- ("Differentiation Rules", "Derivatives"),
12856
- ("Chain Rule", "Derivatives"),
12857
- ("Implicit Differentiation", "Derivatives"),
12858
- ("Higher-Order Derivatives", "Derivatives"),
12859
- ("Related Rates", "Derivatives"),
12860
- ("Extrema and the First Derivative Test", "Derivatives"),
12861
- ("Concavity and the Second Derivative Test", "Derivatives"),
12862
- ("Optimization Problems", "Derivatives"),
12863
- ("Antiderivatives and Indefinite Integrals", "Integration"),
12864
- ("Definite Integrals and the FTC", "Integration"),
12865
- ("Integration by Substitution", "Integration"),
12866
- ("Area Under a Curve", "Integration"),
12867
- ],
12868
- },
12869
  }
12870
 
12871
 
 
261
  os.environ.get("HUGGING_FACE_API_TOKEN", os.environ.get("HUGGINGFACE_API_TOKEN", "")),
262
  ) # Kept for HF Space deployment / dataset push only; AI inference uses DEEPSEEK_API_KEY
263
 
264
+ # Grade 11 tutoring default model. Can be overridden via INFERENCE_MODEL_ID or INFERENCE_CHAT_MODEL_ID.
265
  HF_MATH_MODEL_ID = os.getenv("INFERENCE_CHAT_MODEL_ID") or os.getenv("INFERENCE_MODEL_ID") or os.getenv("HF_MATH_MODEL_ID", "deepseek-chat")
266
 
267
  # Alias kept so automation_engine.py (which imports CHAT_MODEL) keeps working.
 
1589
  "calculus",
1590
  "statistics", "statistic",
1591
  "probability",
 
1592
  "discrete math", "discrete mathematics",
1593
  "linear algebra",
1594
  "number theory",
 
1777
  "stem", "stem math",
1778
  "business math",
1779
  "general math",
 
 
1780
  "probability and statistics",
1781
  }
1782
 
 
2116
  return f"""SYSTEM:
2117
  You are MathPulse Tutor, a precise and patient math tutor for Filipino senior high school STEM students.
2118
  Your job is to:
2119
+ 1) Understand the student's math question (algebra, functions, graphs, business mathematics, statistics and probability, or word problems).
2120
  2) Solve the problem step by step, explaining each transformation in simple language.
2121
+ 3) show all important equations clearly and avoid skipping algebra steps unless obvious to a Grade 11 STEM student.
2122
  4) At the end, restate the final answer explicitly (e.g., "Final answer: x = 3").
2123
  5) If the question is ambiguous or missing information, ask a short clarifying question first instead of guessing.
2124
  6) If the student makes a mistake, point it out gently, explain why it is wrong, and show the correct method.
 
3602
  "role": "system",
3603
  "content": (
3604
  "You are a student success specialist. Provide practical, measurable, and age-appropriate "
3605
+ "interventions for Grade 11 students."
3606
  ),
3607
  },
3608
  {"role": "user", "content": prompt},
 
9198
  "Business Mathematics - Loans and Credit",
9199
  "Business Mathematics - Data Presentation",
9200
  ],
 
 
 
 
 
 
 
9201
  },
9202
  "STEM": {
9203
  "Grade 11": [
 
9217
  "Statistics - Sampling",
9218
  "Statistics - Hypothesis Testing",
9219
  ],
 
 
 
 
 
 
 
 
 
 
9220
  },
9221
  "HUMSS": {
9222
  "Grade 11": [
 
9226
  "General Mathematics - Data Analysis",
9227
  "General Mathematics - Probability",
9228
  ],
 
 
 
 
 
9229
  },
9230
  "GAS": {
9231
  "Grade 11": [
 
9233
  "General Mathematics - Functions",
9234
  "General Mathematics - Statistics Basics",
9235
  ],
 
 
 
 
9236
  },
9237
  "TVL": {
9238
  "Grade 11": [
 
9241
  "Applied Mathematics - Data Interpretation",
9242
  "Applied Mathematics - Problem Solving",
9243
  ],
 
 
 
 
 
9244
  },
9245
  }
9246
 
 
9273
  return '"' + text.replace('"', '""') + '"'
9274
 
9275
 
9276
+ # ─── Quiz Topics Database (SHS Grade 11 Only) ─────────────
9277
 
9278
  MATH_TOPICS_BY_GRADE: Dict[str, Dict[str, List[str]]] = {
9279
  "Grade 11": {
 
9312
  "Correlation and Regression",
9313
  ],
9314
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9315
  }
9316
 
9317
 
 
9365
  if normalized in {"11", "grade11", "grade 11", "g11"}:
9366
  return "Grade 11"
9367
  if normalized in {"12", "grade12", "grade 12", "g12"}:
9368
+ # LEGACY-READ: stored Grade 12 records normalize into the Grade 11 pool (Grade-11-only system).
9369
+ return "Grade 11"
9370
 
9371
  for key in MATH_TOPICS_BY_GRADE.keys():
9372
  if key.lower() == normalized:
 
11678
  async def get_quiz_topics(response: Response, gradeLevel: Optional[str] = None):
11679
  """
11680
  Return structured list of SHS math topics organised by grade level.
11681
+ Only Grade 11 is supported.
11682
  If gradeLevel is provided, return topics for that grade only.
11683
  """
11684
  response.headers["Cache-Control"] = "public, max-age=300, stale-while-revalidate=900"
 
12758
  ("Correlation and Regression", "Correlation and Regression"),
12759
  ],
12760
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12761
  }
12762
 
12763
 
rag/curriculum_rag.py CHANGED
@@ -396,7 +396,7 @@ def build_lesson_prompt(
396
  organized = organize_chunks_by_section(curriculum_chunks)
397
 
398
  return (
399
- "You are a DepEd-aligned Grade 11-12 mathematics instructional designer.\n"
400
  "Generate a lesson in JSON format. Use ONLY the retrieved curriculum evidence below.\n"
401
  "Do NOT invent content. Do NOT add generic motivational text. All content must be grounded in the retrieved excerpts.\n\n"
402
  f"Lesson title: {lesson_title}\n"
@@ -405,7 +405,7 @@ def build_lesson_prompt(
405
  f"Grade level: {grade_level}\n"
406
  f"Subject: {subject}\n"
407
  f"Quarter: Q{quarter}\n"
408
- f"Learner level: {learner_level or 'Grade 11-12'}\n"
409
  f"Module/unit: {module_unit or 'n/a'}\n\n"
410
  "[CURRICULUM CONTEXT]\n"
411
  f"{refs_text}\n\n"
 
396
  organized = organize_chunks_by_section(curriculum_chunks)
397
 
398
  return (
399
+ "You are a DepEd-aligned Grade 11 mathematics instructional designer.\n"
400
  "Generate a lesson in JSON format. Use ONLY the retrieved curriculum evidence below.\n"
401
  "Do NOT invent content. Do NOT add generic motivational text. All content must be grounded in the retrieved excerpts.\n\n"
402
  f"Lesson title: {lesson_title}\n"
 
405
  f"Grade level: {grade_level}\n"
406
  f"Subject: {subject}\n"
407
  f"Quarter: Q{quarter}\n"
408
+ f"Learner level: {learner_level or 'Grade 11'}\n"
409
  f"Module/unit: {module_unit or 'n/a'}\n\n"
410
  "[CURRICULUM CONTEXT]\n"
411
  f"{refs_text}\n\n"
rag/pdf_ingestion.py CHANGED
@@ -246,7 +246,7 @@ async def ingest_pdf(
246
 
247
  Args:
248
  storage_path: Path to PDF in Firebase Storage (e.g., "rag-pdfs/filename.pdf")
249
- grade_level: Grade level (11 or 12)
250
  topic: Topic identifier for the questions
251
  force_reingest: If True, reprocess even if already processed
252
 
 
246
 
247
  Args:
248
  storage_path: Path to PDF in Firebase Storage (e.g., "rag-pdfs/filename.pdf")
249
+ grade_level: Grade level (Grade 11 only; legacy 12 normalizes to 11)
250
  topic: Topic identifier for the questions
251
  force_reingest: If True, reprocess even if already processed
252
 
routes/curriculum_routes.py CHANGED
@@ -44,14 +44,14 @@ class TopicResponse(BaseModel):
44
 
45
 
46
  @router.get("/subjects", response_model=list[SubjectResponse])
47
- async def list_subjects(grade_level: Optional[str] = Query(None, description="Filter by grade level (e.g., 'Grade 11', 'Grade 12')")):
48
  """List all curriculum subjects, optionally filtered by grade level."""
49
  svc = _get_curriculum_service()
50
  if svc is None:
51
  raise HTTPException(status_code=503, detail="Curriculum service unavailable")
52
  _, get_subjects_fn, _, _ = svc
53
  # Guard against malformed grade levels (e.g., "Grade 2011" from bad state)
54
- valid_grades = {"Grade 11", "Grade 12", "Grade 11/12"}
55
  if grade_level and grade_level not in valid_grades:
56
  logger.warning(f"[curriculum] Invalid grade_level received: {grade_level!r}, defaulting to Grade 11")
57
  grade_level = "Grade 11"
 
44
 
45
 
46
  @router.get("/subjects", response_model=list[SubjectResponse])
47
+ async def list_subjects(grade_level: Optional[str] = Query(None, description="Filter by grade level (only 'Grade 11' is supported)")):
48
  """List all curriculum subjects, optionally filtered by grade level."""
49
  svc = _get_curriculum_service()
50
  if svc is None:
51
  raise HTTPException(status_code=503, detail="Curriculum service unavailable")
52
  _, get_subjects_fn, _, _ = svc
53
  # Guard against malformed grade levels (e.g., "Grade 2011" from bad state)
54
+ valid_grades = {"Grade 11"}
55
  if grade_level and grade_level not in valid_grades:
56
  logger.warning(f"[curriculum] Invalid grade_level received: {grade_level!r}, defaulting to Grade 11")
57
  grade_level = "Grade 11"
routes/deepseek_rag_routes.py CHANGED
@@ -177,7 +177,7 @@ async def generate_module_preview(req: ModulePreviewRequest):
177
  band = confidence_info.get("band", "low")
178
 
179
  system_prompt = (
180
- "You are a DepEd K-12 SHS math educator writing for Grade 11-12 Filipino students. "
181
  "Generate content ONLY from the retrieved DepEd curriculum excerpts provided. "
182
  "Do NOT add generic filler. Do NOT invent examples or definitions not present "
183
  "in the retrieved context."
 
177
  band = confidence_info.get("band", "low")
178
 
179
  system_prompt = (
180
+ "You are a DepEd K-12 SHS math educator writing for Grade 11 Filipino students. "
181
  "Generate content ONLY from the retrieved DepEd curriculum excerpts provided. "
182
  "Do NOT add generic filler. Do NOT invent examples or definitions not present "
183
  "in the retrieved context."
routes/diagnostic.py CHANGED
@@ -37,7 +37,7 @@ _in_memory_sessions: Dict[str, Dict[str, Any]] = defaultdict(dict)
37
 
38
  class DiagnosticGenerateRequest(BaseModel):
39
  strand: str = Field(..., description="Student strand: ABM, STEM, HUMSS, GAS, TVL")
40
- grade_level: str = Field(..., description="Grade level: Grade 11 or Grade 12")
41
 
42
 
43
  class DiagnosticOption(BaseModel):
 
37
 
38
  class DiagnosticGenerateRequest(BaseModel):
39
  strand: str = Field(..., description="Student strand: ABM, STEM, HUMSS, GAS, TVL")
40
+ grade_level: str = Field(..., description="Grade level: Grade 11")
41
 
42
 
43
  class DiagnosticOption(BaseModel):
routes/quiz_generation_routes.py CHANGED
@@ -95,7 +95,7 @@ def _build_quiz_generation_prompt(
95
  retrieved_context: str,
96
  variance_seed: Optional[int] = None,
97
  competency_code: Optional[str] = None,
98
- grade_level: str = "Grade 11/12",
99
  lesson_objective: Optional[str] = None,
100
  xp_reward: int = 10,
101
  points: int = 1,
@@ -390,7 +390,7 @@ async def generate_quiz(request: QuizGenerationRequest):
390
  retrieved_context=formatted_context,
391
  variance_seed=request.varianceSeed,
392
  competency_code=request.competencyCode,
393
- grade_level="Grade 11/12",
394
  lesson_objective=request.topic,
395
  xp_reward=10,
396
  points=1,
 
95
  retrieved_context: str,
96
  variance_seed: Optional[int] = None,
97
  competency_code: Optional[str] = None,
98
+ grade_level: str = "Grade 11",
99
  lesson_objective: Optional[str] = None,
100
  xp_reward: int = 10,
101
  points: int = 1,
 
390
  retrieved_context=formatted_context,
391
  variance_seed=request.varianceSeed,
392
  competency_code=request.competencyCode,
393
+ grade_level="Grade 11",
394
  lesson_objective=request.topic,
395
  xp_reward=10,
396
  points=1,
routes/rag_routes.py CHANGED
@@ -9,7 +9,7 @@ from threading import Lock
9
  from typing import Any, Dict, List, Optional
10
 
11
  from fastapi import APIRouter, HTTPException, Request
12
- from pydantic import BaseModel, Field
13
 
14
  from services.inference_client import (
15
  InferenceRequest,
@@ -132,6 +132,17 @@ class RagLessonRequest(BaseModel):
132
  competencyCode: Optional[str] = None
133
  storagePath: Optional[str] = None
134
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  class RagProblemRequest(BaseModel):
137
  topic: str
@@ -316,7 +327,7 @@ async def rag_lesson(request: Request, payload: RagLessonRequest):
316
  prompt = build_lesson_prompt(
317
  lesson_title=payload.lessonTitle or payload.topic,
318
  competency=payload.learningCompetency or payload.topic,
319
- grade_level="Grade 11-12",
320
  subject=payload.subject,
321
  quarter=payload.quarter,
322
  learner_level=payload.learnerLevel,
 
9
  from typing import Any, Dict, List, Optional
10
 
11
  from fastapi import APIRouter, HTTPException, Request
12
+ from pydantic import BaseModel, Field, field_validator
13
 
14
  from services.inference_client import (
15
  InferenceRequest,
 
132
  competencyCode: Optional[str] = None
133
  storagePath: Optional[str] = None
134
 
135
+ @field_validator("quarter", mode="before")
136
+ @classmethod
137
+ def _coerce_quarter(cls, value: Any) -> Any:
138
+ # Grade-11-only: lessons may carry quarter as "Q1" string; RAG needs int 1-4.
139
+ if isinstance(value, int) and not isinstance(value, bool):
140
+ return value
141
+ match = re.search(r"[1-4]", str(value or ""))
142
+ if match:
143
+ return int(match.group(0))
144
+ raise ValueError("quarter must be 1-4 (accepts 'Q1'-style strings)")
145
+
146
 
147
  class RagProblemRequest(BaseModel):
148
  topic: str
 
327
  prompt = build_lesson_prompt(
328
  lesson_title=payload.lessonTitle or payload.topic,
329
  competency=payload.learningCompetency or payload.topic,
330
+ grade_level="Grade 11",
331
  subject=payload.subject,
332
  quarter=payload.quarter,
333
  learner_level=payload.learnerLevel,
scripts/migrate_grade12_to_grade11.py CHANGED
@@ -1,5 +1,9 @@
1
  """
2
- Migrate Grade 12 users to Grade 11.
 
 
 
 
3
 
4
  Run this to convert all existing Grade 12 users to Grade 11:
5
  python backend/scripts/migrate_grade12_to_grade11.py
 
1
  """
2
+ DEPRECATED / LEGACY one-shot migration: Grade 12 users -> Grade 11.
3
+
4
+ The system is now Grade-11-only. This script is kept for history and for
5
+ any lingering stored Firestore records; do not treat Grade 12 as a
6
+ supported grade.
7
 
8
  Run this to convert all existing Grade 12 users to Grade 11:
9
  python backend/scripts/migrate_grade12_to_grade11.py
scripts/upload_lesson_modules.py CHANGED
@@ -16,19 +16,8 @@ from pypdf import PdfWriter, PdfReader
16
  LOCAL_MODULES_DIR = Path(__file__).resolve().parents[1].parent / "datasets" / "lesson_modules"
17
  FIREBASE_STORAGE_BUCKET = "mathpulse-ai-2026.firebasestorage.app"
18
 
19
- # Upload plan
20
  UPLOAD_JOBS = [
21
- {
22
- "id": "basic-calc-q3",
23
- "display_name": "Basic Calculus Q3",
24
- "subject": "Basic Calculus",
25
- "subjectId": "basic-calc",
26
- "quarter": 3,
27
- "storage_path": "curriculum/basic_calc/SDO_Navotas_BasicCalc_SHS_Q3.FV.pdf",
28
- "local_dir": LOCAL_MODULES_DIR / "basic_calculus_q3",
29
- "filename": "Basic Calculus-Q3-Module-{n}.pdf",
30
- "modules": list(range(1, 9)), # Modules 1-8
31
- },
32
  {
33
  "id": "gen-math-q2",
34
  "display_name": "General Mathematics Q2",
@@ -52,18 +41,15 @@ def merge_pdfs(job: dict) -> Path | None:
52
  writer = PdfWriter()
53
 
54
  for mod_num in job["modules"]:
55
- if job["id"] == "basic-calc-q3":
56
- fname = job["filename"].format(n=mod_num)
57
- else:
58
- # GenMath modules have specific naming
59
- fname = None
60
- pattern = job["filename"].format(n=mod_num)
61
- for f in job["local_dir"].glob(pattern):
62
- fname = f.name
63
- break
64
- if fname is None:
65
- print(f" [WARN] Could not find file for module {mod_num}")
66
- continue
67
 
68
  src_path = job["local_dir"] / fname
69
  if not src_path.exists():
 
16
  LOCAL_MODULES_DIR = Path(__file__).resolve().parents[1].parent / "datasets" / "lesson_modules"
17
  FIREBASE_STORAGE_BUCKET = "mathpulse-ai-2026.firebasestorage.app"
18
 
19
+ # Grade-11-only upload plan (legacy Basic Calculus job removed).
20
  UPLOAD_JOBS = [
 
 
 
 
 
 
 
 
 
 
 
21
  {
22
  "id": "gen-math-q2",
23
  "display_name": "General Mathematics Q2",
 
41
  writer = PdfWriter()
42
 
43
  for mod_num in job["modules"]:
44
+ # GenMath modules have specific naming
45
+ fname = None
46
+ pattern = job["filename"].format(n=mod_num)
47
+ for f in job["local_dir"].glob(pattern):
48
+ fname = f.name
49
+ break
50
+ if fname is None:
51
+ print(f" [WARN] Could not find file for module {mod_num}")
52
+ continue
 
 
 
53
 
54
  src_path = job["local_dir"] / fname
55
  if not src_path.exists():
services/curriculum_service.py CHANGED
@@ -70,62 +70,6 @@ _STATIC_SUBJECTS = [
70
  {"id": "stat-014", "name": "Correlation and Regression", "unit": "Correlation and Regression"},
71
  ]
72
  },
73
- {
74
- "id": "pre-calc",
75
- "code": "PRE-CALC",
76
- "name": "Pre-Calculus",
77
- "gradeLevel": "Grade 12",
78
- "semester": "1st Semester",
79
- "color": "from-orange-500 to-red-500",
80
- "pdfAvailable": False,
81
- "topics": [
82
- {"id": "pre-calc-001", "name": "Conic Sections - Parabola", "unit": "Analytic Geometry"},
83
- {"id": "pre-calc-002", "name": "Conic Sections - Ellipse", "unit": "Analytic Geometry"},
84
- {"id": "pre-calc-003", "name": "Conic Sections - Hyperbola", "unit": "Analytic Geometry"},
85
- {"id": "pre-calc-004", "name": "Conic Sections - Circle", "unit": "Analytic Geometry"},
86
- {"id": "pre-calc-005", "name": "Systems of Nonlinear Equations", "unit": "Analytic Geometry"},
87
- {"id": "pre-calc-006", "name": "Sequences and Series", "unit": "Series and Induction"},
88
- {"id": "pre-calc-007", "name": "Arithmetic Sequences", "unit": "Series and Induction"},
89
- {"id": "pre-calc-008", "name": "Geometric Sequences", "unit": "Series and Induction"},
90
- {"id": "pre-calc-009", "name": "Mathematical Induction", "unit": "Series and Induction"},
91
- {"id": "pre-calc-010", "name": "Binomial Theorem", "unit": "Series and Induction"},
92
- {"id": "pre-calc-011", "name": "Angles and Unit Circle", "unit": "Trigonometry"},
93
- {"id": "pre-calc-012", "name": "Trigonometric Functions", "unit": "Trigonometry"},
94
- {"id": "pre-calc-013", "name": "Trigonometric Identities", "unit": "Trigonometry"},
95
- {"id": "pre-calc-014", "name": "Sum and Difference Formulas", "unit": "Trigonometry"},
96
- {"id": "pre-calc-015", "name": "Inverse Trigonometric Functions", "unit": "Trigonometry"},
97
- {"id": "pre-calc-016", "name": "Polar Coordinates", "unit": "Trigonometry"},
98
- ]
99
- },
100
- {
101
- "id": "basic-calc",
102
- "code": "BASIC CALC",
103
- "name": "Basic Calculus",
104
- "gradeLevel": "Grade 12",
105
- "semester": "2nd Semester",
106
- "color": "from-green-500 to-teal-500",
107
- "pdfAvailable": True,
108
- "topics": [
109
- {"id": "calc-001", "name": "Limits of Functions", "unit": "Limits"},
110
- {"id": "calc-002", "name": "Limit Theorems", "unit": "Limits"},
111
- {"id": "calc-003", "name": "One-Sided Limits", "unit": "Limits"},
112
- {"id": "calc-004", "name": "Infinite Limits and Limits at Infinity", "unit": "Limits"},
113
- {"id": "calc-005", "name": "Continuity of Functions", "unit": "Limits"},
114
- {"id": "calc-006", "name": "Definition of the Derivative", "unit": "Derivatives"},
115
- {"id": "calc-007", "name": "Differentiation Rules", "unit": "Derivatives"},
116
- {"id": "calc-008", "name": "Chain Rule", "unit": "Derivatives"},
117
- {"id": "calc-009", "name": "Implicit Differentiation", "unit": "Derivatives"},
118
- {"id": "calc-010", "name": "Higher-Order Derivatives", "unit": "Derivatives"},
119
- {"id": "calc-011", "name": "Related Rates", "unit": "Derivatives"},
120
- {"id": "calc-012", "name": "Extrema and the First Derivative Test", "unit": "Derivatives"},
121
- {"id": "calc-013", "name": "Concavity and the Second Derivative Test", "unit": "Derivatives"},
122
- {"id": "calc-014", "name": "Optimization Problems", "unit": "Derivatives"},
123
- {"id": "calc-015", "name": "Antiderivatives and Indefinite Integrals", "unit": "Integration"},
124
- {"id": "calc-016", "name": "Definite Integrals and the FTC", "unit": "Integration"},
125
- {"id": "calc-017", "name": "Integration by Substitution", "unit": "Integration"},
126
- {"id": "calc-018", "name": "Area Under a Curve", "unit": "Integration"},
127
- ]
128
- },
129
  ]
130
 
131
  _firestore_db = None
 
70
  {"id": "stat-014", "name": "Correlation and Regression", "unit": "Correlation and Regression"},
71
  ]
72
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  ]
74
 
75
  _firestore_db = None
services/youtube_service.py CHANGED
@@ -35,9 +35,9 @@ _EDUCATIONAL_CHANNEL_KEYWORDS = [
35
  "mathbff", "krista king", "mathMeeting", "mathbyfives", "yourteacher",
36
  "virtual nerd", "study.com", "coursera", "edx", "brilliant",
37
  "filipino math", "tagalog math", "pinoy teacher", "math philippines",
38
- "shs math", "senior high school math", "grade 11 math", "grade 12 math",
39
  "general mathematics", "business math", "statistics", "probability",
40
- "finite math", "precalculus", "calculus", "algebra", "geometry",
41
  "trigonometry", "functions", "equations", "problem solving",
42
  ]
43
 
@@ -447,7 +447,7 @@ def _generate_search_queries_with_ai(
447
  f"Topic: {topic}\n"
448
  f"Subject: {subject}\n"
449
  f"Context: {lesson_context or 'General mathematics lesson'}\n"
450
- f"Grade: {grade_level or 'Grade 11-12'}\n\n"
451
  f"Generate exactly 4 YouTube search queries that would find the most relevant educational videos.\n"
452
  f"Rules:\n"
453
  f"1. Query 1: Most specific - exact topic with 'tutorial' or 'lesson'\n"
 
35
  "mathbff", "krista king", "mathMeeting", "mathbyfives", "yourteacher",
36
  "virtual nerd", "study.com", "coursera", "edx", "brilliant",
37
  "filipino math", "tagalog math", "pinoy teacher", "math philippines",
38
+ "shs math", "senior high school math", "grade 11 math",
39
  "general mathematics", "business math", "statistics", "probability",
40
+ "finite math", "algebra", "geometry",
41
  "trigonometry", "functions", "equations", "problem solving",
42
  ]
43
 
 
447
  f"Topic: {topic}\n"
448
  f"Subject: {subject}\n"
449
  f"Context: {lesson_context or 'General mathematics lesson'}\n"
450
+ f"Grade: {grade_level or 'Grade 11'}\n\n"
451
  f"Generate exactly 4 YouTube search queries that would find the most relevant educational videos.\n"
452
  f"Rules:\n"
453
  f"1. Query 1: Most specific - exact topic with 'tutorial' or 'lesson'\n"
test_full_rag.py CHANGED
@@ -35,10 +35,10 @@ try:
35
  prompt = build_lesson_prompt(
36
  lesson_title="Represent real-life relationships as functions and interpret domain/range.",
37
  competency="Represent real-life relationships as functions and interpret domain/range.",
38
- grade_level="Grade 11-12",
39
  subject="General Mathematics",
40
  quarter=2,
41
- learner_level="Grade 11-12",
42
  module_unit="n/a",
43
  curriculum_chunks=chunks,
44
  competency_code="GM11-FG-1",
 
35
  prompt = build_lesson_prompt(
36
  lesson_title="Represent real-life relationships as functions and interpret domain/range.",
37
  competency="Represent real-life relationships as functions and interpret domain/range.",
38
+ grade_level="Grade 11",
39
  subject="General Mathematics",
40
  quarter=2,
41
+ learner_level="Grade 11",
42
  module_unit="n/a",
43
  curriculum_chunks=chunks,
44
  competency_code="GM11-FG-1",
tests/test_rag_pipeline.py CHANGED
@@ -61,7 +61,7 @@ class TestBuildLessonPrompt:
61
  prompt = build_lesson_prompt(
62
  lesson_title="Compound Interest",
63
  competency="M11GM-IIc-1",
64
- grade_level="Grade 11-12",
65
  subject="General Mathematics",
66
  quarter=3,
67
  learner_level="mixed",
@@ -87,7 +87,7 @@ class TestBuildLessonPrompt:
87
  prompt = build_lesson_prompt(
88
  lesson_title="Functions",
89
  competency="M11GM-Ia-1",
90
- grade_level="Grade 11-12",
91
  subject="General Mathematics",
92
  quarter=1,
93
  learner_level=None,
 
61
  prompt = build_lesson_prompt(
62
  lesson_title="Compound Interest",
63
  competency="M11GM-IIc-1",
64
+ grade_level="Grade 11",
65
  subject="General Mathematics",
66
  quarter=3,
67
  learner_level="mixed",
 
87
  prompt = build_lesson_prompt(
88
  lesson_title="Functions",
89
  competency="M11GM-Ia-1",
90
+ grade_level="Grade 11",
91
  subject="General Mathematics",
92
  quarter=1,
93
  learner_level=None,