492 lines
24 KiB
Python
492 lines
24 KiB
Python
"""
|
||
Seed demo data for HKU ICB Class Hub.
|
||
|
||
Usage (inside backend container):
|
||
python seed_demo.py
|
||
Or from host:
|
||
docker compose exec backend python seed_demo.py
|
||
"""
|
||
|
||
import asyncio
|
||
import random
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
from sqlalchemy import select, func
|
||
|
||
from app.db.database import async_session, engine
|
||
from app.db.base import Base
|
||
from app.db.models import (
|
||
Class_, User, Timeline, TimelineLike, TimelineComment,
|
||
Schedule, Announcement, Resource, Notification,
|
||
StudentRoster, Vote, VoteOption, VoteResponse,
|
||
Assignment, AssignmentSubmission,
|
||
)
|
||
from app.core.auth import hash_password
|
||
|
||
|
||
# ── Demo data pools ──────────────────────────────────────────────────────────
|
||
|
||
STUDENTS = [
|
||
{"name": "张伟", "email": "zhangwei@demo.com", "student_id": "3001001"},
|
||
{"name": "李娜", "email": "lina@demo.com", "student_id": "3001002"},
|
||
{"name": "王芳", "email": "wangfang@demo.com", "student_id": "3001003"},
|
||
{"name": "刘洋", "email": "liuyang@demo.com", "student_id": "3001004"},
|
||
{"name": "陈明", "email": "chenming@demo.com", "student_id": "3001005"},
|
||
{"name": "杨秀英", "email": "yangxiuying@demo.com", "student_id": "3001006"},
|
||
{"name": "赵敏", "email": "zhaomin@demo.com", "student_id": "3001007"},
|
||
{"name": "黄强", "email": "huangqiang@demo.com", "student_id": "3001008"},
|
||
{"name": "周丽", "email": "zhouli@demo.com", "student_id": "3001009"},
|
||
{"name": "吴刚", "email": "wugang@demo.com", "student_id": "3001010"},
|
||
{"name": "徐静", "email": "xujing@demo.com", "student_id": "3001011"},
|
||
{"name": "孙磊", "email": "sunlei@demo.com", "student_id": "3001012"},
|
||
{"name": "马超", "email": "machao@demo.com", "student_id": "3001013"},
|
||
{"name": "朱婷", "email": "zhuting@demo.com", "student_id": "3001014"},
|
||
{"name": "胡建华", "email": "hujianhua@demo.com", "student_id": "3001015"},
|
||
]
|
||
|
||
CLASS_ADMIN = {
|
||
"name": "林教授",
|
||
"email": "linprof@demo.com",
|
||
}
|
||
|
||
INDUSTRIES = ["金融", "科技", "医疗", "教育", "制造业", "咨询", "互联网", "房地产", "消费品", "能源"]
|
||
COMPANIES = [
|
||
"腾讯科技", "阿里巴巴", "华为技术", "中国平安", "招商银行",
|
||
"字节跳动", "美团", "京东集团", "中信证券", "小米科技",
|
||
"百度", "网易", "滴滴出行", "拼多多", "比亚迪",
|
||
]
|
||
POSITIONS = [
|
||
"产品总监", "技术总监", "市场副总裁", "运营总监", "战略总监",
|
||
"投资总监", "事业部总经理", "首席架构师", "人力资源总监", "财务总监",
|
||
]
|
||
SKILLS_POOL = [
|
||
"战略规划", "团队管理", "数据分析", "产品设计", "市场营销",
|
||
"财务管理", "风险控制", "项目管理", "商业分析", "数字化转型",
|
||
"人工智能", "供应链管理", "品牌管理", "投融资", "企业并购",
|
||
]
|
||
|
||
TIMELINE_POSTS = [
|
||
{
|
||
"title": "开课第一天,期待已久的学习之旅!",
|
||
"content": "今天终于迎来了 HKU ICB 的开课日,见到了来自各行各业的同学们,非常期待接下来的学习时光。教授的讲解深入浅出,让人受益匪浅。",
|
||
},
|
||
{
|
||
"title": "小组讨论收获满满",
|
||
"content": "今天的小组讨论非常精彩,我们组的成员来自金融、科技和教育三个行业,不同的视角让我们对案例有了更全面的理解。大家碰撞出了很多精彩的火花!",
|
||
},
|
||
{
|
||
"title": "推荐一本好书《创新者的窘境》",
|
||
"content": "最近在读克莱顿·克里斯坦森的《创新者的窘境》,书中关于颠覆式创新的理论与课程中的战略管理内容非常契合,推荐给大家。",
|
||
},
|
||
{
|
||
"title": "企业参访活动回顾",
|
||
"content": "感谢学校组织的腾讯总部参访活动,深入了解了一家科技巨头的企业文化和创新机制。特别是他们敏捷开发流程和用户导向的产品理念,给我留下了深刻印象。",
|
||
},
|
||
{
|
||
"title": "期末项目组队啦",
|
||
"content": "我们正在组建期末项目的团队,主题是「传统企业数字化转型路径研究」,有兴趣的同学欢迎加入!目前团队已有三位同学,覆盖了金融、制造和咨询行业。",
|
||
},
|
||
{
|
||
"title": "学习心得:领导力与变革管理",
|
||
"content": "这周的领导力课程让我对变革管理有了全新的认识。科特的八步变革模型非常实用,结合我所在公司的实际案例,感觉可以直接应用。分享给同学们一起讨论。",
|
||
},
|
||
{
|
||
"title": "周末读书会邀约",
|
||
"content": "这周六下午在中环的咖啡厅组织一次读书分享会,我们计划讨论《从零到一》和《精益创业》两本书,欢迎有空的同学一起来交流。",
|
||
},
|
||
{
|
||
"title": "求职季来了,分享几个面试技巧",
|
||
"content": "最近在准备职业转型,整理了一些高管面试的心得体会。最重要的三点:1) 用数据说话;2) 展示战略思维;3) 体现文化匹配。希望对大家有帮助。",
|
||
},
|
||
]
|
||
|
||
SCHEDULE_DATA = [
|
||
{"type": "course", "title": "战略管理", "location": "HKU ICB 教室 A301", "desc": "教授:林教授\n课程内容:竞争战略分析框架", "day_offset": 7},
|
||
{"type": "course", "title": "财务分析与决策", "location": "HKU ICB 教室 B205", "desc": "教授:陈教授\n课程内容:企业财务报表分析", "day_offset": 14},
|
||
{"type": "course", "title": "数字营销战略", "location": "HKU ICB 教室 A301", "desc": "教授:王教授\n课程内容:数字时代的营销策略", "day_offset": 21},
|
||
{"type": "course", "title": "领导力与组织行为", "location": "HKU ICB 教室 C102", "desc": "教授:赵教授\n课程内容:变革管理与领导力发展", "day_offset": 28},
|
||
{"type": "deadline", "title": "小组项目提案截止", "location": None, "desc": "请提交小组项目的选题提案,包括研究背景、方法论和预期成果", "day_offset": 18},
|
||
{"type": "deadline", "title": "个人反思报告截止", "location": None, "desc": "提交不少于2000字的个人学习反思报告", "day_offset": 35},
|
||
{"type": "activity", "title": "企业参访:华为深圳总部", "location": "华为坂田基地", "desc": "了解华为的研发体系和企业文化,名额有限请提前报名", "day_offset": 25},
|
||
{"type": "activity", "title": "校友 networking 晚宴", "location": "港大校友会", "desc": "与往届校友交流职业发展经验", "day_offset": 32},
|
||
{"type": "course", "title": "创新与创业管理", "location": "HKU ICB 教室 A301", "desc": "教授:李教授\n课程内容:创新方法论与创业实践", "day_offset": 42},
|
||
{"type": "course", "title": "全球商业环境", "location": "HKU ICB 教室 B205", "desc": "教授:张教授\n课程内容:国际贸易与地缘经济", "day_offset": 49},
|
||
]
|
||
|
||
ANNOUNCEMENTS = [
|
||
{
|
||
"title": "2025年秋季学期注册通知",
|
||
"content": "各位同学,2025年秋季学期注册现已开始。请于截止日期前完成选课和缴费。如有任何问题,请联系教务处。\n\n注册截止日期:2025年9月15日\n缴费截止日期:2025年9月20日",
|
||
"is_pinned": True,
|
||
},
|
||
{
|
||
"title": "图书馆资源更新通知",
|
||
"content": "学院图书馆新增了 Harvard Business Review、McKinsey Quarterly 等数据库的访问权限。同学们可通过校园网直接访问,详细使用指南请查看学院官网。",
|
||
"is_pinned": False,
|
||
},
|
||
{
|
||
"title": "期末考试安排公布",
|
||
"content": "期末考试将于12月进行,具体安排如下:\n\n- 战略管理:12月10日 09:00-12:00\n- 财务分析:12月12日 14:00-17:00\n- 数字营销:12月15日 09:00-12:00\n\n请同学们提前做好复习准备。",
|
||
"is_pinned": True,
|
||
},
|
||
{
|
||
"title": "校园 Wi-Fi 升级通知",
|
||
"content": "本周六(9月20日)校园网络将进行升级维护,届时部分区域可能出现网络中断。预计维护时间为 22:00-次日 06:00,给大家带来的不便敬请谅解。",
|
||
"is_pinned": False,
|
||
},
|
||
]
|
||
|
||
RESOURCES = [
|
||
{"title": "战略管理课程讲义 - 第一讲", "category": "course_material", "file_type": "pdf", "desc": "竞争战略分析框架基础"},
|
||
{"title": "财务分析模板", "category": "template", "file_type": "xlsx", "desc": "财务报表分析模板,包含三大报表"},
|
||
{"title": "数字营销案例集", "category": "case_study", "file_type": "pdf", "desc": "精选10个数字营销实战案例"},
|
||
{"title": "领导力自评工具", "category": "template", "file_type": "pdf", "desc": "领导力评估问卷及解读指南"},
|
||
{"title": "小组项目评分标准", "category": "course_material", "file_type": "pdf", "desc": "期末小组项目的详细评分标准"},
|
||
{"title": "商业模式画布模板", "category": "template", "file_type": "pptx", "desc": "Business Model Canvas 空白模板"},
|
||
{"title": "波特五力模型分析指南", "category": "reference", "file_type": "pdf", "desc": "Michael Porter 五力分析框架详解"},
|
||
{"title": "推荐书单 2025", "category": "reference", "file_type": "pdf", "desc": "教授推荐阅读书目清单"},
|
||
]
|
||
|
||
VOTES = [
|
||
{
|
||
"title": "期末聚餐地点投票",
|
||
"description": "请大家投票选出期末聚餐的地点,得票最高的地点将成为最终选择。",
|
||
"vote_type": "single",
|
||
"is_anonymous": False,
|
||
"options": ["中环·镛记酒家", "尖沙咀·海底捞", "铜锣湾·利苑酒家", "湾仔·名人坊"],
|
||
},
|
||
{
|
||
"title": "下次课程主题偏好调查",
|
||
"description": "我们希望了解大家对课程主题的偏好,以便安排后续的专题讲座。",
|
||
"vote_type": "multiple",
|
||
"is_anonymous": True,
|
||
"max_choices": 3,
|
||
"options": ["ESG与可持续发展", "Web3与区块链", "AI与大数据应用", "跨境投资与并购", "家族企业传承"],
|
||
},
|
||
{
|
||
"title": "企业参访意向",
|
||
"description": "选择你最希望参访的企业,我们将根据投票结果安排参访行程。",
|
||
"vote_type": "single",
|
||
"is_anonymous": False,
|
||
"options": ["字节跳动", "大疆创新", "比亚迪", "商汤科技"],
|
||
},
|
||
]
|
||
|
||
ASSIGNMENTS = [
|
||
{
|
||
"title": "个人战略分析报告",
|
||
"description": "选择一家上市公司,运用 SWOT 分析和波特五力模型,撰写一份不少于3000字的战略分析报告。",
|
||
"deadline_days": 30,
|
||
},
|
||
{
|
||
"title": "小组商业计划书",
|
||
"description": "以小组为单位,提出一个创新商业想法,撰写完整的商业计划书,包括市场分析、财务预测和实施路线图。",
|
||
"deadline_days": 45,
|
||
},
|
||
{
|
||
"title": "课堂反思日志",
|
||
"description": "结合本学期所学内容,撰写一篇个人学习反思日志,总结关键收获和未来应用计划。不少于1500字。",
|
||
"deadline_days": 14,
|
||
},
|
||
]
|
||
|
||
COMMENTS = [
|
||
"写得太好了,很有共鸣!",
|
||
"感谢分享,收藏了",
|
||
"这个观点很有启发",
|
||
"期待更多分享",
|
||
"学习了,谢谢!",
|
||
"有同感,我们的经历很相似",
|
||
"补充一点,我觉得还可以从供应链角度分析",
|
||
"这个案例我之前也研究过,确实很经典",
|
||
]
|
||
|
||
|
||
async def seed():
|
||
"""Generate all demo data."""
|
||
# Ensure tables exist
|
||
from app.db.base import Base
|
||
async with engine.begin() as conn:
|
||
await conn.run_sync(Base.metadata.create_all)
|
||
|
||
async with async_session() as db:
|
||
# Check if data already exists
|
||
result = await db.execute(select(func.count(User.id)))
|
||
if result.scalar() > 1: # super_admin already exists
|
||
print("[!] Database already has data. Skipping seed.")
|
||
print(" To re-seed, delete the database file first.")
|
||
return
|
||
|
||
now = datetime.now(timezone.utc)
|
||
pwd_hash = hash_password("demo123")
|
||
|
||
# ── 1. Create class ──────────────────────────────────────────────
|
||
cls = Class_(
|
||
name="HKU ICB 企业管理研究生课程 2025",
|
||
cohort_year=2025,
|
||
description="香港大学中国商业学院企业管理研究生课程 2025 秋季班,汇聚来自各行各业的精英人才。",
|
||
invite_code="HKU2025",
|
||
)
|
||
db.add(cls)
|
||
await db.flush()
|
||
print(f"[+] Class: {cls.name} (invite code: {cls.invite_code})")
|
||
|
||
# ── 2. Create class admin ────────────────────────────────────────
|
||
admin = User(
|
||
email=CLASS_ADMIN["email"],
|
||
password_hash=pwd_hash,
|
||
name=CLASS_ADMIN["name"],
|
||
role="class_admin",
|
||
status="approved",
|
||
class_id=cls.id,
|
||
industry="教育",
|
||
company="香港大学",
|
||
position="教授",
|
||
bio="香港大学中国商业学院教授,专注于战略管理和企业转型研究。",
|
||
wechat_id="lin_prof_hku",
|
||
)
|
||
db.add(admin)
|
||
await db.flush()
|
||
print(f"[+] Class Admin: {admin.name} ({admin.email})")
|
||
|
||
# ── 3. Create students ───────────────────────────────────────────
|
||
students = []
|
||
for i, s in enumerate(STUDENTS):
|
||
skills = random.sample(SKILLS_POOL, k=random.randint(2, 4))
|
||
user = User(
|
||
email=s["email"],
|
||
password_hash=pwd_hash,
|
||
name=s["name"],
|
||
student_id=s["student_id"],
|
||
role="student",
|
||
status="approved",
|
||
class_id=cls.id,
|
||
industry=INDUSTRIES[i % len(INDUSTRIES)],
|
||
company=COMPANIES[i % len(COMPANIES)],
|
||
position=POSITIONS[i % len(POSITIONS)],
|
||
skills_tags='["' + '", "'.join(skills) + '"]',
|
||
bio=f"在{COMPANIES[i % len(COMPANIES)]}担任{POSITIONS[i % len(POSITIONS)]},拥有丰富的{INDUSTRIES[i % len(INDUSTRIES)]}行业经验。",
|
||
wechat_id=f"wx_{s['student_id']}",
|
||
phone=f"138{random.randint(10000000, 99999999)}",
|
||
)
|
||
db.add(user)
|
||
students.append(user)
|
||
|
||
await db.flush()
|
||
print(f"[+] {len(students)} students created (password: demo123)")
|
||
|
||
# ── 4. Student roster ────────────────────────────────────────────
|
||
for s in students:
|
||
roster = StudentRoster(
|
||
class_id=cls.id,
|
||
student_id=s.student_id,
|
||
name=s.name,
|
||
status="registered",
|
||
user_id=s.id,
|
||
)
|
||
db.add(roster)
|
||
await db.flush()
|
||
print(f"[+] {len(students)} roster entries created")
|
||
|
||
# ── 5. Timelines with likes and comments ─────────────────────────
|
||
all_users = [admin] + students
|
||
for i, post_data in enumerate(TIMELINE_POSTS):
|
||
author = random.choice(all_users)
|
||
post = Timeline(
|
||
class_id=cls.id,
|
||
author_id=author.id,
|
||
title=post_data["title"],
|
||
content=post_data["content"],
|
||
created_at=now - timedelta(days=len(TIMELINE_POSTS) - i, hours=random.randint(0, 12)),
|
||
)
|
||
db.add(post)
|
||
await db.flush()
|
||
|
||
# Add random likes (3~10)
|
||
likers = random.sample(all_users, k=min(random.randint(3, 10), len(all_users)))
|
||
for liker in likers:
|
||
db.add(TimelineLike(post_id=post.id, user_id=liker.id))
|
||
|
||
# Add random comments (1~4)
|
||
commenters = random.sample(students, k=random.randint(1, 4))
|
||
for commenter in commenters:
|
||
db.add(TimelineComment(
|
||
post_id=post.id,
|
||
author_id=commenter.id,
|
||
content=random.choice(COMMENTS),
|
||
created_at=post.created_at + timedelta(hours=random.randint(1, 48)),
|
||
))
|
||
|
||
await db.flush()
|
||
print(f"[+] {len(TIMELINE_POSTS)} timeline posts with likes and comments")
|
||
|
||
# ── 6. Schedules ─────────────────────────────────────────────────
|
||
for sched in SCHEDULE_DATA:
|
||
start = now + timedelta(days=sched["day_offset"], hours=9)
|
||
end = start + timedelta(hours=3) if sched["type"] == "course" else None
|
||
s = Schedule(
|
||
class_id=cls.id,
|
||
type=sched["type"],
|
||
title=sched["title"],
|
||
start_time=start,
|
||
end_time=end,
|
||
location=sched["location"],
|
||
description=sched["desc"],
|
||
)
|
||
db.add(s)
|
||
await db.flush()
|
||
print(f"[+] {len(SCHEDULE_DATA)} schedules created")
|
||
|
||
# ── 7. Announcements ─────────────────────────────────────────────
|
||
for ann in ANNOUNCEMENTS:
|
||
a = Announcement(
|
||
class_id=cls.id,
|
||
author_id=admin.id,
|
||
title=ann["title"],
|
||
content=ann["content"],
|
||
is_pinned=ann["is_pinned"],
|
||
created_at=now - timedelta(days=random.randint(1, 30)),
|
||
)
|
||
db.add(a)
|
||
await db.flush()
|
||
print(f"[+] {len(ANNOUNCEMENTS)} announcements created")
|
||
|
||
# ── 8. Resources ─────────────────────────────────────────────────
|
||
for res in RESOURCES:
|
||
r = Resource(
|
||
class_id=cls.id,
|
||
uploader_id=admin.id,
|
||
title=res["title"],
|
||
description=res["desc"],
|
||
file_url=f"https://example.com/files/{res['title'].replace(' ', '_')}.{res['file_type']}",
|
||
file_type=res["file_type"],
|
||
file_size=random.randint(500_000, 15_000_000),
|
||
category=res["category"],
|
||
download_count=random.randint(5, 80),
|
||
)
|
||
db.add(r)
|
||
await db.flush()
|
||
print(f"[+] {len(RESOURCES)} resources created")
|
||
|
||
# ── 9. Votes with options and responses ──────────────────────────
|
||
for v_data in VOTES:
|
||
deadline = now + timedelta(days=random.randint(5, 20))
|
||
vote = Vote(
|
||
class_id=cls.id,
|
||
creator_id=admin.id,
|
||
title=v_data["title"],
|
||
description=v_data["description"],
|
||
vote_type=v_data["vote_type"],
|
||
is_anonymous=v_data["is_anonymous"],
|
||
max_choices=v_data.get("max_choices", 1),
|
||
deadline=deadline,
|
||
status="open",
|
||
)
|
||
db.add(vote)
|
||
await db.flush()
|
||
|
||
options = []
|
||
for j, opt_text in enumerate(v_data["options"]):
|
||
opt = VoteOption(
|
||
vote_id=vote.id,
|
||
content=opt_text,
|
||
sort_order=j,
|
||
)
|
||
db.add(opt)
|
||
options.append(opt)
|
||
await db.flush()
|
||
|
||
# Add random vote responses
|
||
voters = random.sample(students, k=min(random.randint(5, 12), len(students)))
|
||
for voter in voters:
|
||
chosen_count = 1 if v_data["vote_type"] == "single" else random.randint(1, v_data.get("max_choices", 1))
|
||
chosen_opts = random.sample(options, k=min(chosen_count, len(options)))
|
||
for chosen in chosen_opts:
|
||
db.add(VoteResponse(
|
||
vote_id=vote.id,
|
||
option_id=chosen.id,
|
||
voter_id=voter.id,
|
||
))
|
||
|
||
await db.flush()
|
||
print(f"[+] {len(VOTES)} votes with options and responses")
|
||
|
||
# ── 10. Assignments with submissions ──────────────────────────────
|
||
for asgn_data in ASSIGNMENTS:
|
||
asgn = Assignment(
|
||
class_id=cls.id,
|
||
creator_id=admin.id,
|
||
title=asgn_data["title"],
|
||
description=asgn_data["description"],
|
||
deadline=now + timedelta(days=asgn_data["deadline_days"]),
|
||
status="open",
|
||
)
|
||
db.add(asgn)
|
||
await db.flush()
|
||
|
||
# Some students already submitted
|
||
submitters = random.sample(students, k=random.randint(3, 8))
|
||
for submitter in submitters:
|
||
sub = AssignmentSubmission(
|
||
assignment_id=asgn.id,
|
||
student_id=submitter.id,
|
||
notes=f"已提交{asgn_data['title']},请老师查阅。",
|
||
file_url=f"https://example.com/submissions/{submitter.student_id}_{asgn.id}.pdf",
|
||
file_name=f"{submitter.name}_{asgn_data['title']}.pdf",
|
||
file_type="pdf",
|
||
file_size=random.randint(200_000, 5_000_000),
|
||
created_at=now - timedelta(days=random.randint(1, 10)),
|
||
)
|
||
# Grade some submissions
|
||
if random.random() > 0.5:
|
||
sub.grade = random.choice(["A", "A-", "B+", "B", "B+"])
|
||
sub.feedback = "分析到位,逻辑清晰,建议进一步深化数据支撑。"
|
||
sub.graded_at = now - timedelta(days=random.randint(0, 3))
|
||
db.add(sub)
|
||
|
||
await db.flush()
|
||
print(f"[+] {len(ASSIGNMENTS)} assignments with submissions")
|
||
|
||
# ── 11. Notifications ─────────────────────────────────────────────
|
||
notif_templates = [
|
||
{"type": "announcement", "title": "新公告发布", "content": "林教授发布了新公告「期末考试安排公布」"},
|
||
{"type": "assignment", "title": "新作业发布", "content": "林教授发布了新作业「个人战略分析报告」"},
|
||
{"type": "vote", "title": "新投票发布", "content": "林教授发起了投票「期末聚餐地点投票」"},
|
||
{"type": "timeline", "title": "动态互动", "content": "{name} 点赞了你的动态"},
|
||
{"type": "timeline", "title": "新评论", "content": "{name} 评论了你的动态"},
|
||
]
|
||
|
||
for student in students:
|
||
# Each student gets 2~4 notifications
|
||
num_notifs = random.randint(2, 4)
|
||
chosen_notifs = random.sample(notif_templates, k=min(num_notifs, len(notif_templates)))
|
||
for tmpl in chosen_notifs:
|
||
other = random.choice([s for s in students if s.id != student.id])
|
||
content = tmpl["content"].format(name=other.name)
|
||
n = Notification(
|
||
user_id=student.id,
|
||
type=tmpl["type"],
|
||
title=tmpl["title"],
|
||
content=content,
|
||
is_read=random.random() > 0.5,
|
||
created_at=now - timedelta(hours=random.randint(1, 72)),
|
||
)
|
||
db.add(n)
|
||
|
||
await db.flush()
|
||
print(f"[+] Notifications created for all students")
|
||
|
||
await db.commit()
|
||
|
||
print("\n✅ Demo data seeded successfully!")
|
||
print("─" * 50)
|
||
print("Login credentials:")
|
||
print(f" Super Admin: admin@hkuicb.info / (from .env)")
|
||
print(f" Class Admin: {CLASS_ADMIN['email']} / demo123")
|
||
print(f" Students: *姓名拼音*@demo.com / demo123")
|
||
print(f" Invite code: HKU2025")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(seed())
|