有关志愿通的代码仓库
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

"""增量补齐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()