Spaces:
Running
Running
Download modules/lessons/loader.py from Into-The-Eye/refined-backend: direct link, hf CLI and curl.
- Browser
- Download file 4.79 kB
-
https://huggingface.co/spaces/Into-The-Eye/refined-backend/resolve/main/modules/lessons/loader.py
- Command line
-
hf download hf://spaces/Into-The-Eye/refined-backend/modules/lessons/loader.py
-
curl -L -o loader.py https://huggingface.co/spaces/Into-The-Eye/refined-backend/resolve/main/modules/lessons/loader.py
4.79 kB
| """ | |
| Lessons Loader Module - Load and search lesson content | |
| Lessons are stored as JSON in the data/ directory. | |
| """ | |
| import json | |
| import os | |
| from typing import Dict, List, Optional, Any | |
| from pathlib import Path | |
| # Cache for loaded lessons | |
| _lessons_cache: Optional[List[Dict[str, Any]]] = None | |
| def _load_lessons() -> List[Dict[str, Any]]: | |
| """Load lessons from JSON file.""" | |
| global _lessons_cache | |
| if _lessons_cache is not None: | |
| return _lessons_cache | |
| data_path = Path(__file__).parent.parent.parent / "data" / "lessons.json" | |
| if not data_path.exists(): | |
| # Return empty list if no lessons file yet | |
| return [] | |
| with open(data_path, "r", encoding="utf-8") as f: | |
| _lessons_cache = json.load(f) | |
| return _lessons_cache | |
| def get_all_lessons() -> List[Dict[str, Any]]: | |
| """Get all lessons (metadata only).""" | |
| lessons = _load_lessons() | |
| # Return metadata only (no full content) | |
| return [ | |
| { | |
| "id": l.get("id"), | |
| "slug": l.get("slug"), | |
| "title": l.get("title"), | |
| "description": l.get("description"), | |
| "difficulty": l.get("difficulty"), | |
| "isPro": l.get("isPro", False) | |
| } | |
| for l in lessons | |
| ] | |
| def get_lesson_by_id(lesson_id: str) -> Optional[Dict[str, Any]]: | |
| """Get a lesson by its numeric ID.""" | |
| lessons = _load_lessons() | |
| for lesson in lessons: | |
| if str(lesson.get("id")) == str(lesson_id): | |
| return lesson | |
| return None | |
| def get_lesson_by_slug(slug: str) -> Optional[Dict[str, Any]]: | |
| """Get a lesson by its slug.""" | |
| lessons = _load_lessons() | |
| for lesson in lessons: | |
| if lesson.get("slug") == slug: | |
| return lesson | |
| return None | |
| def search_lessons(query: str) -> List[Dict[str, Any]]: | |
| """Search lessons by title, description, or content using keyword scoring.""" | |
| lessons = _load_lessons() | |
| # improved tokenization | |
| stop_words = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is", "are", "was", "were", "what", "how", "why", "who", "does", "do", "can"} | |
| query_tokens = {word.lower() for word in query.split() if word.lower() not in stop_words and len(word) > 2} | |
| if not query_tokens: | |
| return [] | |
| results = [] | |
| for lesson in lessons: | |
| score = 0 | |
| lesson_title = lesson.get("title", "").lower() | |
| lesson_desc = lesson.get("description", "").lower() | |
| # Check Title (High weight per token) | |
| for token in query_tokens: | |
| if token in lesson_title: | |
| score += 10 | |
| # Check Description | |
| for token in query_tokens: | |
| if token in lesson_desc: | |
| score += 5 | |
| # Check Sections | |
| for section in lesson.get("sections", []): | |
| section_title = section.get("title", "").lower() | |
| # FIX: Check 'body' as well as 'content' (legacy vs new) | |
| section_text = (section.get("body", "") + " " + section.get("content", "")).lower() | |
| for token in query_tokens: | |
| if token in section_title: | |
| score += 3 | |
| if token in section_text: | |
| score += 1 | |
| # Check Takeaways | |
| for takeaway in lesson.get("takeaways", []): | |
| takeaway_lower = takeaway.lower() | |
| for token in query_tokens: | |
| if token in takeaway_lower: | |
| score += 2 | |
| if score > 0: | |
| results.append({ | |
| "id": lesson.get("id"), | |
| "slug": lesson.get("slug"), | |
| "title": lesson.get("title"), | |
| "description": lesson.get("description"), | |
| "difficulty": lesson.get("difficulty"), | |
| "score": score, | |
| # Return snippets if needed? For now just metadata is enough context | |
| # The prompt will ask for full content if needed or we can return it here? | |
| # The 'tools.py' function calls get_lesson_by_id typically? | |
| # No, searchLessonContent usually returns snippets or full content? | |
| # Let's verify what `tools.py` does. | |
| # Just return metadata + validation score for now. | |
| "sections": lesson.get("sections", []) if score > 10 else [] # Include content if high match? | |
| }) | |
| # Sort by relevance score | |
| results.sort(key=lambda x: x["score"], reverse=True) | |
| # If we have high confidence matches, filter out low noise | |
| if results and results[0]["score"] > 20: | |
| results = [r for r in results if r["score"] > 5] | |
| return results[:5] # Return top 5 results | |