README.md 11 KB

上海中考招生计划与成绩导入说明

本文档记录本项目每年从 PDF/图片整理上海中考招生计划、成绩,并导入 MySQL 表 kylx365_db.MPS_Score 的需求、步骤和注意事项。
当前已完成 2026 年“计划”中的 1、2、3:自主招生、名额到区、名额到校。

年度工作范围

每年需要处理两大类数据。

一、计划

  1. 自主招生
  2. 名额到区
  3. 名额到校
  4. 1-15 志愿

二、成绩

  1. 自主招生
  2. 名额到区
  3. 名额到校
  4. 1-15 志愿

2026 年当前状态:

  • 计划/自主招生:已导入
  • 计划/名额到区:已导入
  • 计划/名额到校:已导入
  • 计划/1-15 志愿:待官方文件发布后导入
  • 成绩四类:预计 7 月中旬后导入

数据库与核心表

目标数据库:kylx365_db

核心表:

  • MPS_School:学校表,所有学校相关信息以此表为准。
  • MPS_Score:计划与成绩表,所有导入结果写入此表。

常用参照查询:

SELECT *
FROM kylx365_db.MPS_School
WHERE SchoolType1 = '高中';
SELECT *
FROM kylx365_db.MPS_Score
WHERE ScoreYear = '2025'
  AND ScoreType = '名额到校'
  AND DistrictID = 1;

数据库连接信息不要写入 README 或提交到仓库。当前脚本里使用本机已有配置和 PyMySQL 驱动连接,后续最好抽成单独的本地配置文件或环境变量。

DistrictID 对照

1  黄浦区
2  徐汇区
3  长宁区
4  静安区
5  普陀区
6  虹口区
7  杨浦区
8  闵行区
9  宝山区
10 嘉定区
11 浦东新区
12 金山区
13 松江区
14 青浦区
15 奉贤区
16 崇明区

MPS_Score 写入规则

计划类导入一般只写计划数,不写成绩。

通用字段规则:

  • ScoreYear:年份,例如 2026
  • ScoreType自主招生名额到区名额到校1-15志愿
  • DistrictID:对应区 ID
  • SchoolTarget:高中学校 MPS_School.ID,以字符串写入
  • SchoolFullName:必须使用高中 ID 对应的 MPS_School.SchoolFullName
  • PlanNum:计划人数
  • ScoreTotalScore1Score2Score3Score4:计划导入时填 0
  • ScoreTotalDifferenceValue:计划导入时填 0
  • PlanNumDifferenceValue:当前计划数减去上一年同维度计划数
  • OrderID:当前计划导入填 0
  • SchoolNumberSchoolNumber2:当前计划导入填空字符串
  • SchoolOfGraduation1:当前计划导入填 "0"

名额到校额外规则:

  • SchoolOfGraduation:初中学校 MPS_School.ID
  • SchoolFullNameJunior:必须使用初中 ID 对应的 MPS_School.SchoolFullName
  • 一条数据的唯一业务维度可按 ScoreYear + ScoreType + DistrictID + SchoolOfGraduation + SchoolTarget 理解。
  • 不能用 PDF 里的简称直接写入 SchoolFullNameJuniorSchoolFullName

自主招生额外规则:

  • 普通自主招生拆成:
    • 1学科
    • 2体育
    • 3艺术
  • 国际课程班/中外合作办学拆成:
    • 4国际(本市)
    • 5国际(非本市)
  • SchoolTargetRemark2 可参考上一年同学校同类别备注;体育/艺术通常沿用“市级优秀体育学生”“市级艺术骨干学生”等说明。

名额到区额外规则:

  • SchoolOfGraduation = 0
  • SchoolFullNameJunior = NULL
  • SchoolTargetRemark = ""
  • 维度是“区 + 高中”。

总体操作流程

每一类数据都按以下流程做:

  1. 先研究上一年 PDF 与上一年数据库数据,确认字段含义和写入形态。
  2. 读取新一年 PDF/图片,优先用表格解析;表格解析失败或 PDF 其实是图片时再 OCR/人工读图。
  3. 先匹配学校,不确定的数据不要导入,写入问题清单。
  4. 先 dry-run 或打印 ready 汇总,核对每区行数和计划数。
  5. 只插入新数据,不删除、不修改已有数据。
  6. 导入后查询 MPS_Score 总行数、总计划数、分区汇总。
  7. 对问题学校更新 MPS_School 后,再运行补录脚本,只补缺失行,并刷新问题清单。

重要原则:

  • 凡是弄不清楚的,先不入库,放入 JSON 问题清单。
  • 若某个区解析问题较多,整个区可以先不动,等其他区处理完再单独解决。
  • 每次补录必须跳过已存在业务 key,避免重复插入。
  • 新增/改名学校优先修正 MPS_School,再重新匹配导入。

PDF/图片解析经验

优先级:

  1. 有 6 位学校编号:优先用编号匹配。
  2. 有学校全称:用 SchoolFullName 匹配。
  3. 有简称或别名:用 SchoolShortNameSchoolOtherName 匹配。
  4. 仍不能唯一匹配:列为问题数据。

学校名称常见问题:

  • PDF 中会使用简称,而且初中简称比高中多。
  • 有学校改名,PDF 可能写成“原名(现新名/校区)”。
  • 有新增学校,学校表中原本没有。
  • OCR 可能把换行、空格、序号、备注混进学校名。
  • 部分 PDF 表格中的学校名可能被拆成多行,需要清理换行再匹配。

本次经验:

  • 高中通常有 6 位编号,匹配相对稳定。
  • 名额到校的初中数量多,名称最容易出问题。
  • SchoolOtherName 很适合放改名后的现名或曾用名。
  • 对“原名(现某某)”这种文本,匹配时应同时尝试原名、括号内现名、去括号名称。
  • 图片清晰时可以 OCR/读图解决,但要把结果转成结构化行,再按学校表 ID 入库。

当前脚本说明

脚本分为三类:主流程脚本、公共解析/补录脚本、2026 一次性补充脚本。后续年度工作时,主流程和公共脚本可以复制改年份;一次性补充脚本主要用于追溯 2026 的特殊处理,不建议直接运行到新年份。

自主招生:

  • import_mps_score_2026.py
  • 读取 2026 自主招生计划 PDF 与国际课程班/中外合作办学 PDF。
  • 导入 ScoreType = '自主招生'
  • 脚本会在已有 2026 自主招生数据时拒绝再次插入。

名额到区:

  • import_mps_score_quota_2026.py
  • 读取 16 个区的名额到区 PDF。
  • 支持 --dry-run
  • 如果某区已存在数据,会跳过并报告。
  • 对图片或解析失败区,使用 import_mps_score_quota_manual_2026.py 做手工/OCR 补充。

名额到校:

  • research_mps_score_school_quota_2026.py
  • 负责学校加载、名称清洗、PDF 表格解析、学校匹配。
  • 已支持编号匹配、全称/简称/别名匹配、括号内“现名”匹配、同区唯一包含式匹配。

  • import_mps_score_school_quota_2026.py

  • 主导入脚本,读取 16 个区名额到校 PDF。

  • 支持 --dry-run

  • 解析不确定的数据写入 mps_score_school_quota_2026_problems.json

  • import_mps_score_school_quota_supplement_2026.py

  • 用于补充处理徐汇、嘉定等表格/OCR特殊区。

  • import_mps_score_school_quota_hongkou_2026.py

  • 用于处理虹口图片读图后的结构化数据。

  • fix_mps_score_school_quota_problems_2026.py

  • MPS_School 中新增/修正学校后,重新解析问题区并补插当前能匹配的数据。

  • 会跳过数据库中已存在的 DistrictID + SchoolOfGraduation + SchoolTarget 组合。

  • 会刷新 mps_score_school_quota_2026_problems.json,只保留仍未解决的问题。

2026 一次性补充脚本:

  • import_mps_score_quota_manual_2026.py:用于 2026 名额到区图片/OCR特殊区的手工补录,不是新年份通用入口。
  • import_mps_score_school_quota_hongkou_2026.py:用于 2026 虹口名额到校图片读图后的手工矩阵导入,不是新年份通用入口。
  • import_mps_score_school_quota_supplement_2026.py:包含 2026 徐汇手工矩阵和嘉定特殊 PDF 解析;其中 collect_jiading 目前仍被 fix_mps_score_school_quota_problems_2026.py 引用,所以不要单独删除。

生成物:

  • __pycache__/*.pyc 是 Python 运行缓存,不属于业务数据或脚本,已在主仓库 .gitignore 中忽略。

2026 已完成结果

计划/自主招生:

  • ScoreYear = 2026
  • ScoreType = 自主招生
  • 已导入 265 行
  • 计划数合计 7813

计划/名额到区:

  • ScoreYear = 2026
  • ScoreType = 名额到区
  • 已导入 947 行
  • 计划数合计 7171

计划/名额到校:

  • ScoreYear = 2026
  • ScoreType = 名额到校
  • 已导入 3892 行
  • 计划数合计 12833
  • 问题清单 mps_score_school_quota_2026_problems.json 已清空

2026 名额到校最终分区汇总:

DistrictID 行数 计划数
1 黄浦区 217 996
2 徐汇区 221 899
3 长宁区 63 418
4 静安区 271 1102
5 普陀区 179 736
6 虹口区 80 488
7 杨浦区 144 707
8 闵行区 460 1290
9 宝山区 348 1076
10 嘉定区 130 612
11 浦东新区 1259 2082
12 金山区 56 355
13 松江区 190 779
14 青浦区 93 725
15 奉贤区 131 345
16 崇明区 50 223

常用核验 SQL

总量:

SELECT COUNT(*) AS c, SUM(PlanNum) AS total
FROM MPS_Score
WHERE ScoreYear = '2026'
  AND ScoreType = '名额到校';

分区:

SELECT DistrictID, COUNT(*) AS c, SUM(PlanNum) AS total
FROM MPS_Score
WHERE ScoreYear = '2026'
  AND ScoreType = '名额到校'
GROUP BY DistrictID
ORDER BY DistrictID;

检查某区上一年参照:

SELECT *
FROM MPS_Score
WHERE ScoreYear = '2025'
  AND ScoreType = '名额到校'
  AND DistrictID = 1
ORDER BY ID;

查初中学校名称:

SELECT ID, DistrictID, SchoolNumber, SchoolFullName, SchoolShortName, SchoolOtherName
FROM MPS_School
WHERE SchoolType1 = '初中'
  AND (
    SchoolFullName LIKE '%学校名关键词%'
    OR SchoolShortName LIKE '%学校名关键词%'
    OR SchoolOtherName LIKE '%学校名关键词%'
  );

明年复制脚本时要改的地方

把脚本从 2026 复制到新年份后,至少检查这些常量:

  • YEAR
  • PREVIOUS_YEAR
  • BASE_DIR
  • PDF 文件名
  • 问题 JSON 文件名
  • 特殊区手工数据脚本中的高中代码、初中代码、计划矩阵
  • 自主招生中国际课程班 PDF 名称

导入前必须确认目标年份目标类型没有已有数据,或脚本明确支持跳过/补录。
不要为了重新跑脚本而删除数据库旧数据,除非明确确认要重做且已备份。

待办

计划/1-15 志愿:

  • 等 2026 官方文件发布后处理。
  • 需要先研究 2025 的 PDF 与数据库写入形态,再决定 ScoreType、字段、维度和差值计算。

成绩导入:

  • 预计 7 月中旬后开始。
  • 四类成绩都要先研究上一年数据。
  • 成绩类导入会涉及 ScoreTotalScore1Score2Score3Score4 等字段,不能沿用计划类全部填 0 的规则。
  • 成绩导入前要明确每个分数列的含义、缺考/无分/未录取的表示方式,以及是否需要计算差值。