Spaces:
Sleeping
Sleeping
βbotaylaβ commited on
Commit Β·
5a604fc
1
Parent(s): f60fad6
update
Browse files
src/recommendation/recommender.py
CHANGED
|
@@ -49,22 +49,29 @@ class RecommendationService:
|
|
| 49 |
# Step 2: keywords from latest note per category
|
| 50 |
# ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
def _latest_note_per_category(
|
| 52 |
-
self, notes: List[Dict], categories: List[str]
|
| 53 |
-
) -> Dict[str, Dict]:
|
| 54 |
"""
|
| 55 |
-
|
| 56 |
-
Notes are pre-fetched so no extra DB calls needed.
|
| 57 |
"""
|
| 58 |
-
|
|
|
|
|
|
|
| 59 |
for note in notes:
|
| 60 |
cat = note.get("category")
|
| 61 |
cats = cat if isinstance(cat, list) else [cat] if cat else []
|
| 62 |
ts = note.get("createdAt", 0)
|
|
|
|
| 63 |
for c in cats:
|
| 64 |
if c in categories:
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
async def _extract_keywords_with_claude(
|
| 70 |
self, note: Dict, category: str
|
|
@@ -213,23 +220,39 @@ class RecommendationService:
|
|
| 213 |
# ββ Step 2: keywords via Claude ββββββββββ
|
| 214 |
latest_notes = self._latest_note_per_category(notes, top_categories)
|
| 215 |
|
| 216 |
-
keyword_tasks = [
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
if cat in latest_notes
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
keyword_results = await asyncio.gather(*keyword_tasks)
|
| 222 |
|
| 223 |
-
category_keywords
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
|
| 228 |
# ββ Step 3: build query & recommend ββββββ
|
| 229 |
all_videos = []
|
| 230 |
|
| 231 |
for category, keywords in category_keywords.items():
|
| 232 |
-
query = " ".join(keywords[:3])
|
| 233 |
|
| 234 |
logger.info(f"π― Searching category: {category} | Query: {query}")
|
| 235 |
|
|
|
|
| 49 |
# Step 2: keywords from latest note per category
|
| 50 |
# ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
def _latest_note_per_category(
|
| 52 |
+
self, notes: List[Dict], categories: List[str], top_k: int = 3
|
| 53 |
+
) -> Dict[str, List[Dict]]:
|
| 54 |
"""
|
| 55 |
+
Return top K latest notes per category instead of a single note.
|
|
|
|
| 56 |
"""
|
| 57 |
+
|
| 58 |
+
grouped: Dict[str, List[Dict]] = {c: [] for c in categories}
|
| 59 |
+
|
| 60 |
for note in notes:
|
| 61 |
cat = note.get("category")
|
| 62 |
cats = cat if isinstance(cat, list) else [cat] if cat else []
|
| 63 |
ts = note.get("createdAt", 0)
|
| 64 |
+
|
| 65 |
for c in cats:
|
| 66 |
if c in categories:
|
| 67 |
+
grouped[c].append(note)
|
| 68 |
+
|
| 69 |
+
# sort each category by latest first
|
| 70 |
+
for c in grouped:
|
| 71 |
+
grouped[c].sort(key=lambda x: x.get("createdAt", 0), reverse=True)
|
| 72 |
+
grouped[c] = grouped[c][:top_k]
|
| 73 |
+
|
| 74 |
+
return grouped
|
| 75 |
|
| 76 |
async def _extract_keywords_with_claude(
|
| 77 |
self, note: Dict, category: str
|
|
|
|
| 220 |
# ββ Step 2: keywords via Claude ββββββββββ
|
| 221 |
latest_notes = self._latest_note_per_category(notes, top_categories)
|
| 222 |
|
| 223 |
+
keyword_tasks = []
|
| 224 |
+
|
| 225 |
+
for cat in top_categories:
|
| 226 |
+
if cat not in latest_notes:
|
| 227 |
+
continue
|
| 228 |
+
|
| 229 |
+
notes_list = latest_notes[cat]
|
| 230 |
+
|
| 231 |
+
# ΩΨ¬Ω
ΨΉ ΩΩ Ψ§ΩΩΩΨͺΨ³ ΩΩ ΩΨ΅ ΩΨ§ΨΨ―
|
| 232 |
+
combined_content = "\n".join(
|
| 233 |
+
n.get("content", "")[:500]
|
| 234 |
+
for n in notes_list
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
fake_note = {
|
| 238 |
+
"content": combined_content
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
keyword_tasks.append(
|
| 242 |
+
self._extract_keywords_with_claude(fake_note, cat)
|
| 243 |
+
)
|
| 244 |
keyword_results = await asyncio.gather(*keyword_tasks)
|
| 245 |
|
| 246 |
+
category_keywords = {}
|
| 247 |
+
|
| 248 |
+
for cat, kws in zip(latest_notes.keys(), keyword_results):
|
| 249 |
+
category_keywords[cat] = kws
|
| 250 |
|
| 251 |
# ββ Step 3: build query & recommend ββββββ
|
| 252 |
all_videos = []
|
| 253 |
|
| 254 |
for category, keywords in category_keywords.items():
|
| 255 |
+
query = " ".join(keywords[:3]) +" tutorial explanation"
|
| 256 |
|
| 257 |
logger.info(f"π― Searching category: {category} | Query: {query}")
|
| 258 |
|