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.
541 lines
28 KiB
541 lines
28 KiB
import argparse
|
|
import csv
|
|
import hashlib
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import uuid
|
|
from datetime import date
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
|
|
import pandas as pd
|
|
|
|
|
|
PROVINCE = "甘肃省"
|
|
YEAR = 2024
|
|
|
|
|
|
def sql_string(value):
|
|
if value is None or value == "":
|
|
return "NULL"
|
|
text = str(value).replace("\\", "\\\\").replace("'", "\\'")
|
|
text = text.replace("\x00", "").replace("\r", " ").replace("\n", " ")
|
|
return f"'{text}'"
|
|
|
|
|
|
def sql_text(value):
|
|
return sql_string(value)
|
|
|
|
|
|
def sql_number(value):
|
|
if value is None or str(value).strip() == "":
|
|
return "NULL"
|
|
text = re.sub(r"\.0$", "", str(value).strip())
|
|
if not re.fullmatch(r"-?\d+(?:\.\d+)?", text):
|
|
raise ValueError(f"不是有效数值: {value!r}")
|
|
return text
|
|
|
|
|
|
def normalize_code(value, width):
|
|
text = re.sub(r"\.0$", "", str(value or "").strip())
|
|
if not text:
|
|
return None
|
|
return text.zfill(width) if text.isdigit() else text
|
|
|
|
|
|
def file_digest(path):
|
|
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
|
|
|
|
|
def read_csv_rows(path, expected_columns):
|
|
with Path(path).open("r", encoding="utf-8-sig", newline="") as handle:
|
|
rows = list(csv.DictReader(handle))
|
|
if len(rows) < 3:
|
|
raise ValueError(f"文件无有效数据: {path}")
|
|
actual = [str(rows[1].get(f"col{i}") or "").replace("\n", "").replace(" ", "") for i in range(1, len(expected_columns) + 1)]
|
|
expected = [item.replace("\n", "").replace(" ", "") for item in expected_columns]
|
|
if actual != expected:
|
|
raise ValueError(f"表头不匹配 {path}: {actual!r}")
|
|
return [row for row in rows[2:] if any(str(row.get(f"col{i}") or "").strip() for i in range(1, len(expected_columns) + 1))]
|
|
|
|
|
|
def source_upsert(meta, digest, raw_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(meta['source_type'])}, {sql_string(meta['publisher'])},
|
|
{sql_string(meta['title'])}, {sql_string(meta.get('landing_page_url'))},
|
|
{sql_string(meta.get('file_url'))}, {sql_string(meta.get('published_on'))},
|
|
NOW(), {sql_string(digest)}, {sql_string(str(Path(raw_path).resolve()))},
|
|
{sql_string(meta.get('notes'))}
|
|
) AS incoming
|
|
ON DUPLICATE KEY UPDATE 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(digest)});
|
|
"""
|
|
|
|
|
|
def run_start(dataset_name, row_count):
|
|
run_id = str(uuid.uuid4())
|
|
sql = f"""
|
|
INSERT INTO ingestion_runs (
|
|
ingestion_run_id, source_id, dataset_name, dataset_year, started_at,
|
|
status, rows_read, rows_rejected
|
|
) VALUES ({sql_string(run_id)}, @source_id, {sql_string(dataset_name)}, {YEAR},
|
|
NOW(), 'running', {row_count}, 0);
|
|
"""
|
|
return run_id, sql
|
|
|
|
|
|
def run_finish(run_id, row_count, message):
|
|
return f"""
|
|
UPDATE ingestion_runs SET finished_at=NOW(), status='success',
|
|
rows_inserted={row_count}, message={sql_string(message)}
|
|
WHERE ingestion_run_id={sql_string(run_id)};
|
|
"""
|
|
|
|
|
|
def chunks(rows, size=300):
|
|
for index in range(0, len(rows), size):
|
|
yield rows[index:index + size]
|
|
|
|
|
|
def build_cutoff_sql(csv_path, raw_path, meta, subject, level, segment):
|
|
headers = [
|
|
"院校代号", "院校名称", "院校专业组", "院校专业组名称", "投档最低分",
|
|
"总分(不含政策性照顾加分)", "语文、数学之和", "语文或数学单科最高成绩",
|
|
"外语单科成绩", "首选科目单科成绩", "再选科目单科最高成绩",
|
|
"再选科目单科次高成绩", "志愿号",
|
|
]
|
|
rows = read_csv_rows(csv_path, headers)
|
|
digest = file_digest(raw_path)
|
|
statements = [source_upsert(meta, digest, raw_path)]
|
|
run_id, start_sql = run_start(f"gansu_2024_cutoff_{segment}_{subject}", len(rows))
|
|
statements += [start_sql, "DELETE FROM admission_cutoff_lines WHERE source_id=@source_id;"]
|
|
values = []
|
|
for row in rows:
|
|
school_code = normalize_code(row["col1"], 4)
|
|
group_code = normalize_code(row["col3"], 3)
|
|
values.append("(" + ",".join([
|
|
sql_string(PROVINCE), str(YEAR), sql_string("普通类"), sql_string(subject),
|
|
sql_string(level), sql_string(segment), "NULL", sql_string(school_code),
|
|
sql_string(row["col2"].strip()), sql_string(group_code), sql_string(row["col4"].strip()),
|
|
sql_number(row["col5"]), sql_number(row["col6"]), sql_number(row["col7"]),
|
|
sql_number(row["col8"]), sql_number(row["col9"]), sql_number(row["col10"]),
|
|
sql_number(row["col11"]), sql_number(row["col12"]), sql_number(row["col13"]),
|
|
str(int(row["row_number"])), "@source_id"
|
|
]) + ")")
|
|
for batch in chunks(values):
|
|
statements.append("""
|
|
INSERT INTO admission_cutoff_lines (
|
|
province_name, admission_year, admission_category, subject_category,
|
|
education_level, segment_code, school_id, source_school_code,
|
|
school_name_raw, group_code, group_name, cutoff_score, raw_total_score,
|
|
chinese_math_sum, chinese_or_math_max, foreign_language_score,
|
|
first_choice_subject_score, second_choice_subject_max,
|
|
second_choice_subject_second, volunteer_number, source_row_number, source_id
|
|
) VALUES
|
|
""" + ",\n".join(batch) + ";")
|
|
statements.append("""
|
|
UPDATE admission_cutoff_lines c
|
|
SET c.school_id=(SELECT MIN(s.school_id) FROM schools s WHERE s.official_name=c.school_name_raw)
|
|
WHERE c.source_id=@source_id;
|
|
""")
|
|
statements.append("""
|
|
INSERT INTO admission_groups (
|
|
plan_release_id, province_name, admission_year, admission_category,
|
|
subject_category, segment_code, school_id, source_school_code,
|
|
school_name_raw, group_code, group_name, source_id
|
|
)
|
|
SELECT NULL, province_name, admission_year, admission_category,
|
|
subject_category, segment_code, school_id, source_school_code,
|
|
school_name_raw, group_code, group_name, source_id
|
|
FROM admission_cutoff_lines
|
|
WHERE source_id=@source_id
|
|
ON DUPLICATE KEY UPDATE
|
|
school_id=COALESCE(admission_groups.school_id, VALUES(school_id)),
|
|
group_name=COALESCE(admission_groups.group_name, VALUES(group_name));
|
|
""")
|
|
statements.append(run_finish(run_id, len(rows), "官方平行志愿投档最低分完整公开表已导入"))
|
|
statements.append(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 ('院校专业组投档线', '{PROVINCE}', {YEAR}, '普通类', {sql_string(subject)},
|
|
{sql_string(segment)}, 'complete_public_snapshot', {len(rows)},
|
|
'官方公开的该段次首轮平行志愿投档最低分完整附件。', @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;
|
|
""")
|
|
return statements, len(rows)
|
|
|
|
|
|
def category_for(subject):
|
|
if subject in ("物理类", "历史类"):
|
|
return "普通类"
|
|
return subject
|
|
|
|
|
|
def build_plan_sql(csv_path, raw_path, meta, plan_round):
|
|
headers_a = ["批次", "科类", "院校代号", "院校名称", "专业代号", "专业名称", "院校专业组", "考试科目要求", "剩余计划数", "备注"]
|
|
headers_b = ["批次", "科类", "院校代号", "院校名称", "专业代号", "专业名称", "院校专业组", "科目要求", "缺额数", "备注"]
|
|
try:
|
|
rows = read_csv_rows(csv_path, headers_a)
|
|
except ValueError:
|
|
rows = read_csv_rows(csv_path, headers_b)
|
|
digest = file_digest(raw_path)
|
|
statements = [source_upsert(meta, digest, raw_path)]
|
|
run_id, start_sql = run_start(f"gansu_2024_plan_{plan_round}", len(rows))
|
|
statements += [start_sql]
|
|
statements += [
|
|
"DELETE FROM admission_major_plans WHERE source_id=@source_id;",
|
|
f"""
|
|
INSERT INTO admission_plan_releases (
|
|
province_name, admission_year, release_title, plan_round,
|
|
coverage_scope, published_on, source_id
|
|
) VALUES ('{PROVINCE}', {YEAR}, {sql_string(meta['title'])},
|
|
{sql_string(plan_round)}, 'remaining_only', {sql_string(meta['published_on'])}, @source_id)
|
|
AS incoming ON DUPLICATE KEY UPDATE
|
|
release_title=incoming.release_title, plan_round=incoming.plan_round,
|
|
coverage_scope=incoming.coverage_scope, published_on=incoming.published_on;
|
|
SET @release_id=(SELECT plan_release_id FROM admission_plan_releases WHERE source_id=@source_id);
|
|
""",
|
|
]
|
|
group_values = {}
|
|
plan_values = []
|
|
for row in rows:
|
|
segment = row["col1"].strip()
|
|
subject = row["col2"].strip()
|
|
school_code = normalize_code(row["col3"], 4)
|
|
school_name = row["col4"].strip()
|
|
major_code = normalize_code(row["col5"], 2)
|
|
group_code = normalize_code(row["col7"], 3)
|
|
key = (subject, segment, school_code, group_code)
|
|
group_values[key] = "(" + ",".join([
|
|
"@release_id", sql_string(PROVINCE), str(YEAR), sql_string(category_for(subject)),
|
|
sql_string(subject), sql_string(segment), "NULL", sql_string(school_code),
|
|
sql_string(school_name), sql_string(group_code), "NULL", "@source_id"
|
|
]) + ")"
|
|
plan_values.append({
|
|
"key": key, "major_code": major_code, "major_name": row["col6"].strip(),
|
|
"requirements": row["col8"].strip() or None, "count": row["col9"],
|
|
"notes": row["col10"].strip() or None, "row_number": int(row["row_number"]),
|
|
})
|
|
for batch in chunks(list(group_values.values())):
|
|
statements.append("""
|
|
INSERT INTO admission_groups (
|
|
plan_release_id, province_name, admission_year, admission_category,
|
|
subject_category, segment_code, school_id, source_school_code,
|
|
school_name_raw, group_code, group_name, source_id
|
|
) VALUES
|
|
""" + ",\n".join(batch) + """
|
|
AS incoming ON DUPLICATE KEY UPDATE
|
|
plan_release_id=COALESCE(admission_groups.plan_release_id, incoming.plan_release_id),
|
|
school_id=COALESCE(admission_groups.school_id, incoming.school_id),
|
|
school_name_raw=incoming.school_name_raw;
|
|
""")
|
|
for batch in chunks(plan_values):
|
|
values = []
|
|
for item in batch:
|
|
subject, segment, school_code, group_code = item["key"]
|
|
group_id = f"(SELECT admission_group_id FROM admission_groups WHERE province_name={sql_string(PROVINCE)} AND admission_year={YEAR} AND admission_category={sql_string(category_for(subject))} AND subject_category={sql_string(subject)} AND segment_code={sql_string(segment)} AND source_school_code={sql_string(school_code)} AND group_code={sql_string(group_code)} LIMIT 1)"
|
|
values.append("(" + ",".join([
|
|
group_id, "@release_id", sql_string(item["major_code"]), sql_string(item["major_name"]),
|
|
sql_string(item["requirements"]), sql_number(item["count"]), "NULL", "NULL", "NULL",
|
|
sql_text(item["notes"]), str(item["row_number"]), "@source_id"
|
|
]) + ")")
|
|
statements.append("""
|
|
INSERT INTO admission_major_plans (
|
|
admission_group_id, plan_release_id, major_code, major_name,
|
|
subject_requirements, plan_count, tuition_yuan_per_year, study_years,
|
|
campus_name, notes, source_row_number, source_id
|
|
) VALUES
|
|
""" + ",\n".join(values) + ";")
|
|
statements.append("""
|
|
UPDATE admission_groups g
|
|
SET g.school_id=(SELECT MIN(s.school_id) FROM schools s WHERE s.official_name=g.school_name_raw)
|
|
WHERE g.plan_release_id=@release_id;
|
|
""")
|
|
statements.append(run_finish(run_id, len(rows), "官方征集志愿剩余计划已导入;不能视为初始招生计划"))
|
|
segments = sorted({row["col1"].strip() for row in rows})
|
|
statements.append(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 ('专业招生计划', '{PROVINCE}', {YEAR}, '', '', {sql_string(','.join(segments))},
|
|
'partial_public_snapshot', {len(rows)},
|
|
'仅为官方公开的征集志愿剩余或缺额计划,不是2024年初始全量招生专业目录。', @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;
|
|
""")
|
|
return statements, len(rows), len(group_values)
|
|
|
|
|
|
def build_score_rank_sql(html_path, meta):
|
|
text = Path(html_path).read_text(encoding="utf-8")
|
|
table = pd.read_html(StringIO(text))[0]
|
|
records = []
|
|
for subject, offset in (("物理类", 0), ("历史类", 3)):
|
|
points = []
|
|
for _, row in table.iloc[2:].iterrows():
|
|
sequence, score, cumulative = row.iloc[offset:offset + 3]
|
|
if pd.isna(score) or pd.isna(cumulative):
|
|
continue
|
|
points.append((int(float(sequence)), int(float(score)), int(float(cumulative))))
|
|
points.sort(key=lambda item: item[1], reverse=True)
|
|
previous = 0
|
|
for sequence, score, cumulative in points:
|
|
if cumulative < previous:
|
|
raise ValueError(f"{subject}累计人数不是单调递增: {score}")
|
|
same = cumulative - previous
|
|
rank_from = previous + 1 if same else cumulative
|
|
records.append((subject, score, same, cumulative, rank_from, cumulative, sequence))
|
|
previous = cumulative
|
|
digest = file_digest(html_path)
|
|
statements = [source_upsert(meta, digest, html_path)]
|
|
run_id, start_sql = run_start("gansu_2024_score_rank", len(records))
|
|
statements += [start_sql, "DELETE FROM score_rank_points WHERE source_id=@source_id;"]
|
|
values = []
|
|
for subject, score, same, cumulative, rank_from, rank_to, sequence in records:
|
|
values.append("(" + ",".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 batch in chunks(values):
|
|
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(batch) + ";")
|
|
statements.append(run_finish(run_id, len(records), "官方2024年物理类、历史类一分一段表已导入"))
|
|
for subject in ("物理类", "历史类"):
|
|
count = sum(1 for row in records if row[0] == subject)
|
|
statements.append(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 ('一分一段', '{PROVINCE}', {YEAR}, '普通类', {sql_string(subject)}, '',
|
|
'complete_public_snapshot', {count}, '官方公开的分数至累计人数完整分段表。', @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;
|
|
""")
|
|
return statements, len(records)
|
|
|
|
|
|
def build_reference_sql(root):
|
|
implementation = root / "data/raw/gansu/2024/admission_implementation_plan_2024.html"
|
|
control = root / "data/raw/gansu/2024/control_score_lines_republication_2024.html"
|
|
implementation_meta = {
|
|
"source_type": "official_html", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃省普通高等学校招生考试和录取工作实施方案",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/910.html?appPrefix=xbsfdx",
|
|
"published_on": "2023-11-08", "notes": "用于批次段次、院校专业组和志愿数量规则。",
|
|
}
|
|
statements = [source_upsert(implementation_meta, file_digest(implementation), implementation)]
|
|
batches = [
|
|
("普通类", "本科", "A", "普通类本科提前批A段", "顺序志愿", 2, 6, 1),
|
|
("普通类", "本科", "B", "普通类本科批B段", "单志愿", 1, 6, 0),
|
|
("普通类", "本科", "C", "普通类本科批C段", "平行志愿", 45, 6, 2),
|
|
("普通类", "高职专科", "D", "普通类高职(专科)提前批D段", "顺序志愿", 2, 6, 1),
|
|
("普通类", "高职专科", "E", "普通类高职(专科)提前批E段", "平行志愿", 45, 6, 2),
|
|
("普通类", "高职专科", "F", "普通类高职(专科)批F段", "平行志愿", 45, 6, 2),
|
|
("艺体类", "本科", "G", "艺体类本科提前批G段", "单志愿", 1, 6, 0),
|
|
("艺体类", "本科", "H", "艺体类本科批H段", "平行志愿", 30, 6, 2),
|
|
("艺体类", "高职专科", "I", "艺体类高职(专科)批I段", "平行志愿", 30, 6, 2),
|
|
]
|
|
values = ["(" + ",".join([
|
|
sql_string(PROVINCE), str(YEAR), sql_string(category), sql_string(level),
|
|
sql_string(segment), sql_string(name), sql_string(mode), str(max_groups),
|
|
str(max_majors), str(solicitations), "@source_id"
|
|
]) + ")" for category, level, segment, name, mode, max_groups, max_majors, solicitations in batches]
|
|
statements.append("""
|
|
INSERT INTO admission_batches (
|
|
province_name, admission_year, admission_category, education_level,
|
|
segment_code, batch_name, volunteer_mode, max_group_volunteers,
|
|
max_majors_per_group, solicitation_count, source_id
|
|
) VALUES
|
|
""" + ",\n".join(values) + """
|
|
AS incoming ON DUPLICATE KEY UPDATE education_level=incoming.education_level,
|
|
batch_name=incoming.batch_name, volunteer_mode=incoming.volunteer_mode,
|
|
max_group_volunteers=incoming.max_group_volunteers,
|
|
max_majors_per_group=incoming.max_majors_per_group,
|
|
solicitation_count=incoming.solicitation_count, source_id=incoming.source_id;
|
|
""")
|
|
statements.append(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 ('初始招生计划', '{PROVINCE}', {YEAR}, '', '', '',
|
|
'unavailable_publicly', 0,
|
|
'考试院公开网页指向纸质《2024年甘肃省普通高等学校招生专业目录》和登录后平台;本次未发现可公开下载的初始全量计划附件。', @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;
|
|
""")
|
|
|
|
control_meta = {
|
|
"source_type": "official_republication_html",
|
|
"publisher": "甘肃省教育考试院微信公众号(澎湃新闻转载)",
|
|
"title": "甘肃高考分数线出炉:普通类本科历史类421、物理类370,特殊类型历史类502、物理类488",
|
|
"landing_page_url": "https://www.thepaper.cn/newsDetail_forward_27849388",
|
|
"published_on": "2024-06-25", "notes": "公开转载甘肃省教育考试院发布内容;高职专科普通类控制线为160分。",
|
|
}
|
|
statements.append(source_upsert(control_meta, file_digest(control), control))
|
|
lines = [
|
|
("历史类", "特殊类型", 502), ("历史类", "本科", 421), ("历史类", "高职专科", 160),
|
|
("物理类", "特殊类型", 488), ("物理类", "本科", 370), ("物理类", "高职专科", 160),
|
|
]
|
|
values = ["(" + ",".join([
|
|
sql_string(PROVINCE), str(YEAR), sql_string("普通类"), sql_string(subject),
|
|
sql_string(level), str(score), "NULL", sql_string("普通类文化课控制线"), "@source_id"
|
|
]) + ")" for subject, level, score in lines]
|
|
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
|
|
""" + ",\n".join(values) + """
|
|
AS incoming ON DUPLICATE KEY UPDATE culture_score=incoming.culture_score,
|
|
professional_score=incoming.professional_score, notes=incoming.notes,
|
|
source_id=incoming.source_id;
|
|
""")
|
|
statements.append(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 ('普通类控制线', '{PROVINCE}', {YEAR}, '普通类', '', '',
|
|
'complete_public_snapshot', 6, '物理类、历史类各含特殊类型、本科和高职专科控制线。', @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;
|
|
""")
|
|
return statements
|
|
|
|
|
|
def mysql_run(args, sql):
|
|
if not os.environ.get("MYSQL_PWD"):
|
|
raise RuntimeError("必须通过 MYSQL_PWD 环境变量提供数据库密码")
|
|
process = subprocess.run([
|
|
args.mysql_bin, "--protocol=TCP", "-h", args.host, "-P", str(args.port),
|
|
"-u", args.user, "--default-character-set=utf8mb4",
|
|
], input=sql, text=True, encoding="utf-8", capture_output=True)
|
|
if process.returncode:
|
|
raise RuntimeError(process.stderr.strip() or process.stdout.strip())
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="导入甘肃省2024年公开招录数据")
|
|
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)
|
|
raw = root / "data/raw/gansu/2024"
|
|
csv_dir = root / "data/processed/gansu/2024/csv"
|
|
|
|
source_specs = {
|
|
"cutoff_bachelor_c_history": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃省普通高校招生本科批(C段)物理类平行志愿投档最低分",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1181.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240719/11-240G9113416231.xls",
|
|
"published_on": "2024-07-19", "notes": "官方附件;本地文件名保留下载识别名,表内标题确认为物理类。",
|
|
},
|
|
"cutoff_bachelor_c_physics": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃省普通高校招生本科批(C段)历史类平行志愿投档最低分",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1181.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240811/11-240Q1105311438.xls",
|
|
"published_on": "2024-07-19", "notes": "官方附件;本地文件名保留下载识别名,表内标题确认为历史类。",
|
|
},
|
|
"cutoff_vocational_f_1": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃省普通高校招生高职(专科)批(F段)物理类平行志愿投档最低分",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1198.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240808/27-240PQ5223b96.xls",
|
|
"published_on": "2024-08-08", "notes": "官方附件。",
|
|
},
|
|
"cutoff_vocational_f_2": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃省普通高校招生高职(专科)批(F段)历史类平行志愿投档最低分",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1198.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240808/27-240PQ52250120.xls",
|
|
"published_on": "2024-08-08", "notes": "官方附件。",
|
|
},
|
|
"plan_bachelor_c_first_solicitation": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "普通类本科批C段第一次征集志愿剩余计划统计表",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1186.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240722/27-240H216345JO.XLS",
|
|
"published_on": "2024-07-22", "notes": "仅为第一次征集时的剩余计划。",
|
|
},
|
|
"plan_segments_dei_solicitation": {
|
|
"source_type": "official_xls", "publisher": "甘肃省教育考试院",
|
|
"title": "普通类高职(专科)批E段、艺体类高职(专科)批I段征集志愿剩余计划统计表",
|
|
"landing_page_url": "https://www.ganseea.cn/shouyegonggao/1196.html",
|
|
"file_url": "https://www.ganseea.cn/uploads/allimg/20240803/27-240P31G3015Q.XLS",
|
|
"published_on": "2024-08-03", "notes": "第6号征集志愿公告附件;D段计划已完成,附件实际含E、I段。",
|
|
},
|
|
}
|
|
|
|
statements = ["SET NAMES utf8mb4;", "SET time_zone='+08:00';", f"USE `{args.database}`;", "START TRANSACTION;"]
|
|
statements += build_reference_sql(root)
|
|
score_meta = {
|
|
"source_type": "official_html", "publisher": "甘肃省教育考试院",
|
|
"title": "2024年甘肃普通高等学校招生考试成绩分段表",
|
|
"landing_page_url": "https://www.ganseea.cn/gaokaogaozhao/1157.html",
|
|
"published_on": "2024-06-25", "notes": "普通类物理、历史一分一段累计人数表。",
|
|
}
|
|
score_sql, score_count = build_score_rank_sql(raw / "score_rank_2024.html", score_meta)
|
|
statements += score_sql
|
|
|
|
cutoff_jobs = [
|
|
("cutoff_bachelor_c_history", "物理类", "本科", "C"),
|
|
("cutoff_bachelor_c_physics", "历史类", "本科", "C"),
|
|
("cutoff_vocational_f_1", "物理类", "高职专科", "F"),
|
|
("cutoff_vocational_f_2", "历史类", "高职专科", "F"),
|
|
]
|
|
cutoff_count = 0
|
|
for name, subject, level, segment in cutoff_jobs:
|
|
sql, count = build_cutoff_sql(csv_dir / f"{name}.csv", raw / f"{name}.xls", source_specs[name], subject, level, segment)
|
|
statements += sql
|
|
cutoff_count += count
|
|
|
|
plan_count = group_count = 0
|
|
for name, round_name in (("plan_bachelor_c_first_solicitation", "第一次征集"), ("plan_segments_dei_solicitation", "第六号公告征集")):
|
|
sql, rows, groups = build_plan_sql(csv_dir / f"{name}.csv", raw / f"{name}.xls", source_specs[name], round_name)
|
|
statements += sql
|
|
plan_count += rows
|
|
group_count += groups
|
|
|
|
statements.append("""
|
|
INSERT INTO school_external_codes (
|
|
school_id, code_system, external_code, province_name, admission_year,
|
|
admission_batch, admission_type, source_id
|
|
)
|
|
SELECT mapped.school_id, 'gansu_admission_school_code', mapped.source_school_code,
|
|
'甘肃省', 2024, '', '', MIN(mapped.source_id)
|
|
FROM (
|
|
SELECT school_id, source_school_code, source_id FROM admission_cutoff_lines
|
|
WHERE province_name='甘肃省' AND admission_year=2024 AND school_id IS NOT NULL
|
|
UNION ALL
|
|
SELECT school_id, source_school_code, source_id FROM admission_groups
|
|
WHERE province_name='甘肃省' AND admission_year=2024 AND school_id IS NOT NULL
|
|
) mapped
|
|
GROUP BY mapped.school_id, mapped.source_school_code
|
|
ON DUPLICATE KEY UPDATE school_id=VALUES(school_id), source_id=VALUES(source_id);
|
|
""")
|
|
statements += ["COMMIT;"]
|
|
mysql_run(args, "\n".join(statements))
|
|
print(f"导入完成: 一分一段 {score_count} 条,投档线 {cutoff_count} 条,专业计划 {plan_count} 条,院校专业组 {group_count} 条")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|