chengjie 1 dag sedan
förälder
incheckning
ff8b16fd04

+ 14 - 11
秒过分数线数据导入/README.md

@@ -1,7 +1,7 @@
1 1
 # 上海中考招生计划与成绩导入说明
2 2
 
3 3
 本文档记录本项目每年从 PDF/图片整理上海中考招生计划、成绩,并导入 MySQL 表 `kylx365_db.MPS_Score` 的需求、步骤和注意事项。  
4
-当前已完成 2026 年“计划”中的自主招生、名额到区、名额到校,以及黄浦、徐汇、宝山 1-15 志愿。
4
+当前已完成 2026 年“计划”中的自主招生、名额到区、名额到校,以及黄浦、徐汇、静安、虹口、杨浦、宝山 1-15 志愿。
5 5
 
6 6
 ## 年度工作范围
7 7
 
@@ -26,7 +26,7 @@
26 26
 - 计划/自主招生:已导入
27 27
 - 计划/名额到区:已导入
28 28
 - 计划/名额到校:已导入
29
-- 计划/1-15 志愿:黄浦、徐汇、宝山已导入,其他区待文件
29
+- 计划/1-15 志愿:黄浦、徐汇、静安、虹口、杨浦、宝山已导入,其他区待文件
30 30
 - 成绩四类:预计 7 月中旬后导入
31 31
 
32 32
 ## 数据库与核心表
@@ -263,9 +263,9 @@ WHERE ScoreYear = '2025'
263 263
 1-15 志愿:
264 264
 
265 265
 - `import_mps_score_1_15_2026.py`
266
-- 当前包含黄浦、徐汇、宝山两张图片的人工结构化数据。
267
-- 支持 `--dry-run`,写入前校验学校 ID、全称、行数、计划数和学校唯一性。
268
-- 使用 `--district 1`、`--district 2`、`--district 9` 选择区,可重复传入处理多个区。
266
+- 当前包含黄浦、徐汇、静安、虹口、杨浦、宝山图片的人工结构化数据。
267
+- 支持 `--dry-run`,写入前校验学校 ID、全称、行数、计划数和学校 + 备注”唯一性。
268
+- 使用 `--district 1`、`--district 2`、`--district 4`、`--district 6`、`--district 7`、`--district 9` 选择区,可重复传入处理多个区。
269 269
 - 如果所选区已有 2026 数据会拒绝再次插入。
270 270
 - 写入后在同一事务内按学校、备注、计划数、同比差值逐行集合校验,失败自动回滚。
271 271
 - 黄浦区第二张图片、徐汇区第二张图片标题写成“2025 年”,但计划数与新增学校均为 2026 数据,按 2026 导入。
@@ -316,17 +316,20 @@ WHERE ScoreYear = '2025'
316 316
 
317 317
 计划/1-15 志愿:
318 318
 
319
-- 黄浦、徐汇、宝山已导入
319
+- 黄浦、徐汇、静安、虹口、杨浦、宝山已导入
320 320
 - `ScoreYear = 2026`
321 321
 - `ScoreType = 1-15志愿`
322
-- 已导入 194
323
-- 计划数合计 10673
322
+- 已导入 362
323
+- 计划数合计 19113
324 324
 - 黄浦区:67 行、1512 人;本区 1365、外区 147
325 325
 - 徐汇区:68 行、3839 人;本区 3529、外区 310
326
+- 静安区:63 行、3250 人;本区 3150、外区 100
327
+- 虹口区:50 行、1947 人;本区 1868、外区 79
328
+- 杨浦区:55 行、3243 人;本区 3061、外区 182
326 329
 - 宝山区:59 行、5322 人;本区 5091、外区 231
327
-- 相比 2025 年全区总计划:黄浦净增 40、徐汇净增 377、宝山净增 462
330
+- 相比 2025 年全区总计划:黄浦净增 40、徐汇净增 377、静安净增 216、虹口净增 42、杨浦净增 392、宝山净增 462
328 331
 - 重复业务 key 为 0
329
-- 其他 13 个区待图片文件
332
+- 其他 10 个区待图片文件
330 333
 
331 334
 
332 335
 修正记录:
@@ -420,7 +423,7 @@ WHERE SchoolType1 = '初中'
420 423
 
421 424
 计划/1-15 志愿:
422 425
 
423
-- 黄浦、徐汇、宝山已完成。
426
+- 黄浦、徐汇、静安、虹口、杨浦、宝山已完成。
424 427
 - 等其他区 2026 图片文件后继续处理。
425 428
 
426 429
 成绩导入:

+ 228 - 10
秒过分数线数据导入/import_mps_score_1_15_2026.py

@@ -199,6 +199,207 @@ DISTRICT_DATA = {
199 199
         "expected_local_total": 3529,
200 200
         "expected_outside_total": 310,
201 201
     },
202
+    4: {
203
+        "name": "静安区",
204
+        "local": [
205
+            (25, "上海市市西中学", 79),
206
+            (28, "上海市市北中学", 86),
207
+            (26, "上海市育才中学", 91),
208
+            (27, "上海市新中高级中学", 79),
209
+            (30, "上海市第六十中学", 66),
210
+            (31, "上海市华东模范中学", 49),
211
+            (29, "上海市回民中学", 68),
212
+            (101, "上海市民立中学", 280),
213
+            (102, "上海市第一中学", 200),
214
+            (103, "上海市风华中学", 380),
215
+            (104, "上海市彭浦中学", 304),
216
+            (106, "上海市闸北第八中学", 228),
217
+            (105, "上海市久隆模范中学", 50, "(收费生)", None),
218
+            (105, "上海市久隆模范中学", 77),
219
+            (107, "同济大学附属七一中学", 160),
220
+            (1023, "复旦附中静安高级中学", 45),
221
+            (100, "上海戏剧学院附属高级中学", 80, "", None),
222
+            (
223
+                100,
224
+                "上海戏剧学院附属高级中学",
225
+                10,
226
+                "(艺术班)",
227
+                "只招收经学校艺术特长测试合格的学生。",
228
+            ),
229
+            (109, "上海市向东中学", 240),
230
+            (110, "上海大学市北附属中学", 240),
231
+            (108, "上海田家炳中学", 72),
232
+            (111, "上海市民办扬波中学", 103),
233
+            (112, "上海市民办新和中学(停招)", 0),
234
+            (113, "上海市民办风范中学", 163),
235
+        ],
236
+        "outside": [
237
+            (1, "上海市上海中学", 1),
238
+            (4, "华东师范大学第二附属中学", 1),
239
+            (3, "复旦大学附属中学", 1),
240
+            (2, "上海交通大学附属中学", 2),
241
+            (81, "上海市同济黄浦设计创意中学", 1),
242
+            (87, "上海市民办西南高级中学", 2),
243
+            (117, "上海音乐学院附属安师实验中学", 7),
244
+            (122, "上海安生学校", 3),
245
+            (148, "上海市文来中学", 2),
246
+            (156, "上海市民办燎原双语高级中学", 2),
247
+            (153, "上海市民办文绮中学", 1),
248
+            (176, "上海宝山区世外学校", 4),
249
+            (180, "上海市宝山华曜高级中学", 1),
250
+            (177, "上海存志高级中学", 4),
251
+            (178, "上海宝山区民办维尚高级中学", 1),
252
+            (179, "上海创艺高级中学", 2),
253
+            (851, "上海民办行中中学", 2),
254
+            (175, "上海市同洲模范学校", 1),
255
+            (852, "上海金瑞学校", 1),
256
+            (189, "上海华旭双语学校", 2),
257
+            (267, "上海美达菲双语高级中学", 1),
258
+            (190, "上海嘉定区民办华盛怀少学校", 1),
259
+            (230, "上海市民办尚德实验学校", 4),
260
+            (229, "上海浦东新区民办东鼎外国语学校", 1),
261
+            (220, "上海市民办丰华高级中学", 1),
262
+            (218, "上海市浦东新区民办浦实高级中学", 2),
263
+            (223, "民办上海工商外国语职业学院附属中学", 1),
264
+            (244, "上海金山区世外学校", 4),
265
+            (246, "上海市民办永昌中学", 1),
266
+            (243, "上海市民办交大南洋中学", 3),
267
+            (254, "上海赫贤学校", 4),
268
+            (251, "上海市西外外国语学校", 4),
269
+            (859, "上海市松江区科德高级中学", 1),
270
+            (1034, "上海市松江区励滕高级中学", 1),
271
+            (995, "上海松江区爱菊学校", 2),
272
+            (863, "上海青浦区世外高级中学", 2),
273
+            (260, "上海青浦区协和双语学校", 5),
274
+            (862, "上海青浦区宏润博源高级中学", 1),
275
+            (272, "上海民办民一中学", 20),
276
+        ],
277
+        "expected_local_total": 3150,
278
+        "expected_outside_total": 100,
279
+    },
280
+    6: {
281
+        "name": "虹口区",
282
+        "local": [
283
+            (36, "复旦大学附属复兴中学", 86),
284
+            (37, "华东师范大学第一附属中学", 86),
285
+            (38, "上海财经大学附属北郊高级中学", 64),
286
+            (125, "上海音乐学院虹口区北虹高级中学", 270),
287
+            (124, "上海师范大学附属虹口中学", 340),
288
+            (126, "上海市继光高级中学", 262),
289
+            (128, "上海市鲁迅中学", 210),
290
+            (127, "同济大学附属澄衷中学", 270),
291
+            (129, "上海市第五十二中学", 280),
292
+        ],
293
+        "outside": [
294
+            (1, "上海市上海中学", 1),
295
+            (4, "华东师范大学第二附属中学", 1),
296
+            (3, "复旦大学附属中学", 1),
297
+            (2, "上海交通大学附属中学", 2),
298
+            (81, "上海市同济黄浦设计创意中学", 3),
299
+            (105, "上海市久隆模范中学", 1),
300
+            (108, "上海田家炳中学", 3),
301
+            (111, "上海市民办扬波中学", 1),
302
+            (113, "上海市民办风范中学", 5),
303
+            (100, "上海戏剧学院附属高级中学", 4),
304
+            (122, "上海安生学校", 2),
305
+            (117, "上海音乐学院附属安师实验中学", 1),
306
+            (851, "上海民办行中中学", 1),
307
+            (177, "上海存志高级中学", 2),
308
+            (178, "上海宝山区民办维尚高级中学", 1),
309
+            (179, "上海创艺高级中学", 1),
310
+            (180, "上海市宝山华曜高级中学", 3),
311
+            (175, "上海市同洲模范学校", 1),
312
+            (852, "上海金瑞学校", 1),
313
+            (176, "上海宝山区世外学校", 1),
314
+            (188, "上海市民办远东学校", 2),
315
+            (189, "上海华旭双语学校", 4),
316
+            (190, "上海嘉定区民办华盛怀少学校", 4),
317
+            (220, "上海市民办丰华高级中学", 1),
318
+            (228, "上海市民办金苹果学校", 1),
319
+            (229, "上海浦东新区民办东鼎外国语学校", 1),
320
+            (230, "上海市民办尚德实验学校", 3),
321
+            (243, "上海市民办交大南洋中学", 2),
322
+            (246, "上海市民办永昌中学", 2),
323
+            (244, "上海金山区世外学校", 2),
324
+            (860, "上海领科双语学校", 1),
325
+            (251, "上海市西外外国语学校", 3),
326
+            (254, "上海赫贤学校", 1),
327
+            (863, "上海青浦区世外高级中学", 1),
328
+            (862, "上海青浦区宏润博源高级中学", 1),
329
+            (259, "上海宋庆龄学校", 1),
330
+            (260, "上海青浦区协和双语学校", 1),
331
+            (267, "上海美达菲双语高级中学", 1),
332
+            (266, "上海奉贤区博华高级中学", 1),
333
+            (268, "上海市崇明区城桥中学", 2),
334
+            (272, "上海民办民一中学", 8),
335
+        ],
336
+        "expected_local_total": 1868,
337
+        "expected_outside_total": 79,
338
+    },
339
+    7: {
340
+        "name": "杨浦区",
341
+        "local": [
342
+            (40, "上海市控江中学", 120),
343
+            (39, "上海市杨浦高级中学", 123),
344
+            (41, "同济大学第一附属中学", 78),
345
+            (132, "上海理工大学附属中学", 300),
346
+            (133, "上海市市东实验学校(上海市市东中学)", 380),
347
+            (134, "上海财经大学附属中学", 396),
348
+            (137, "上海市复旦实验中学", 315),
349
+            (135, "上海市同济中学", 315),
350
+            (136, "上海市中原中学", 315),
351
+            (138, "上海理工大学附属杨浦少云中学", 225),
352
+            (139, "上海市民星中学", 270),
353
+            (140, "上海体育大学附属中学", 84),
354
+            (142, "上海市民办上实剑桥外国语中学", 140),
355
+        ],
356
+        "outside": [
357
+            (1, "上海市上海中学", 1),
358
+            (4, "华东师范大学第二附属中学", 1),
359
+            (3, "复旦大学附属中学", 1),
360
+            (2, "上海交通大学附属中学", 1),
361
+            (105, "上海市久隆模范中学", 1),
362
+            (268, "上海市崇明区城桥中学", 3),
363
+            (271, "上海市崇明区堡镇中学", 6),
364
+            (100, "上海戏剧学院附属高级中学", 8),
365
+            (117, "上海音乐学院附属安师实验中学", 4),
366
+            (81, "上海市同济黄浦设计创意中学", 2),
367
+            (108, "上海田家炳中学", 2),
368
+            (113, "上海市民办风范中学", 5),
369
+            (122, "上海安生学校", 6),
370
+            (156, "上海市民办燎原双语高级中学", 1),
371
+            (851, "上海民办行中中学", 3),
372
+            (177, "上海存志高级中学", 28),
373
+            (178, "上海宝山区民办维尚高级中学", 3),
374
+            (179, "上海创艺高级中学", 8),
375
+            (180, "上海市宝山华曜高级中学", 2),
376
+            (175, "上海市同洲模范学校", 8),
377
+            (852, "上海金瑞学校", 5),
378
+            (176, "上海宝山区世外学校", 1),
379
+            (188, "上海市民办远东学校", 2),
380
+            (189, "上海华旭双语学校", 5),
381
+            (190, "上海嘉定区民办华盛怀少学校", 7),
382
+            (220, "上海市民办丰华高级中学", 1),
383
+            (223, "民办上海工商外国语职业学院附属中学", 2),
384
+            (228, "上海市民办金苹果学校", 1),
385
+            (229, "上海浦东新区民办东鼎外国语学校", 1),
386
+            (230, "上海市民办尚德实验学校", 2),
387
+            (243, "上海市民办交大南洋中学", 15),
388
+            (246, "上海市民办永昌中学", 1),
389
+            (244, "上海金山区世外学校", 1),
390
+            (255, "上海市松江九峰实验学校", 1),
391
+            (251, "上海市西外外国语学校", 5),
392
+            (863, "上海青浦区世外高级中学", 5),
393
+            (862, "上海青浦区宏润博源高级中学", 4),
394
+            (259, "上海宋庆龄学校", 1),
395
+            (260, "上海青浦区协和双语学校", 2),
396
+            (267, "上海美达菲双语高级中学", 1),
397
+            (266, "上海奉贤区博华高级中学", 2),
398
+            (272, "上海民办民一中学", 23),
399
+        ],
400
+        "expected_local_total": 3061,
401
+        "expected_outside_total": 182,
402
+    },
202 403
     9: {
203 404
         "name": "宝山区",
204 405
         "local": [
@@ -275,6 +476,15 @@ SPECIAL_REMARKS = {
275 476
 }
276 477
 
277 478
 
479
+def unpack_source_row(row):
480
+    school_id, school_name, plan_num = row[:3]
481
+    if len(row) == 5:
482
+        remark, remark2 = row[3:]
483
+    else:
484
+        remark, remark2 = SPECIAL_REMARKS.get(school_id, ("", None))
485
+    return school_id, school_name, plan_num, remark, remark2
486
+
487
+
278 488
 def connect():
279 489
     return pymysql.connect(
280 490
         **DB_CONFIG,
@@ -284,9 +494,14 @@ def connect():
284 494
 
285 495
 
286 496
 def validate_source_data(district_id, data):
287
-    local_total = sum(plan_num for _, _, plan_num in data["local"])
288
-    outside_total = sum(plan_num for _, _, plan_num in data["outside"])
289
-    all_ids = [school_id for school_id, _, _ in data["local"] + data["outside"]]
497
+    local_total = sum(row[2] for row in data["local"])
498
+    outside_total = sum(row[2] for row in data["outside"])
499
+    all_keys = [
500
+        (school_id, remark)
501
+        for school_id, _, _, remark, _ in map(
502
+            unpack_source_row, data["local"] + data["outside"]
503
+        )
504
+    ]
290 505
 
291 506
     if local_total != data["expected_local_total"]:
292 507
         raise ValueError(
@@ -298,15 +513,18 @@ def validate_source_data(district_id, data):
298 513
             f"{data['name']} outside total: expected "
299 514
             f"{data['expected_outside_total']}, got {outside_total}"
300 515
         )
301
-    if len(all_ids) != len(set(all_ids)):
302
-        raise ValueError(f"{data['name']} contains duplicate school IDs")
516
+    if len(all_keys) != len(set(all_keys)):
517
+        raise ValueError(f"{data['name']} contains duplicate school/remark keys")
303 518
     if district_id < 1 or district_id > 16:
304 519
         raise ValueError(f"invalid DistrictID: {district_id}")
305 520
 
306 521
 
307 522
 def load_and_validate_schools(cursor, data):
308 523
     source = data["local"] + data["outside"]
309
-    expected = {school_id: school_name for school_id, school_name, _ in source}
524
+    expected = {
525
+        school_id: school_name
526
+        for school_id, school_name, _, _, _ in map(unpack_source_row, source)
527
+    }
310 528
     placeholders = ", ".join(["%s"] * len(expected))
311 529
     cursor.execute(
312 530
         f"""
@@ -356,8 +574,8 @@ def load_previous_plan_nums(cursor, district_id):
356 574
 
357 575
 def build_records(district_id, data, schools, previous_plan_nums):
358 576
     records = []
359
-    for school_id, _, plan_num in data["local"] + data["outside"]:
360
-        remark, remark2 = SPECIAL_REMARKS.get(school_id, ("", None))
577
+    for source_row in data["local"] + data["outside"]:
578
+        school_id, _, plan_num, remark, remark2 = unpack_source_row(source_row)
361 579
         previous = previous_plan_nums.get((school_id, remark), 0)
362 580
         records.append(
363 581
             {
@@ -482,14 +700,14 @@ def print_summary(district_id, data, records):
482 700
         "rows",
483 701
         len(data["local"]),
484 702
         "plan",
485
-        sum(plan_num for _, _, plan_num in data["local"]),
703
+        sum(row[2] for row in data["local"]),
486 704
     )
487 705
     print(
488 706
         "outside",
489 707
         "rows",
490 708
         len(data["outside"]),
491 709
         "plan",
492
-        sum(plan_num for _, _, plan_num in data["outside"]),
710
+        sum(row[2] for row in data["outside"]),
493 711
     )
494 712
     for row in records:
495 713
         print(