feat(timetable): 课表底座——pku_account 北大账号绑定 + timetable 课表/导入/ICS + 小程序 API - #1012
Draft
tangmc0210 wants to merge 15 commits into
Draft
feat(timetable): 课表底座——pku_account 北大账号绑定 + timetable 课表/导入/ICS + 小程序 API#1012tangmc0210 wants to merge 15 commits into
tangmc0210 wants to merge 15 commits into
Conversation
This was referenced Sep 9, 2026
tangmc0210
force-pushed
the
feat/timetable
branch
from
September 10, 2026 01:45
0c36f3a to
ba4ff59
Compare
…contract Add empty pku_account and timetable apps, register them in INSTALLED_APPS, mount /api/v2/pku/, /api/v2/timetable/ and the /timetable/ ICS route, add pku_portal and timetable config sections, and document the models, source registry and API contract in timetable/README.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…session vault Add the pku_account app: requests-based IAAA login and portal client (course info, scores, liveness probe), PkuAccount/PkuPortalSession models, Fernet-encrypted cookie storage, brute-force lock, consent flags, and the /api/v2/pku/ binding, login, unbind and consents endpoints with tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…S feed and mini-program API Add the timetable app: AcademicTerm/TimetableEntry/ImportLog/TimetableSettings, stdlib parsers for the portal course JSON/HTML and elective 选课结果 (HTML and plain text), config-registered event sources (stored entries, 书院课 via Course/CourseTime/Activity, applied activities, basement appointments), week view with conflict detection, portal/text import with upsert-by-source semantics, an RFC 5545 ICS feed, the timetable_seed_terms command, and the /api/v2/timetable/ endpoints with tests. Update the README contract with the agreed refinements. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
Serialize imports per person with a NaturalPerson row lock so a first import cannot race on the entry unique key; show canceled/aborted 书院课 activities as status 'canceled' instead of dropping them and ignore them in conflict detection; require a valid person account in the timetable API like the pku_account API; default pku_portal.enabled to false in config_template.json (enable per deployment). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
Add the academic_record app: portal score parser (nested and flattened shapes, non-numeric scores), GradeRecord model stored only with the student's grades consent, GPA summaries, the grades/sync/delete endpoints with the {code, message} error envelope, a pku_account consent_changed signal emitted after commit, and receivers that delete stored grades when consent is revoked or the binding is removed. Document the as-built behaviour in the timetable README.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…talog import/search Add SubscribeQuota/ReminderLog/CourseCatalogEntry, the subscribe-message sender (extern/wx_miniapp.py), the 5-minute reminder job with notification fallback, quota grant/templates endpoints, catalog parsing, the import_course_catalog command and catalog search API with tests; document the new config keys in config_template.json and the as-built notes in the README; list the new API modules in api/AGENTS.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…art, keep appsecret out of logs Look back three job intervals (ReminderLog keeps re-scans idempotent) so a short scheduler outage does not drop reminders, skip occurrences whose class already started, and log only the exception class when the WeChat access-token request fails because requests embeds the URL (with the appsecret) in the message. Document the academic-calendar contract (README §6.4). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
… timetable expansion Add semester.CalendarEvent (global, keyed by date; editable in admin) and semester.calendar, the platform-wide read API (is_class_day, effective_weekday, AcademicCalendar) that is queried fresh at use time; make timetable expansion, week view (days labels), Term.calendar and the ICS feed honour it; add the import_academic_calendar command with the 2026-2027 fall and spring seed files transcribed from the official PKU calendar. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…four-source toggles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…ng all source toggles Add TimetableSettings.show_courses, make every source (stored entries, 书院课, activities, appointments) respect the settings in the ICS feed as well, add date-span queries to the live sources and the GET /api/v2/timetable/agenda/ endpoint (consecutive days across term boundaries with calendar labels) for the home-page agenda, with tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…ggles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…nt envelopes
Validation errors now carry errors: {field: [{code, message}]}, the shape of the platform-wide envelope introduced by api/exceptions.py (PR Yuanpei-Intelligence#1014) and parsed by the mini-program's unified RequestError (YPPF-mini Yuanpei-Intelligence#10). Switching these modules to StandardizedExceptionHandlerMixin is a mechanical follow-up once Yuanpei-Intelligence#1014 lands.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
…telligence#1014 follow-up Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
… and share assets Iteration 3 of the timetable (README §8): - Entries link to the course catalog on import (blank fields filled, the student's values win) and carry role (已选/旁听), category (course/exam/ other) and a free tag; POST catalog/<id>/add/ adds a catalog course as 旁听. - Per-week overrides (TimetableEntryOverride) back PATCH entries/<id>/ with scope all/single/following and a canceled flag; imported entries keep the student's edits across re-imports; GET entries/<id>/ and DELETE overrides. - Settings expose the registered sources, the student's tags and hidden_tags; a new exam source matches the term's CourseExam rows (import_exam_schedule) to the student's courses; terms gain exam_week_start / teaching_weeks and the seed calendars cover the exam weeks. - GET share/assets/ serves the mini-program code (wxacode, cached) and the official-account QR code for the poster. - Spectacular enum names pinned for the new choice sets. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
tangmc0210
force-pushed
the
feat/timetable
branch
from
September 10, 2026 03:18
25f12db to
fecd33b
Compare
…d a deployment checklist - static/assets/img/yppf_official_qrcode.png is the default wx_miniapp.share.official_qrcode_url; a leading slash now means a site path joined with global.base_url (MEDIA_URL only for bare names). - New management command `timetable_check` prints the README §9 deployment checklist as [OK]/[WARN]/[FAIL] lines (migrations, config, sources, subscribe template, share assets, jobs, terms / calendar / catalog / exams) and exits non-zero on failures unless --warn-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
面向小程序的课表功能后端(需求池「课表」及其下游「晒课表」「课表活动」的底座),按可插拔方式拆成三个新 app,契约见
timetable/README.md:pku_account— 北大账号绑定:IAAA/门户客户端(requests,无业务依赖)、PkuAccount(YPPF 用户 ↔ 北大账号,一对一、含课表/成绩授权项)、PkuPortalSession(Fernet 加密的门户 cookie,不存密码)、失败计数与锁定、授权变更信号、/api/v2/pku/{binding,login,unbind,consents}/。timetable—AcademicTerm/TimetableEntry/ImportLog/TimetableSettings;纯标准库解析器(门户getCourseInfo.doJSON、门户「我的课表」HTML、选课系统「选课结果」表格与纯文本);通过config.json → timetable.sources注册的事件源(已存条目、书院课Course/CourseTime/Activity、已报名活动、地下室预约、考试安排);周视图 + 冲突检测;门户/粘贴导入(按 (person, term, source) upsert,不动其它来源与历史学期);RFC 5545 ICS 订阅GET /timetable/ics/<token>.ics;上课提醒(微信订阅消息 + 站内通知/企微推送回退,5 分钟 job);全校课程库(import_course_catalog命令吃 PKU-Course-Crawler 的 xlsx,检索 API 供手动添加联想);/api/v2/timetable/路由。academic_record— 成绩:门户成绩解析、GradeRecord仅在学生勾选成绩授权后落库,撤销授权或解绑即删除;/api/v2/grades/三个端点。GET /api/v2/timetable/agenda/?from=&days=(跨学期连续日期、带校历标签,供小程序首页「我的日程」与单日视图);TimetableSettings.show_courses使学校课表/书院课/活动/预约四个来源在周视图、日程、ICS 导出、上课提醒中统一可勾选。errors: {字段: [{code, message}]}(与api/exceptions.py及小程序 Develop #10 的解析器一致);Unify exception handling in miniprogram API #1014 合并后改用StandardizedExceptionHandlerMixin即可。semester.CalendarEvent+semester.calendar) — 全局、按日期的放假 / 考试周 / 调休 / 标注事件表,admin 可随时改、import_academic_calendar可按学期 JSON 重复导入覆盖(附 2026–2027 两学期从官方 PDF 转录的种子文件);semester.calendar是全平台读接口(is_class_day/effective_weekday),课表周视图、ICS、提醒在读取时实时查询,放假日不出课、调休日按被换星期出课;活动等其它模块以后也从这里读,不再各自写死。依赖方向单向:
pku_account←timetable← 适配器(懒导入app/Appointment),academic_record←pku_account。app/、Appointment/不引用新 app。取消一个数据源、提醒通道或整个门户登录只需改config.json。与 #999 的关系:本 PR 取代其「教务课表」部分(并修正了节次时间表:北大为 50 分钟制),已在 #999 留言请作者收窄为日程表页与手动日程,改为读本 app 的数据。
第三轮(2026-09-10,README §8)
CourseCatalogEntry(只补空字段,学生自己的值优先,重导入不改学生的标注);POST catalog/<id>/add/一键把课程库课程加为旁听(每个时段一条,role=audit);条目新增role / category / tag,备注改长文本;GET catalog/返回院系 / 类别 / 学分 / 周次与added。TimetableEntryOverride(JSON 覆盖字段 +canceled+ 周范围,窄范围优先);PATCH entries/<id>/加scope/week,门户 / 粘贴条目的修改存为整段覆盖、重导入不丢;GET entries/<id>/详情(课程库信息、覆盖记录、考试),DELETE entries/<id>/overrides/[<oid>/]恢复;周视图 / 日程 / ICS / 提醒统一消费覆盖后的日程(Occurrence.role/tag/modified)。settings/返回已注册来源(含控制它的setting键)与我用过的标签,接受hidden_tags/show_exams——小程序的筛选弹层不再写死四个来源。AcademicTerm.exam_week_start+teaching_weeks,total_weeks改为含考试周(秋 19 / 春 18,校历种子已更新);CourseExam+import_exam_schedule(教务部 xlsx/csv,识别多种时间写法)+exam源自动按课程号 / 课程名匹配到学生课表;条目详情带exam。门户是否有个人考试接口未知(需真实账号探测),暂无按学生导入。GET share/assets/(wxacode.getUnlimited小程序码 30 天文件缓存 + 公众号二维码 + 口号)。公众号二维码随仓库提供(static/assets/img/yppf_official_qrcode.png,模板默认值;配置值以/开头视为站内路径拼base_url,裸文件名走MEDIA_URL)。python manage.py timetable_check按 README §9 清单逐项输出[OK]/[WARN]/[FAIL](迁移、配置、来源、订阅模板、海报素材、定时任务、学期 / 校历 / 课程库 / 考试表),有 FAIL 时非零退出(--warn-only可只报告)。SPECTACULAR_SETTINGS['ENUM_NAME_OVERRIDES']固定新增枚举的组件名(否则与活动category冲突产生告警)。部署注意
config.json新增pku_portal、timetable、wx_miniapp.subscribe_templates、wx_miniapp.share段(见config_template.json)。pku_portal.enabled模板默认false,生产需显式设为true;session_key留空则由SECRET_KEY派生;timetable.sources需加timetable.sources.exam.ExamSource。pku_account.0001、timetable.0001–0004(0004 含手动条目类别的数据步骤)、academic_record.0001。python manage.py import_academic_calendar timetable/data/calendar_26-27-1.json(同时建/更新AcademicTerm 26-27-1、total_weeks/exam_week_start与校历事件;春季用calendar_26-27-2.json;元旦 2027 待国办公布后在 admin 加一行)。考试安排:python manage.py import_exam_schedule <教务部表格> --term 26-27-1。collect_jobs会自动发现timetable/jobs.py,runscheduler照常;订阅消息需在小程序后台申请模板后把 id 填进wx_miniapp.subscribe_templates.class_reminder.id(留空则只走站内通知 + 企微)。MEDIA_ROOT需对外可访问,小程序后台把后端域名加进 downloadFile 合法域名;公众号二维码已随仓库提供,wx_miniapp.share.official_qrcode_url保持模板默认值即可。python manage.py timetable_check看清单(README §9)。POST /api/v2/pku/login/。验证
python manage.py test全量 934 OK(三轮共新增约 580 个用例:pku_account、timetable(含 overrides / exams / share / catalog 匹配)、academic_record及各自 API),manage.py check/makemigrations --check干净,spectacular无新增告警。S000063,后端跑在 dev container):真实微信登录 → 绑定 → 课表;粘贴导入 → 周视图 → 详情 → 海报;成绩授权门;课程库「添加为旁听」→ 201 → 课表出现「旁」格子;「仅本次」编辑 →PATCH scope=single→ 格子显示新地点;「本次停课」→ 格子消失;「恢复默认」→DELETE overrides204;首页筛选关掉课程 → 日程清空、角标 4/5;第 19 周显示匹配到的考试红块;四套海报渲染。对 pku.edu.cn / weixin 的请求在测试中全部 mock。小程序前端见配套 PR Yuanpei-Intelligence/YPPF-mini#9。
🤖 Generated with Claude Code
https://claude.ai/code/session_012t4BD5H1P5nn2zF7oU6q9e