cipher-attacks commited on
Commit
6b161eb
·
1 Parent(s): 3998cca

kind fixes

Browse files
app/bot/handlers/admin.py CHANGED
@@ -53,7 +53,7 @@ HELP = (
53
  "<b>Smart upload (default):</b> just send or FORWARD files. The bot finds the course, type, "
54
  "title and skips duplicates. If unsure it asks you (tap a button).\n"
55
  "/recent : last 15 files · /review : files waiting for you\n"
56
- "/move &lt;id&gt; &lt;course_id&gt; [kind] · /rename &lt;id&gt; &lt;title&gt; · /del &lt;id&gt;\n"
57
  "/mergecourses : merge courses shared by both streams (run once, then /seedcourses)\n\n"
58
  "<b>Force a course:</b> <code>/upload 3 exam</code>, then send files, /done when finished.\n"
59
  "<b>Or</b> send a file with caption\n"
@@ -398,6 +398,18 @@ async def move(message: Message, command: CommandObject, session: AsyncSession)
398
  await message.answer(f"✅ #{material.id} → {course.name} · {material.kind}")
399
 
400
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  @router.message(Command("rename"))
402
  async def rename(message: Message, command: CommandObject, session: AsyncSession) -> None:
403
  parts = (command.args or "").split(maxsplit=1)
 
53
  "<b>Smart upload (default):</b> just send or FORWARD files. The bot finds the course, type, "
54
  "title and skips duplicates. If unsure it asks you (tap a button).\n"
55
  "/recent : last 15 files · /review : files waiting for you\n"
56
+ "/move &lt;id&gt; &lt;course_id&gt; [kind] · /kind &lt;id&gt; &lt;kind&gt; · /rename &lt;id&gt; &lt;title&gt; · /del &lt;id&gt;\n"
57
  "/mergecourses : merge courses shared by both streams (run once, then /seedcourses)\n\n"
58
  "<b>Force a course:</b> <code>/upload 3 exam</code>, then send files, /done when finished.\n"
59
  "<b>Or</b> send a file with caption\n"
 
398
  await message.answer(f"✅ #{material.id} → {course.name} · {material.kind}")
399
 
400
 
401
+ @router.message(Command("kind"))
402
+ async def set_kind(message: Message, command: CommandObject, session: AsyncSession) -> None:
403
+ parts = (command.args or "").split()
404
+ kind = KIND_ALIASES.get(parts[1].lower()) if len(parts) == 2 else None
405
+ material = await repo.get_material(session, int(parts[0])) if parts and parts[0].isdigit() else None
406
+ if not material or not kind:
407
+ await message.answer("Usage: <code>/kind &lt;file_id&gt; &lt;slide|exam|note|book&gt;</code>")
408
+ return
409
+ material.kind = kind
410
+ await message.answer(f"✅ #{material.id} is now <b>{kind}</b>")
411
+
412
+
413
  @router.message(Command("rename"))
414
  async def rename(message: Message, command: CommandObject, session: AsyncSession) -> None:
415
  parts = (command.args or "").split(maxsplit=1)
app/services/classifier.py CHANGED
@@ -133,7 +133,8 @@ def decide(ranked: Sequence[tuple[float, object]]) -> tuple[Optional[object], bo
133
 
134
 
135
  # ---------------------------------------------------------------- kind
136
- _EXAM = ("exam", "midterm", "mid exam", "mid-exam", "final", "quiz", "test", "question", "past paper", "previous", "ፈተና")
 
137
  _SLIDE = ("slide", "lecture", "ppt", "presentation", "chapter")
138
  _NOTE = ("note", "handout", "summary", "worksheet", "assignment", "tutorial", "module", "ማስታወሻ")
139
  _BOOK = ("textbook", "book", "edition", "isbn", "volume")
@@ -144,19 +145,21 @@ def detect_kind(name: str, caption: str, text: str, pages: Optional[int], size:
144
  label = f" {(name or '').lower()} {(caption or '').lower()} ".replace("_", " ").replace("-", " ")
145
  if ext in (".ppt", ".pptx"):
146
  return "slide", "presentation file"
147
- if any(k in label for k in _EXAM):
148
  return "exam", "name says exam"
149
  if any(k in label for k in _SLIDE):
150
  return "slide", "name says slides/chapter"
151
  if any(k in label for k in _BOOK):
152
  return "book", "name says book"
 
 
 
 
153
  if sum(1 for k in _EXAM_TEXT if k in (text or "").lower()) >= 2:
154
  return "exam", "content looks like an exam"
155
- if any(k in label for k in _NOTE):
156
- return "note" if not pages or pages < 150 else "book", "name says module/notes"
157
- if pages and pages >= 200:
158
  return "book", f"{pages} pages"
159
- if size and size > 30 * 1024 * 1024:
160
  return "book", "very large file"
161
  return "note", "default"
162
 
 
133
 
134
 
135
  # ---------------------------------------------------------------- kind
136
+ _EXAM_STRONG = ("exam", "midterm", "mid exam", "quiz", "past paper", "question paper", "ፈተና")
137
+ _EXAM_WEAK = ("final", "test", "question", "previous") # "Geography Module Final" is NOT an exam
138
  _SLIDE = ("slide", "lecture", "ppt", "presentation", "chapter")
139
  _NOTE = ("note", "handout", "summary", "worksheet", "assignment", "tutorial", "module", "ማስታወሻ")
140
  _BOOK = ("textbook", "book", "edition", "isbn", "volume")
 
145
  label = f" {(name or '').lower()} {(caption or '').lower()} ".replace("_", " ").replace("-", " ")
146
  if ext in (".ppt", ".pptx"):
147
  return "slide", "presentation file"
148
+ if any(k in label for k in _EXAM_STRONG):
149
  return "exam", "name says exam"
150
  if any(k in label for k in _SLIDE):
151
  return "slide", "name says slides/chapter"
152
  if any(k in label for k in _BOOK):
153
  return "book", "name says book"
154
+ if any(k in label for k in _NOTE):
155
+ return "note", "name says module/notes" # module/notes beat the ambiguous word "final"
156
+ if any(k in label for k in _EXAM_WEAK) or re.search(r"\bmid\b", label):
157
+ return "exam", "name hints at an exam"
158
  if sum(1 for k in _EXAM_TEXT if k in (text or "").lower()) >= 2:
159
  return "exam", "content looks like an exam"
160
+ if pages and pages >= 350: # national modules are 150-300 pages: those stay "note"
 
 
161
  return "book", f"{pages} pages"
162
+ if size and size > 40 * 1024 * 1024:
163
  return "book", "very large file"
164
  return "note", "default"
165
 
tests/test_logic.py CHANGED
@@ -193,7 +193,12 @@ def test_kind_detection():
193
  assert C.detect_kind("Phys final exam.pdf", "", "", None, None, ".pdf")[0] == "exam"
194
  assert C.detect_kind("lecture1.pptx", "", "", None, None, ".pptx")[0] == "slide"
195
  assert C.detect_kind("Math module.pdf", "", "", 120, None, ".pdf")[0] == "note"
196
- assert C.detect_kind("Math module.pdf", "", "", 260, None, ".pdf")[0] == "book"
 
 
 
 
 
197
  assert C.detect_kind("scan.pdf", "", "Time allowed: 2 hours. Answer all questions", None, None, ".pdf")[0] == "exam"
198
  assert C.detect_kind("whatever.pdf", "", "", None, None, ".pdf")[0] == "note"
199
 
 
193
  assert C.detect_kind("Phys final exam.pdf", "", "", None, None, ".pdf")[0] == "exam"
194
  assert C.detect_kind("lecture1.pptx", "", "", None, None, ".pptx")[0] == "slide"
195
  assert C.detect_kind("Math module.pdf", "", "", 120, None, ".pdf")[0] == "note"
196
+ assert C.detect_kind("Math module.pdf", "", "", 260, None, ".pdf")[0] == "note"
197
+ assert C.detect_kind("calculus.pdf", "", "", 400, None, ".pdf")[0] == "book"
198
+ assert C.detect_kind("Mathematics for Social Sciences.pdf", "", "", 280, None, ".pdf")[0] == "note" # was #6
199
+ assert C.detect_kind("Geography Module Final.pdf", "", "", 150, None, ".pdf")[0] == "note" # was #3
200
+ assert C.detect_kind("Physics final.pdf", "", "", None, None, ".pdf")[0] == "exam"
201
+ assert C.detect_kind("Phys mid.pdf", "", "", None, None, ".pdf")[0] == "exam"
202
  assert C.detect_kind("scan.pdf", "", "Time allowed: 2 hours. Answer all questions", None, None, ".pdf")[0] == "exam"
203
  assert C.detect_kind("whatever.pdf", "", "", None, None, ".pdf")[0] == "note"
204