cipher-attacks commited on
Commit
8572d5c
·
1 Parent(s): 7982d01

seed courses

Browse files
app/bot/handlers/admin.py CHANGED
@@ -11,6 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
11
 
12
  from app.config import get_settings
13
  from app.core.catalog import KINDS, STREAMS
 
14
  from app.db.models import User
15
  from app.db.repos import library as repo
16
  from app.db.repos import stats as stats_repo
@@ -32,6 +33,8 @@ HELP = (
32
  "<b>Admin tools</b>\n"
33
  "/addcourse &lt;natural|social|all&gt; &lt;year 0-5&gt; &lt;code&gt; &lt;name...&gt;\n"
34
  " e.g. <code>/addcourse natural 1 Math1011 Calculus I</code>\n"
 
 
35
  "/courses : list courses with ids\n"
36
  "/stats : numbers\n"
37
  "/ping : measure database and Telegram speed\n\n"
@@ -73,10 +76,23 @@ async def add_course(message: Message, command: CommandObject, session: AsyncSes
73
  return
74
  stream = None if parts[0] == "all" else parts[0]
75
  year = int(parts[1]) or None
76
- course = await repo.add_course(session, name=parts[3].strip(), code=parts[2], stream=stream, year=year)
 
77
  await message.answer(f"✅ Course <b>#{course.id}</b> added: {course.code} · {course.name}")
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  @router.message(Command("courses"))
81
  async def list_courses(message: Message, session: AsyncSession) -> None:
82
  courses = await repo.all_courses(session)
 
11
 
12
  from app.config import get_settings
13
  from app.core.catalog import KINDS, STREAMS
14
+ from app.core.seed_courses import FRESHMAN_COURSES
15
  from app.db.models import User
16
  from app.db.repos import library as repo
17
  from app.db.repos import stats as stats_repo
 
33
  "<b>Admin tools</b>\n"
34
  "/addcourse &lt;natural|social|all&gt; &lt;year 0-5&gt; &lt;code&gt; &lt;name...&gt;\n"
35
  " e.g. <code>/addcourse natural 1 Math1011 Calculus I</code>\n"
36
+ " (use <code>-</code> as the code if a course has none)\n"
37
+ "/seedcourses : add the Year-1 course list (safe to run twice)\n"
38
  "/courses : list courses with ids\n"
39
  "/stats : numbers\n"
40
  "/ping : measure database and Telegram speed\n\n"
 
76
  return
77
  stream = None if parts[0] == "all" else parts[0]
78
  year = int(parts[1]) or None
79
+ code = None if parts[2] == "-" else parts[2]
80
+ course = await repo.add_course(session, name=parts[3].strip(), code=code, stream=stream, year=year)
81
  await message.answer(f"✅ Course <b>#{course.id}</b> added: {course.code} · {course.name}")
82
 
83
 
84
+ @router.message(Command("seedcourses"))
85
+ async def seed_courses(message: Message, session: AsyncSession) -> None:
86
+ added = skipped = 0
87
+ for stream, year, semester, code, name in FRESHMAN_COURSES:
88
+ if await repo.find_course(session, name, stream, year):
89
+ skipped += 1
90
+ continue
91
+ await repo.add_course(session, name=name, code=code, stream=stream, year=year, semester=semester)
92
+ added += 1
93
+ await message.answer(f"✅ Added <b>{added}</b> courses, skipped {skipped} that already existed.\nSee /courses for ids.")
94
+
95
+
96
  @router.message(Command("courses"))
97
  async def list_courses(message: Message, session: AsyncSession) -> None:
98
  courses = await repo.all_courses(session)
app/core/seed_courses.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Year-1 (freshman) course catalog used by /seedcourses.
2
+
3
+ Sources (checked 14 Oct 2026):
4
+ * NATURAL stream, courses WITH a code: official DBU course catalog (dbu.edu.et, College of
5
+ Engineering, Year I). Codes are DBU's own.
6
+ * NATURAL stream, General Biology / General Chemistry: common national natural-science
7
+ freshman list (Jimma / Debark University). DBU code not verified -> shown without a code.
8
+ * SOCIAL stream: national common social-science freshman list (University of Gondar listing).
9
+ No DBU-specific source found -> shown without codes. Please verify against your registrar list.
10
+
11
+ Tuple: (stream, year, semester, code or None, name)
12
+ """
13
+
14
+ FRESHMAN_COURSES: list[tuple[str, int, int, str | None, str]] = [
15
+ # ---- NATURAL, semester I (DBU catalog) ----
16
+ ("natural", 1, 1, "FLEn1001", "Communicative English Language Skills I"),
17
+ ("natural", 1, 1, "Math1003", "Mathematics for Natural Sciences"),
18
+ ("natural", 1, 1, "LoCT1005", "Critical Thinking"),
19
+ ("natural", 1, 1, "Psys1007", "General Psychology"),
20
+ ("natural", 1, 1, "Phys1009", "General Physics"),
21
+ ("natural", 1, 1, "SpSc1011", "Physical Fitness"),
22
+ ("natural", 1, 1, "GeES1013", "Geography of Ethiopia and the Horn"),
23
+ # ---- NATURAL, semester II (DBU catalog) ----
24
+ ("natural", 1, 2, "FLEn1002", "Communicative English Language Skills II"),
25
+ ("natural", 1, 2, "Math1004", "Applied Mathematics I"),
26
+ ("natural", 1, 2, "Anth1006", "Social Anthropology"),
27
+ ("natural", 1, 2, "MGMT1008", "Entrepreneurship"),
28
+ ("natural", 1, 2, "EmTe1010", "Introduction to Emerging Technologies"),
29
+ ("natural", 1, 2, "ECEg1012", "Computer Programming"),
30
+ ("natural", 1, 2, "MCiE1014", "Moral and Civic Education"),
31
+ ("natural", 1, 2, "Hist1016", "History of Ethiopia and the Horn"),
32
+ # ---- NATURAL science (non-engineering), semester II: code not verified for DBU ----
33
+ ("natural", 1, 2, None, "General Biology"),
34
+ ("natural", 1, 2, None, "General Chemistry"),
35
+ # ---- SOCIAL, semester I (national common list; codes not verified for DBU) ----
36
+ ("social", 1, 1, None, "Communicative English Language Skills I"),
37
+ ("social", 1, 1, None, "General Psychology"),
38
+ ("social", 1, 1, None, "Geography of Ethiopia and the Horn"),
39
+ ("social", 1, 1, None, "Introduction to Economics"),
40
+ ("social", 1, 1, None, "Logic and Critical Thinking"),
41
+ ("social", 1, 1, None, "Mathematics for Social Sciences"),
42
+ ("social", 1, 1, None, "Physical Fitness"),
43
+ # ---- SOCIAL, semester II ----
44
+ ("social", 1, 2, None, "Communicative English Language Skills II"),
45
+ ("social", 1, 2, None, "Entrepreneurship"),
46
+ ("social", 1, 2, None, "Global Affairs"),
47
+ ("social", 1, 2, None, "Introduction to Law and the Ethiopian Legal System"),
48
+ ("social", 1, 2, None, "Social Anthropology"),
49
+ ("social", 1, 2, None, "History of Ethiopia and the Horn"),
50
+ ("social", 1, 2, None, "Introduction to Emerging Technologies"),
51
+ ("social", 1, 2, None, "Moral and Civic Education"),
52
+ ]
app/db/repos/library.py CHANGED
@@ -50,10 +50,25 @@ async def all_courses(session: AsyncSession) -> list[Course]:
50
  return list(res.scalars())
51
 
52
 
 
 
 
 
 
 
 
 
 
53
  async def add_course(
54
- session: AsyncSession, *, name: str, code: Optional[str], stream: Optional[str], year: Optional[int]
 
 
 
 
 
 
55
  ) -> Course:
56
- course = Course(name=name, code=code, stream=stream, year=year)
57
  session.add(course)
58
  await session.flush()
59
  return course
 
50
  return list(res.scalars())
51
 
52
 
53
+ async def find_course(
54
+ session: AsyncSession, name: str, stream: Optional[str], year: Optional[int]
55
+ ) -> Optional[Course]:
56
+ res = await session.execute(
57
+ select(Course).where(Course.name == name, Course.stream == stream, Course.year == year).limit(1)
58
+ )
59
+ return res.scalar_one_or_none()
60
+
61
+
62
  async def add_course(
63
+ session: AsyncSession,
64
+ *,
65
+ name: str,
66
+ code: Optional[str],
67
+ stream: Optional[str],
68
+ year: Optional[int],
69
+ semester: Optional[int] = None,
70
  ) -> Course:
71
+ course = Course(name=name, code=code, stream=stream, year=year, semester=semester)
72
  session.add(course)
73
  await session.flush()
74
  return course