Ines1994 commited on
Commit
5e5c7df
ยท
verified ยท
1 Parent(s): 56d869c

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +50 -19
  2. scraper.py +30 -4
app.py CHANGED
@@ -277,22 +277,37 @@ def hybrid_search(query: str, k: int = TOP_K_SEARCH) -> tuple:
277
  brank = {chunks[i]: j for j, i in enumerate(btop) if i < len(chunks)}
278
  tb = time.time()-t1
279
 
280
- texts = set(vrank)|set(brank)
281
- # ุฅุฐุง ู„ุง ุชูˆุฌุฏ ู†ุชุงุฆุฌ ุนู„ู‰ ุงู„ุฅุทู„ุงู‚ โ†’ ุฃุฑุฌุน vector results ูู‚ุท
282
- if not texts:
283
- results = [{"content":d.page_content,"meta":d.metadata,"rrf_score":1.0,"snippet":d.page_content[:120].replace("\n"," ")+"..."} for d in vdocs]
284
- return results, {"vector_ms":round(tv*1000),"bm25_ms":0}
285
-
286
- fused = {t: vw/(vrank.get(t,k+10)+RRF_K)+bw/(brank.get(t,k+10)+RRF_K) for t in texts}
287
- ranked = sorted(fused, key=fused.get, reverse=True)[:k]
288
-
289
- tmeta = {d.page_content: d.metadata for d in vdocs}
290
- for i in btop:
291
- if i < len(chunks) and chunks[i] not in tmeta:
292
- tmeta[chunks[i]] = metas[i]
293
-
294
- results = [{"content":t,"meta":tmeta.get(t,{}),"rrf_score":fused[t],"snippet":t[:120].replace("\n"," ")+"..."} for t in ranked]
295
- return results, {"vector_ms":round(tv*1000),"bm25_ms":round(tb*1000)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
298
  # ๐Ÿ”Ž QUERY EXPANSION
@@ -682,7 +697,7 @@ with st.sidebar:
682
  st.markdown("---")
683
  # DB status โ€” ุขู…ู† ู‡ู†ุง ุฎุงุฑุฌ cache_resource
684
  chunks_count = len(load_all_chunks(vectordb)[0])
685
- if chunks_count < 1500:
686
  st.warning("โณ ุฌุงุฑูŠ ุจู†ุงุก ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ู„ุฃูˆู„ ู…ุฑุฉ ุนู„ู‰ ุงู„ุณูŠุฑูุฑ... ูŠุฑุฌู‰ ุงู„ุงู†ุชุธุงุฑ ุฏู‚ูŠู‚ุฉ/ุฏู‚ูŠู‚ุชูŠู†...")
687
  try:
688
  # Auto-run build process seamlessly
@@ -694,8 +709,16 @@ with st.sidebar:
694
  ts = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=80, separators=["\n\n", "\n", ".", " "])
695
  all_chunks = []
696
  for page in pages:
 
 
697
  for chunk in ts.split_text(page["content"]):
698
- all_chunks.append({"page_name": page["page_name"], "url": page["url"], "langue": page["langue"], "content": chunk})
 
 
 
 
 
 
699
  cclient = _chromadb.PersistentClient(path=CHROMA_PATH)
700
  try: cclient.delete_collection("ena_collection")
701
  except: pass
@@ -703,7 +726,15 @@ with st.sidebar:
703
  BATCH = 50
704
  for i in range(0, len(all_chunks), BATCH):
705
  b = all_chunks[i:i+BATCH]
706
- temp_db.add_texts(texts=[c["content"] for c in b], metadatas=[{"page_name": c["page_name"], "url": c["url"], "langue": c["langue"]} for c in b])
 
 
 
 
 
 
 
 
707
 
708
  # Clear caches and rerun to load fresh data
709
  load_vectordb.clear()
 
277
  brank = {chunks[i]: j for j, i in enumerate(btop) if i < len(chunks)}
278
  tb = time.time()-t1
279
 
280
+ # ๐ŸŽฏ Category-Aware Search (Soft Boosting)
281
+ qn_low = qn.lower()
282
+ target_cat = None
283
+ if any(k in qn_low for k in ["ู…ู†ุงุธุฑุฉ", "ู…ู†ุงุธุฑุงุช", "concours"]):
284
+ target_cat = "concours_ar" if detect_lang(qn) == "ar" else "concours_fr"
285
+ elif any(k in qn_low for k in ["ุชูƒูˆูŠู†", "ุชูƒูˆูŠู†ุงุช", "formation"]):
286
+ target_cat = "formation_ar" if detect_lang(qn) == "ar" else "formation_fr"
287
+ elif any(k in qn_low for k in ["ุญูˆูƒู…ุฉ", "gouvernance"]):
288
+ target_cat = "gouvernance"
289
+ elif any(k in qn_low for k in ["ุฎุจุฑ", "ุฃุฎุจุงุฑ", "actualites"]):
290
+ target_cat = "news"
291
+
292
+ results = []
293
+ for t in ranked:
294
+ meta = tmeta.get(t, {})
295
+ score = fused[t]
296
+
297
+ # Soft boost targets
298
+ if target_cat and meta.get("category") == target_cat:
299
+ score *= 1.5
300
+
301
+ results.append({
302
+ "content": t,
303
+ "meta": meta,
304
+ "rrf_score": score,
305
+ "snippet": t[:120].replace("\n", " ") + "..."
306
+ })
307
+
308
+ # Re-sort by boosted score
309
+ results = sorted(results, key=lambda x: x["rrf_score"], reverse=True)
310
+ return results, {"vector_ms": round(tv * 1000), "bm25_ms": round(tb * 1000)}
311
 
312
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
313
  # ๐Ÿ”Ž QUERY EXPANSION
 
697
  st.markdown("---")
698
  # DB status โ€” ุขู…ู† ู‡ู†ุง ุฎุงุฑุฌ cache_resource
699
  chunks_count = len(load_all_chunks(vectordb)[0])
700
+ if chunks_count < 2200:
701
  st.warning("โณ ุฌุงุฑูŠ ุจู†ุงุก ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ู„ุฃูˆู„ ู…ุฑุฉ ุนู„ู‰ ุงู„ุณูŠุฑูุฑ... ูŠุฑุฌู‰ ุงู„ุงู†ุชุธุงุฑ ุฏู‚ูŠู‚ุฉ/ุฏู‚ูŠู‚ุชูŠู†...")
702
  try:
703
  # Auto-run build process seamlessly
 
709
  ts = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=80, separators=["\n\n", "\n", ".", " "])
710
  all_chunks = []
711
  for page in pages:
712
+ # Use category from JSON or infer it
713
+ cat = page.get("category", "other")
714
  for chunk in ts.split_text(page["content"]):
715
+ all_chunks.append({
716
+ "page_name": page["page_name"],
717
+ "url": page["url"],
718
+ "langue": page["langue"],
719
+ "category": cat,
720
+ "content": chunk
721
+ })
722
  cclient = _chromadb.PersistentClient(path=CHROMA_PATH)
723
  try: cclient.delete_collection("ena_collection")
724
  except: pass
 
726
  BATCH = 50
727
  for i in range(0, len(all_chunks), BATCH):
728
  b = all_chunks[i:i+BATCH]
729
+ temp_db.add_texts(
730
+ texts=[c["content"] for c in b],
731
+ metadatas=[{
732
+ "page_name": c["page_name"],
733
+ "url": c["url"],
734
+ "langue": c["langue"],
735
+ "category": c["category"]
736
+ } for c in b]
737
+ )
738
 
739
  # Clear caches and rerun to load fresh data
740
  load_vectordb.clear()
scraper.py CHANGED
@@ -14,14 +14,36 @@ import json, requests, time
14
  from bs4 import BeautifulSoup
15
  from urllib.parse import urljoin, urlparse
16
 
17
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
- # ๐ŸŒ URLs ุงู„ุจุฏุงูŠุฉ ู„ูƒู„ ู„ุบุฉ
19
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
20
  BASE_URLS = [
21
  "https://www.ena.tn/ar/",
22
  "https://www.ena.tn/fr/"
23
  ]
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  visited = set()
26
  all_data = []
27
 
@@ -48,7 +70,9 @@ def is_valid_url(url):
48
  parsed = urlparse(url)
49
  if "ena.tn" not in parsed.netloc:
50
  return False
51
- if any(url.endswith(ext) for ext in [".pdf",".jpg",".png",".zip"]):
 
 
52
  return False
53
  return True
54
 
@@ -129,11 +153,13 @@ def crawl(url, depth=0, max_depth=3):
129
  content = clean_page(soup)
130
 
131
  if len(content) > 200:
 
132
  all_data.append({
133
  "page_name": url.split("/")[-2] if "/" in url else "page",
134
  "url": url,
135
  "source": "ena.tn",
136
  "langue": "ar" if "/ar/" in url else "fr",
 
137
  "content": content,
138
  "chars": len(content)
139
  })
 
14
  from bs4 import BeautifulSoup
15
  from urllib.parse import urljoin, urlparse
16
 
 
 
 
17
  BASE_URLS = [
18
  "https://www.ena.tn/ar/",
19
  "https://www.ena.tn/fr/"
20
  ]
21
 
22
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23
+ # ๐Ÿท๏ธ ุงู„ุชุตู†ูŠู ุงู„ุฐูƒูŠ ูˆุงู„ู€ Blacklist
24
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
25
+ CATEGORIES = {
26
+ "/concours/": "concours_fr",
27
+ "/formation/": "formation_fr",
28
+ "/gouvernance/": "gouvernance",
29
+ "/ar/concours": "concours_ar",
30
+ "/ar/formation": "formation_ar",
31
+ "/ar/service": "service_ar", # ุฎุฏู…ุงุช ุฅุฏุงุฑูŠุฉ
32
+ "/actualites/": "news",
33
+ }
34
+
35
+ BLACKLIST = [
36
+ "/login/", "/connexion/", "/reset-password/", "/mot-de-passe-oublie/",
37
+ "/my-account/", "/search/", "/cart/", "/checkout/",
38
+ "wp-login", "admin-ajax", "/feed/"
39
+ ]
40
+
41
+ def get_category(url):
42
+ for pattern, cat in CATEGORIES.items():
43
+ if pattern in url.lower():
44
+ return cat
45
+ return "other"
46
+
47
  visited = set()
48
  all_data = []
49
 
 
70
  parsed = urlparse(url)
71
  if "ena.tn" not in parsed.netloc:
72
  return False
73
+ if any(url.endswith(ext) for ext in [".pdf",".jpg",".png",".zip",".docx",".xlsx"]):
74
+ return False
75
+ if any(b in url.lower() for b in BLACKLIST):
76
  return False
77
  return True
78
 
 
153
  content = clean_page(soup)
154
 
155
  if len(content) > 200:
156
+ category = get_category(url)
157
  all_data.append({
158
  "page_name": url.split("/")[-2] if "/" in url else "page",
159
  "url": url,
160
  "source": "ena.tn",
161
  "langue": "ar" if "/ar/" in url else "fr",
162
+ "category": category,
163
  "content": content,
164
  "chars": len(content)
165
  })