You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
536 lines
30 KiB
536 lines
30 KiB
"""增量补齐2025年青海/山西一分一段与西藏/新疆控制线。
|
|
|
|
只修改脚本中明确列出的省份和数据集;所有来源均保存原件、SHA-256 和导入审计记录。
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import os
|
|
import subprocess
|
|
import uuid
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
|
|
import pandas as pd
|
|
|
|
|
|
YEAR = 2025
|
|
|
|
|
|
def sql_string(value):
|
|
if value is None:
|
|
return "NULL"
|
|
text = str(value).replace("\\", "\\\\").replace("'", "\\'")
|
|
text = text.replace("\x00", "").replace("\r", " ").replace("\n", " ")
|
|
return f"'{text}'"
|
|
|
|
|
|
def digest(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def parse_score_rows(path):
|
|
html_text = path.read_text(encoding="utf-8")
|
|
candidates = []
|
|
for table in pd.read_html(StringIO(html_text)):
|
|
if table.shape[1] not in {3, 7}:
|
|
continue
|
|
rows = []
|
|
triples = [(0, 1, 2)] if table.shape[1] == 3 else [(0, 1, 2), (4, 5, 6)]
|
|
for score_column, same_column, cumulative_column in triples:
|
|
for _, row in table.iterrows():
|
|
score_label = str(row.iloc[score_column])
|
|
if "以下" in score_label or "缺考" in score_label:
|
|
continue
|
|
import re
|
|
match = re.match(r"^\s*(\d+)(?:\s*[-—]\s*\d+)?", score_label)
|
|
if not match:
|
|
continue
|
|
try:
|
|
score = int(match.group(1))
|
|
same = int(float(row.iloc[same_column]))
|
|
cumulative = int(float(row.iloc[cumulative_column]))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
rows.append((score, same, cumulative))
|
|
if len(rows) >= 100:
|
|
candidates.append(rows)
|
|
if len(candidates) != 1:
|
|
raise ValueError(f"{path} 可解析的一分一段表数量异常:{len(candidates)}")
|
|
rows = candidates[0]
|
|
if len({row[0] for row in rows}) != len(rows):
|
|
raise ValueError(f"{path} 存在重复分数")
|
|
if any(rows[index][2] != rows[index - 1][2] + rows[index][1]
|
|
for index in range(1, len(rows))):
|
|
raise ValueError(f"{path} 累计人数校验失败")
|
|
return [
|
|
(score, same, cumulative, max(1, cumulative - same + 1), cumulative, index + 1)
|
|
for index, (score, same, cumulative) in enumerate(rows)
|
|
]
|
|
|
|
|
|
def mysql_command(args, sql, capture=False):
|
|
if not os.environ.get("MYSQL_PWD"):
|
|
raise RuntimeError("必须通过 MYSQL_PWD 环境变量提供数据库密码")
|
|
command = [
|
|
args.mysql_bin, "--protocol=TCP", "-h", args.host, "-P", str(args.port),
|
|
"-u", args.user, "--default-character-set=utf8mb4", "-D", args.database,
|
|
]
|
|
if capture:
|
|
command.extend(["-N", "-B"])
|
|
process = subprocess.run(command, input=sql, text=True, encoding="utf-8", capture_output=True)
|
|
if process.returncode:
|
|
raise RuntimeError(process.stderr.strip() or process.stdout.strip())
|
|
return process.stdout
|
|
|
|
|
|
def source_sql(path, source_type, publisher, title, landing_url, file_url, published_on, notes):
|
|
sha256 = digest(path)
|
|
return f"""
|
|
INSERT INTO data_sources (
|
|
source_type, publisher, title, landing_page_url, file_url, published_on,
|
|
retrieved_at, sha256, raw_file_path, notes
|
|
) VALUES (
|
|
{sql_string(source_type)}, {sql_string(publisher)}, {sql_string(title)},
|
|
{sql_string(landing_url)}, {sql_string(file_url)}, {sql_string(published_on)},
|
|
NOW(), {sql_string(sha256)}, {sql_string(str(path.resolve()))}, {sql_string(notes)}
|
|
) AS incoming ON DUPLICATE KEY UPDATE
|
|
source_type=incoming.source_type, publisher=incoming.publisher, title=incoming.title,
|
|
landing_page_url=incoming.landing_page_url, file_url=incoming.file_url,
|
|
published_on=incoming.published_on, retrieved_at=incoming.retrieved_at,
|
|
raw_file_path=incoming.raw_file_path, notes=incoming.notes;
|
|
SET @source_id=(SELECT source_id FROM data_sources WHERE sha256={sql_string(sha256)});
|
|
"""
|
|
|
|
|
|
def ingestion_sql(dataset_name, rows_read, message):
|
|
run_id = str(uuid.uuid4())
|
|
return f"""
|
|
INSERT INTO ingestion_runs (
|
|
ingestion_run_id, source_id, dataset_name, dataset_year, started_at,
|
|
finished_at, status, rows_read, rows_inserted, rows_updated, rows_rejected, message
|
|
) VALUES (
|
|
{sql_string(run_id)}, @source_id, {sql_string(dataset_name)}, {YEAR}, NOW(), NOW(),
|
|
'success', {rows_read}, {rows_read}, 0, 0, {sql_string(message)}
|
|
);
|
|
"""
|
|
|
|
|
|
def score_values(province, subject, rows):
|
|
return [
|
|
"(" + ",".join([
|
|
sql_string(province), str(YEAR), sql_string("普通类"), sql_string(subject),
|
|
str(score), str(same), str(cumulative), str(rank_from), str(rank_to),
|
|
str(sequence), "@source_id",
|
|
]) + ")"
|
|
for score, same, cumulative, rank_from, rank_to, sequence in rows
|
|
]
|
|
|
|
|
|
def insert_score_sql(province, subject, rows):
|
|
statements = []
|
|
values = score_values(province, subject, rows)
|
|
for start in range(0, len(values), 300):
|
|
statements.append("""
|
|
INSERT INTO score_rank_points (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
score, same_score_count, cumulative_count, rank_from, rank_to,
|
|
source_sequence, source_id
|
|
) VALUES
|
|
""" + ",\n".join(values[start:start + 300]) + ";")
|
|
return "\n".join(statements)
|
|
|
|
|
|
def coverage_sql(province, dataset, status, count, notes, subject="", category=""):
|
|
return f"""
|
|
INSERT INTO dataset_coverage (
|
|
dataset_name, province_name, admission_year, admission_category,
|
|
subject_category, segment_code, coverage_status, record_count, notes, source_id
|
|
) VALUES (
|
|
{sql_string(dataset)}, {sql_string(province)}, {YEAR}, {sql_string(category)},
|
|
{sql_string(subject)}, '', {sql_string(status)}, {count}, {sql_string(notes)}, @source_id
|
|
) AS incoming ON DUPLICATE KEY UPDATE
|
|
coverage_status=incoming.coverage_status, record_count=incoming.record_count,
|
|
notes=incoming.notes, source_id=incoming.source_id;
|
|
"""
|
|
|
|
|
|
def control_values(province, records):
|
|
return ",\n".join(
|
|
"(" + ",".join([
|
|
sql_string(province), str(YEAR), sql_string(category), sql_string(subject),
|
|
sql_string(level), str(culture), "NULL" if professional is None else str(professional),
|
|
sql_string(notes), "@source_id",
|
|
]) + ")"
|
|
for category, subject, level, culture, professional, notes in records
|
|
)
|
|
|
|
|
|
TIBET_CONTROLS = [
|
|
(f"普通类({candidate}类考生)", subject, level, score, None, "普通生源;A/B类考生口径依原公告。")
|
|
for candidate, values in {
|
|
"A": {"文史类": [("本科一批", 338), ("本科二批", 304), ("专科批", 255)],
|
|
"理工类": [("本科一批", 300), ("本科二批", 266), ("专科批", 222)]},
|
|
"B": {"文史类": [("本科一批", 410), ("本科二批", 315), ("专科批", 255)],
|
|
"理工类": [("本科一批", 400), ("本科二批", 305), ("专科批", 222)]},
|
|
}.items()
|
|
for subject, levels in values.items()
|
|
for level, score in levels
|
|
] + [
|
|
(f"艺术体育类({candidate}类考生)", subject, level, score, None,
|
|
"文化成绩控制线;艺术类与体育类专业成绩及排序规则依原公告。")
|
|
for candidate, values in {
|
|
"A": {"文史类": [("本科", 228), ("专科", 179)], "理工类": [("本科", 200), ("专科", 155)]},
|
|
"B": {"文史类": [("本科", 236), ("专科", 179)], "理工类": [("本科", 229), ("专科", 155)]},
|
|
}.items()
|
|
for subject, levels in values.items()
|
|
for level, score in levels
|
|
] + [
|
|
("部队生源", "文史类", "本科", 257, None, "部队生源招生录取最低控制分数线。"),
|
|
("部队生源", "理工类", "本科", 205, None, "部队生源招生录取最低控制分数线。"),
|
|
]
|
|
|
|
|
|
XINJIANG_CONTROLS = [
|
|
(category, subject, level, score, None, "普通高考文化课最低投档控制分数线。")
|
|
for category, values in {
|
|
"普通类计划": {"文科": [("本科一批次", 451), ("本科二批次", 330), ("高职(专科)批次", 140)],
|
|
"理科": [("本科一批次", 421), ("本科二批次", 280), ("高职(专科)批次", 140)]},
|
|
"单列类计划": {"文科": [("本科一批次", 382), ("本科二批次", 319), ("高职(专科)批次", 140)],
|
|
"理科": [("本科一批次", 350), ("本科二批次", 276), ("高职(专科)批次", 140)]},
|
|
}.items()
|
|
for subject, levels in values.items()
|
|
for level, score in levels
|
|
] + [
|
|
(f"体育类({plan}计划)", "不分科类", level, culture, professional,
|
|
"文化课、术科成绩均须达到控制线。")
|
|
for plan, values in {
|
|
"普通类": [("本科提前批次", 245, 86), ("高职(专科)提前批次", 140, 82)],
|
|
"单列类": [("本科提前批次", 240, 89), ("高职(专科)提前批次", 140, 82)],
|
|
}.items()
|
|
for level, culture, professional in values
|
|
] + [
|
|
(f"艺术类({plan}计划)", specialty, level, culture, professional,
|
|
"文化课、术科成绩均须达到控制线;美术本科另要求21门科目成绩各不低于60分。"
|
|
if specialty == "美术与设计类" and level == "本科提前批次B段"
|
|
else "文化课、术科成绩均须达到控制线。")
|
|
for plan, culture_undergrad, culture_vocational in [("普通类", 210, 140), ("单列类", 207, 140)]
|
|
for level, culture, specialties in [
|
|
("本科提前批次B段", culture_undergrad, [
|
|
("美术与设计类", 180), ("音乐类", 170), ("播音与主持类", 185),
|
|
("书法类", 190), ("舞蹈类", 145), ("戏剧影视导演方向", 185),
|
|
("戏剧影视表演方向", 185), ("服装表演方向", 180),
|
|
]),
|
|
("高职(专科)提前批次", culture_vocational, [
|
|
("美术与设计类", 170), ("音乐类", 170), ("播音与主持类", 185),
|
|
("书法类", 190), ("舞蹈类", 145), ("戏剧影视导演方向", 185),
|
|
("戏剧影视表演方向", 185), ("服装表演方向", 180),
|
|
]),
|
|
]
|
|
for specialty, professional in specialties
|
|
]
|
|
|
|
|
|
QINGHAI_CONTROLS = [
|
|
("普通类", subject, level, score, None, "普通班口径;本科含国家、地方、省内专项及普通民族班、民族预科班。")
|
|
for subject, levels in {
|
|
"历史类": [("特殊类型", 450), ("本科批", 405), ("高职(专科)批", 150)],
|
|
"物理类": [("特殊类型", 420), ("本科批", 350), ("高职(专科)批", 150)],
|
|
}.items()
|
|
for level, score in levels
|
|
] + [
|
|
("高校民族语言授课(藏文)", "历史类", "本科普通班及高职(专科)普通班", 366, None,
|
|
"藏文投档成绩,适用于本科普通班(含民族预科班)和高职(专科)普通班。"),
|
|
("高校民族语言授课(藏文)", "物理类", "本科普通班及高职(专科)普通班", 333, None,
|
|
"藏文投档成绩,适用于本科普通班(含民族预科班)和高职(专科)普通班。"),
|
|
("高校民族语言授课(蒙文)", "历史类", "本科普通班", 280, None, "蒙文投档成绩,计算口径依原通知。"),
|
|
("高校民族语言授课(蒙文)", "物理类", "本科普通班", 300, None, "蒙文投档成绩,计算口径依原通知。"),
|
|
("体育类", "历史类", "本科普通班", 385, None, "省统考合格考生投档成绩,非单独文化课线。"),
|
|
("体育类", "历史类", "高职(专科)普通班", 300, None, "省统考合格考生投档成绩,非单独文化课线。"),
|
|
("体育类", "物理类", "本科普通班(含普通民族班)", 335, None, "省统考合格考生投档成绩,非单独文化课线。"),
|
|
("体育类", "物理类", "高职(专科)普通班", 224, None, "省统考合格考生投档成绩,非单独文化课线。"),
|
|
] + [
|
|
("艺术类", subject, f"{level}-{specialty}", score, None,
|
|
"艺术类专业省级统考合格考生的投档成绩,计算方法依原通知。")
|
|
for specialty in [
|
|
"音乐类", "美术与设计类", "舞蹈类", "播音与主持类", "书法类",
|
|
"戏剧影视导演", "服装表演",
|
|
]
|
|
for subject, score_undergrad in [("历史类", 304), ("物理类", 263)]
|
|
for level, score in [("本科", score_undergrad), ("高职(专科)", 150)]
|
|
] + [
|
|
("艺术类", subject, f"{level}-戏剧影视表演", score, None,
|
|
"艺术类专业省级统考合格考生的投档成绩,计算方法依原通知。")
|
|
for subject, score_undergrad in [("历史类", 296), ("物理类", 263)]
|
|
for level, score in [("本科", score_undergrad), ("高职(专科)", 150)]
|
|
]
|
|
|
|
|
|
SHANXI_ART_PROFESSIONAL = [
|
|
("音乐表演(声乐)", 230), ("音乐教育(声乐主项)", 208),
|
|
("音乐表演(器乐)", 240), ("音乐教育(器乐主项)", 230),
|
|
("戏剧影视表演", 240), ("戏剧影视导演", 247), ("服装表演", 228),
|
|
("播音与主持", 241), ("美术与设计", 197), ("舞蹈", 224), ("书法", 236),
|
|
]
|
|
|
|
|
|
SHANXI_CONTROLS = [
|
|
("普通类", "历史类", "普通本科批", 443, None, "普通本科录取最低控制分数线。"),
|
|
("普通类", "历史类", "特殊类型", 534, None, "特殊类型录取控制分数线。"),
|
|
("普通类", "物理类", "普通本科批", 419, None, "普通本科录取最低控制分数线。"),
|
|
("普通类", "物理类", "特殊类型", 507, None, "特殊类型录取控制分数线。"),
|
|
("艺术类", "历史类", "艺术本科提前批-校考", 443, None, "专业成绩须校考合格。"),
|
|
("艺术类", "物理类", "艺术本科提前批-校考", 419, None, "专业成绩须校考合格。"),
|
|
("艺术类", "历史类", "艺术本科提前批-戏曲省际联考", 222, 180, "文化课与省际联考专业成绩控制线。"),
|
|
("艺术类", "物理类", "艺术本科提前批-戏曲省际联考", 210, 180, "文化课与省际联考专业成绩控制线。"),
|
|
] + [
|
|
("艺术类", subject, f"艺术本科批-{specialty}", culture, professional,
|
|
"文化课与专业省级统考成绩均须达到控制线。")
|
|
for subject, culture in [("历史类", 332), ("物理类", 314)]
|
|
for specialty, professional in SHANXI_ART_PROFESSIONAL
|
|
] + [
|
|
("体育类", "历史类", "体育本科批", 354, 85, "文化课与体育专业成绩控制线。"),
|
|
("体育类", "物理类", "体育本科批", 335, 85, "文化课与体育专业成绩控制线。"),
|
|
("普通类", "历史类", "普通专科(高职)提前批及普通专科(高职)批", 100, None, "专科(高职)控制线。"),
|
|
("普通类", "物理类", "普通专科(高职)提前批及普通专科(高职)批", 100, None, "专科(高职)控制线。"),
|
|
("艺术类", "历史类", "艺术专科(高职)批", 75, None, "须取得相应科类专业统考成绩。"),
|
|
("艺术类", "物理类", "艺术专科(高职)批", 75, None, "须取得相应科类专业统考成绩。"),
|
|
("体育类", "历史类", "体育专科(高职)批", 80, None, "须取得体育专业考试成绩。"),
|
|
("体育类", "物理类", "体育专科(高职)批", 80, None, "须取得体育专业考试成绩。"),
|
|
]
|
|
|
|
|
|
def validate_shanxi_existing(args):
|
|
sql = """
|
|
SELECT subject_category, COUNT(*), CAST(MAX(score) AS UNSIGNED), CAST(MIN(score) AS UNSIGNED),
|
|
MAX(cumulative_count)
|
|
FROM score_rank_points
|
|
WHERE province_name='山西省' AND admission_year=2025 AND subject_category LIKE '未标注序列%'
|
|
GROUP BY subject_category ORDER BY subject_category;
|
|
"""
|
|
rows = [line.split("\t") for line in mysql_command(args, sql, capture=True).strip().splitlines()]
|
|
expected = [
|
|
["未标注序列1", "285", "703", "419", "120285"],
|
|
["未标注序列2", "228", "670", "443", "35877"],
|
|
]
|
|
if rows == expected:
|
|
return "unlabeled"
|
|
# 允许脚本重复运行,重复运行时应已经是完整的物理/历史分类。
|
|
sql = """
|
|
SELECT subject_category, COUNT(*), CAST(MAX(score) AS UNSIGNED), CAST(MIN(score) AS UNSIGNED),
|
|
MAX(cumulative_count)
|
|
FROM score_rank_points
|
|
WHERE province_name='山西省' AND admission_year=2025 AND subject_category IN ('物理类','历史类')
|
|
GROUP BY subject_category ORDER BY subject_category;
|
|
"""
|
|
labeled = [line.split("\t") for line in mysql_command(args, sql, capture=True).strip().splitlines()]
|
|
expected_labeled = [
|
|
["历史类", "229", "671", "443", "35877"],
|
|
["物理类", "286", "704", "419", "120285"],
|
|
]
|
|
if labeled == expected_labeled:
|
|
return "labeled"
|
|
raise ValueError(f"山西既有序列不符合预期,停止修改:{rows or labeled}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="增量导入2025年缺失的官方公开数据")
|
|
parser.add_argument("--mysql-bin", required=True)
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=3306)
|
|
parser.add_argument("--user", default="root")
|
|
parser.add_argument("--database", default="zhiyuantong")
|
|
parser.add_argument("--root", default=str(Path(__file__).resolve().parents[1]))
|
|
args = parser.parse_args()
|
|
|
|
root = Path(args.root)
|
|
qh_history_path = root / "data/raw/qinghai/2025/score_rank_历史类_2025.html"
|
|
qh_physics_path = root / "data/raw/qinghai/2025/score_rank_物理类_2025.html"
|
|
sx_physics_path = root / "data/raw/shanxi/2025/score_rank_物理类_2025.html"
|
|
sx_history_image = root / "data/raw/shanxi/2025/score_rank_历史类_2025.png"
|
|
tibet_path = root / "data/raw/tibet/2025/control_lines_2025.html"
|
|
xinjiang_image = root / "data/raw/xinjiang/2025/control_lines_2025.jpg"
|
|
qinghai_controls_pdf = root / "data/raw/qinghai/2025/control_lines_2025.pdf"
|
|
shanxi_controls_bachelor = root / "data/raw/shanxi/2025/control_lines_bachelor_2025.html"
|
|
shanxi_controls_vocational = root / "data/raw/shanxi/2025/control_lines_vocational_2025.html"
|
|
for path in [
|
|
qh_history_path, qh_physics_path, sx_physics_path, sx_history_image, tibet_path,
|
|
xinjiang_image, qinghai_controls_pdf, shanxi_controls_bachelor, shanxi_controls_vocational,
|
|
]:
|
|
if not path.exists():
|
|
raise FileNotFoundError(path)
|
|
|
|
qh_history = parse_score_rows(qh_history_path)
|
|
qh_physics = parse_score_rows(qh_physics_path)
|
|
sx_physics = parse_score_rows(sx_physics_path)
|
|
if (len(qh_history), len(qh_physics), len(sx_physics)) != (638, 662, 286):
|
|
raise ValueError("一分一段解析行数与已核验快照不一致")
|
|
shanxi_state = validate_shanxi_existing(args)
|
|
|
|
statements = ["SET NAMES utf8mb4;", "SET time_zone='+08:00';", "START TRANSACTION;"]
|
|
|
|
statements.append("DELETE FROM score_rank_points WHERE province_name='青海省' AND admission_year=2025 AND admission_category='普通类';")
|
|
statements.append("DELETE FROM dataset_coverage WHERE province_name='青海省' AND admission_year=2025 AND dataset_name='一分一段';")
|
|
for subject, path, rows, url in [
|
|
("历史类", qh_history_path, qh_history, "https://gaokao.eol.cn/qing_hai/dongtai/202506/t20250625_2677002.shtml"),
|
|
("物理类", qh_physics_path, qh_physics, "https://gaokao.eol.cn/qing_hai/dongtai/202506/t20250625_2677001.shtml"),
|
|
]:
|
|
statements.append(source_sql(
|
|
path, "official_republication_html", "中国教育在线(转载青海省教育招生考试院公开数据)",
|
|
f"青海省2025年普通高考一分一段表({subject})", url, None, "2025-06-25",
|
|
"公开转载页正文表格;累计人数逐行校验通过,原件及SHA-256已归档。",
|
|
))
|
|
statements.append(insert_score_sql("青海省", subject, rows))
|
|
statements.append(coverage_sql(
|
|
"青海省", "一分一段", "complete_public_snapshot", len(rows),
|
|
f"完整导入公开表{len(rows)}个分数点;累计人数逐行校验通过,最高分段按公开隐私合并口径保存。", subject, "普通类",
|
|
))
|
|
statements.append(ingestion_sql(f"qinghai_2025_score_rank_{subject}", len(rows), f"青海{subject}一分一段导入并校验通过"))
|
|
|
|
statements.append("DELETE FROM dataset_coverage WHERE province_name='山西省' AND admission_year=2025 AND dataset_name='一分一段';")
|
|
statements.append(source_sql(
|
|
sx_physics_path, "official_republication_html", "中国教育在线(转载山西省招生考试管理中心公开数据)",
|
|
"山西省2025年普通高考成绩(物理科目组合)一分一段统计表",
|
|
"https://gaokao.eol.cn/shan_xi/dongtai/202506/t20250626_2677293.shtml", None, "2025-06-26",
|
|
"公开转载页正文表格;与原未标注序列逐分核对一致,并补回704分最高汇总行。",
|
|
))
|
|
statements.append("DELETE FROM score_rank_points WHERE province_name='山西省' AND admission_year=2025 AND subject_category IN ('未标注序列1','物理类');")
|
|
statements.append(insert_score_sql("山西省", "物理类", sx_physics))
|
|
statements.append(coverage_sql(
|
|
"山西省", "一分一段", "complete_public_snapshot", len(sx_physics),
|
|
"物理科目组合公开表286个分数点;累计人数逐行校验通过。", "物理类", "普通类",
|
|
))
|
|
statements.append(ingestion_sql("shanxi_2025_score_rank_物理类", len(sx_physics), "山西物理类序列完成分类并补回最高分汇总行"))
|
|
|
|
statements.append(source_sql(
|
|
sx_history_image, "official_republication_image", "中国教育在线(图片水印标注山西省招生考试管理中心)",
|
|
"山西省2025年普通高考成绩(历史科目组合)一分一段统计表",
|
|
"https://gaokao.eol.cn/shan_xi/dongtai/202506/t20250626_2677335.shtml",
|
|
"https://t4.chei.com.cn/news/img/2293390948.png", "2025-06-26",
|
|
"公开图片表;与原未标注序列逐分核对一致,并人工核验671分以上汇总行。",
|
|
))
|
|
if shanxi_state == "unlabeled":
|
|
statements.append("""
|
|
UPDATE score_rank_points SET subject_category='历史类', source_id=@source_id,
|
|
source_sequence=source_sequence-286
|
|
WHERE province_name='山西省' AND admission_year=2025 AND subject_category='未标注序列2';
|
|
INSERT INTO score_rank_points (
|
|
province_name, admission_year, admission_category, subject_category, score,
|
|
same_score_count, cumulative_count, rank_from, rank_to, source_sequence, source_id
|
|
) VALUES ('山西省',2025,'普通类','历史类',671,3,11,9,11,1,@source_id);
|
|
""")
|
|
else:
|
|
statements.append("UPDATE score_rank_points SET source_id=@source_id WHERE province_name='山西省' AND admission_year=2025 AND subject_category='历史类';")
|
|
statements.append(coverage_sql(
|
|
"山西省", "一分一段", "complete_public_snapshot", 229,
|
|
"历史科目组合公开表229个分数点;原序列逐分核对一致,已补回671分以上隐私汇总行。", "历史类", "普通类",
|
|
))
|
|
statements.append(ingestion_sql("shanxi_2025_score_rank_历史类", 229, "山西历史类序列完成分类并补回最高分汇总行"))
|
|
|
|
for province, path, source_type, publisher, title, landing_url, file_url, records in [
|
|
("西藏自治区", tibet_path, "official_html", "西藏自治区人民政府(信息来源:西藏自治区教育考试院)",
|
|
"西藏自治区2025年普通高考分数线", "https://www.xizang.gov.cn/xwzx_406/bmkx/202506/t20250626_486252.html",
|
|
None, TIBET_CONTROLS),
|
|
("新疆维吾尔自治区", xinjiang_image, "official_image", "新疆维吾尔自治区教育考试院",
|
|
"新疆2025年普通高校招生各批次最低投档控制分数线",
|
|
"https://www.xjzk.gov.cn/c/2025-06-25/494441.shtml",
|
|
"https://www.xjzk.gov.cn/upload/resources/image/2025/06/25/29511.jpg", XINJIANG_CONTROLS),
|
|
]:
|
|
statements.append(source_sql(
|
|
path, source_type, publisher, title, landing_url, file_url, "2025-06-25",
|
|
"官方公开页或官方图片;已保存原件、SHA-256并人工核对字段。",
|
|
))
|
|
statements.append(f"DELETE FROM control_score_lines WHERE province_name={sql_string(province)} AND admission_year={YEAR};")
|
|
statements.append("""
|
|
INSERT INTO control_score_lines (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
education_level, culture_score, professional_score, notes, source_id
|
|
) VALUES
|
|
""" + control_values(province, records) + ";")
|
|
statements.append(f"DELETE FROM dataset_coverage WHERE province_name={sql_string(province)} AND admission_year={YEAR} AND dataset_name='普通类控制线';")
|
|
statements.append(coverage_sql(
|
|
province, "普通类控制线", "complete_public_snapshot", len(records),
|
|
f"已按官方2025年控制线公告完整结构化{len(records)}条,包含公告中公开的普通、艺体及特殊类别口径。",
|
|
))
|
|
statements.append(ingestion_sql(f"{province}_2025_control_lines", len(records), f"{province}官方控制线完整导入"))
|
|
|
|
statements.append(source_sql(
|
|
qinghai_controls_pdf, "official_pdf_republication", "青海省教育招生考试院",
|
|
"关于发布2025年普通高等学校在青招生录取控制分数线的通知",
|
|
"https://gaokao.chsi.com.cn/gkxx/zc/ss/202506/20250625/2293390791.html",
|
|
"https://imgs.app.gaokaozhitongche.com/resource/pdf/paper/202506/20250625043757_gmn4h2.pdf",
|
|
"2025-06-25", "省考试院通知的公开PDF镜像;4页均已渲染人工核验,原件及SHA-256已归档。",
|
|
))
|
|
statements.append("DELETE FROM control_score_lines WHERE province_name='青海省' AND admission_year=2025;")
|
|
statements.append("""
|
|
INSERT INTO control_score_lines (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
education_level, culture_score, professional_score, notes, source_id
|
|
) VALUES
|
|
""" + control_values("青海省", QINGHAI_CONTROLS) + ";")
|
|
statements.append("DELETE FROM dataset_coverage WHERE province_name='青海省' AND admission_year=2025 AND dataset_name='普通类控制线';")
|
|
statements.append(coverage_sql(
|
|
"青海省", "普通类控制线", "complete_public_snapshot", len(QINGHAI_CONTROLS),
|
|
f"已按省考试院通知完整结构化{len(QINGHAI_CONTROLS)}条,包含普通、民族语言、体育和艺术口径。",
|
|
))
|
|
statements.append(ingestion_sql("qinghai_2025_control_lines", len(QINGHAI_CONTROLS), "青海官方控制线完整导入"))
|
|
|
|
statements.append(source_sql(
|
|
shanxi_controls_bachelor, "official_republication_html", "山西省教育考试服务中心(来源:山西省招生考试管理中心)",
|
|
"山西省2025年普通高校招生本科录取最低控制分数线公告",
|
|
"https://oa.sxjyksfw.cn/news/ptgk/20250625/n2025062569813774.html", None,
|
|
"2025-06-25", "官方教育考试服务网转载省招考中心公告;本科、艺术和体育控制线。",
|
|
))
|
|
statements.append("DELETE FROM control_score_lines WHERE province_name='山西省' AND admission_year=2025;")
|
|
bachelor_count = len(SHANXI_CONTROLS) - 6
|
|
statements.append("""
|
|
INSERT INTO control_score_lines (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
education_level, culture_score, professional_score, notes, source_id
|
|
) VALUES
|
|
""" + control_values("山西省", SHANXI_CONTROLS[:bachelor_count]) + ";")
|
|
statements.append(ingestion_sql("shanxi_2025_control_lines_bachelor", bachelor_count, "山西本科、艺术和体育控制线导入"))
|
|
statements.append(source_sql(
|
|
shanxi_controls_vocational, "official_republication_html", "山西省教育考试服务中心(来源:山西省招生考试管理中心)",
|
|
"山西省2025年普通高校招生专科(高职)批录取最低控制分数线公告",
|
|
"https://oa.sxjyksfw.cn/news/ptgk/20250814/n2025081480893780.html", None,
|
|
"2025-08-14", "官方教育考试服务网转载省招考中心公告;普通、艺术和体育专科控制线。",
|
|
))
|
|
statements.append("""
|
|
INSERT INTO control_score_lines (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
education_level, culture_score, professional_score, notes, source_id
|
|
) VALUES
|
|
""" + control_values("山西省", SHANXI_CONTROLS[bachelor_count:]) + ";")
|
|
statements.append("DELETE FROM dataset_coverage WHERE province_name='山西省' AND admission_year=2025 AND dataset_name='普通类控制线';")
|
|
statements.append(coverage_sql(
|
|
"山西省", "普通类控制线", "complete_public_snapshot", len(SHANXI_CONTROLS),
|
|
f"已按本科与专科两份官方公告完整结构化{len(SHANXI_CONTROLS)}条,包含普通、艺术和体育口径。",
|
|
))
|
|
statements.append(ingestion_sql("shanxi_2025_control_lines_vocational", 6, "山西普通、艺术和体育专科控制线导入"))
|
|
|
|
statements.append("""
|
|
UPDATE collection_scopes SET collection_status='partial', priority_level='high',
|
|
status_note='已补齐2025年普通类历史/物理一分一段公开表;计划、投档线及批次规则继续补充。',
|
|
first_collected_at=COALESCE(first_collected_at,NOW()), last_collected_at=NOW()
|
|
WHERE province_name='青海省' AND admission_year=2025;
|
|
UPDATE collection_scopes SET collection_status='partial', priority_level='high',
|
|
status_note='已完成2025年普通类历史/物理一分一段科类纠正及最高分汇总行补齐;专业录取线继续补充。',
|
|
first_collected_at=COALESCE(first_collected_at,NOW()), last_collected_at=NOW()
|
|
WHERE province_name='山西省' AND admission_year=2025;
|
|
UPDATE collection_scopes SET collection_status='partial', priority_level='high',
|
|
status_note='已补齐2025年官方控制线;一分一段及其他公开招录数据继续补充。',
|
|
first_collected_at=COALESCE(first_collected_at,NOW()), last_collected_at=NOW()
|
|
WHERE province_name IN ('西藏自治区','新疆维吾尔自治区') AND admission_year=2025;
|
|
""")
|
|
statements.append("COMMIT;")
|
|
mysql_command(args, "\n".join(statements))
|
|
print(
|
|
f"导入完成:青海一分一段{len(qh_history) + len(qh_physics)}条;"
|
|
f"山西一分一段{len(sx_physics) + 229}条;西藏控制线{len(TIBET_CONTROLS)}条;"
|
|
f"新疆控制线{len(XINJIANG_CONTROLS)}条。"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|