Spaces:
Running on Zero
Running on Zero
Commit ·
8572d5c
1
Parent(s): 7982d01
seed courses
Browse files- app/bot/handlers/admin.py +17 -1
- app/core/seed_courses.py +52 -0
- app/db/repos/library.py +17 -2
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 <natural|social|all> <year 0-5> <code> <name...>\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 |
-
|
|
|
|
| 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 <natural|social|all> <year 0-5> <code> <name...>\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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|