Files
YunNanProject/Tools/T7_TeacherCount.py
2025-09-11 20:50:59 +08:00

232 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import traceback
from typing import List, Dict, Any, Tuple
import openpyxl
from openpyxl.utils import column_index_from_string
from openpyxl.worksheet.worksheet import Worksheet
from Config.Config import EXCEL_PATH
from Util.DataUtil import (
init_directories, process_value, print_conversion_stats,
convert_area_name, save_to_json, load_workbook_sheet
)
# ======================== 配置常量 ======================== #
# 数据目录与输出路径
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Data')
JSON_PATH = os.path.join(DATA_DIR, 'TeacherCount.json')
# Excel配置
SHEET_NAME = '教职工数、专任教师数'
REGION_NAME_COLUMN = 'B' # 区域名称所在列
START_ROW = 5 # 数据起始行从第5行开始
# 教育阶段数据列配置
EDUCATION_STAGES = {
'preschool_teachers': {
'years': [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
'columns': [
{'year': 2015, 'total_staff': 'D', 'urban_staff': 'E', 'town_staff': 'F', 'rural_staff': 'G', 'total_teacher': 'H', 'urban_teacher': 'I', 'town_teacher': 'J', 'rural_teacher': 'K'},
{'year': 2016, 'total_staff': 'L', 'urban_staff': 'M', 'town_staff': 'N', 'rural_staff': 'O', 'total_teacher': 'P', 'urban_teacher': 'Q', 'town_teacher': 'R', 'rural_teacher': 'S'},
{'year': 2017, 'total_staff': 'T', 'urban_staff': 'U', 'town_staff': 'V', 'rural_staff': 'W', 'total_teacher': 'X', 'urban_teacher': 'Y', 'town_teacher': 'Z', 'rural_teacher': 'AA'},
{'year': 2018, 'total_staff': 'AB', 'urban_staff': 'AC', 'town_staff': 'AD', 'rural_staff': 'AE', 'total_teacher': 'AF', 'urban_teacher': 'AG', 'town_teacher': 'AH', 'rural_teacher': 'AI'},
{'year': 2019, 'total_staff': 'AJ', 'urban_staff': 'AK', 'town_staff': 'AL', 'rural_staff': 'AM', 'total_teacher': 'AN', 'urban_teacher': 'AO', 'town_teacher': 'AP', 'rural_teacher': 'AQ'},
{'year': 2020, 'total_staff': 'AR', 'urban_staff': 'AS', 'town_staff': 'AT', 'rural_staff': 'AU', 'total_teacher': 'AV', 'urban_teacher': 'AW', 'town_teacher': 'AX', 'rural_teacher': 'AY'},
{'year': 2021, 'total_staff': 'AZ', 'urban_staff': 'BA', 'town_staff': 'BB', 'rural_staff': 'BC', 'total_teacher': 'BD', 'urban_teacher': 'BE', 'town_teacher': 'BF', 'rural_teacher': 'BG'},
{'year': 2022, 'total_staff': 'BH', 'urban_staff': 'BI', 'town_staff': 'BJ', 'rural_staff': 'BK', 'total_teacher': 'BL', 'urban_teacher': 'BM', 'town_teacher': 'BN', 'rural_teacher': 'BO'},
{'year': 2023, 'total_staff': 'BP', 'urban_staff': 'BQ', 'town_staff': 'BR', 'rural_staff': 'BS', 'total_teacher': 'BT', 'urban_teacher': 'BU', 'town_teacher': 'BV', 'rural_teacher': 'BW'},
{'year': 2024, 'total_staff': 'BX', 'urban_staff': 'BY', 'town_staff': 'BZ', 'rural_staff': 'CA', 'total_teacher': 'CB', 'urban_teacher': 'CC', 'town_teacher': 'CD', 'rural_teacher': 'CE'}
]
},
'primary_teachers': {
'years': [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
'columns': [
{'year': 2015, 'total_staff': 'CF', 'urban_staff': 'CG', 'town_staff': 'CH', 'rural_staff': 'CI', 'total_teacher': 'CJ', 'urban_teacher': 'CK', 'town_teacher': 'CL', 'rural_teacher': 'CM'},
{'year': 2016, 'total_staff': 'CN', 'urban_staff': 'CO', 'town_staff': 'CP', 'rural_staff': 'CQ', 'total_teacher': 'CR', 'urban_teacher': 'CS', 'town_teacher': 'CT', 'rural_teacher': 'CU'},
{'year': 2017, 'total_staff': 'CV', 'urban_staff': 'CW', 'town_staff': 'CX', 'rural_staff': 'CY', 'total_teacher': 'CZ', 'urban_teacher': 'DA', 'town_teacher': 'DB', 'rural_teacher': 'DC'},
{'year': 2018, 'total_staff': 'DD', 'urban_staff': 'DE', 'town_staff': 'DF', 'rural_staff': 'DG', 'total_teacher': 'DH', 'urban_teacher': 'DI', 'town_teacher': 'DJ', 'rural_teacher': 'DK'},
{'year': 2019, 'total_staff': 'DL', 'urban_staff': 'DM', 'town_staff': 'DN', 'rural_staff': 'DO', 'total_teacher': 'DP', 'urban_teacher': 'DQ', 'town_teacher': 'DR', 'rural_teacher': 'DS'},
{'year': 2020, 'total_staff': 'DT', 'urban_staff': 'DU', 'town_staff': 'DV', 'rural_staff': 'DW', 'total_teacher': 'DX', 'urban_teacher': 'DY', 'town_teacher': 'DZ', 'rural_teacher': 'EA'},
{'year': 2021, 'total_staff': 'EB', 'urban_staff': 'EC', 'town_staff': 'ED', 'rural_staff': 'EE', 'total_teacher': 'EF', 'urban_teacher': 'EG', 'town_teacher': 'EH', 'rural_teacher': 'EI'},
{'year': 2022, 'total_staff': 'EJ', 'urban_staff': 'EK', 'town_staff': 'EL', 'rural_staff': 'EM', 'total_teacher': 'EN', 'urban_teacher': 'EO', 'town_teacher': 'EP', 'rural_teacher': 'EQ'},
{'year': 2023, 'total_staff': 'ER', 'urban_staff': 'ES', 'town_staff': 'ET', 'rural_staff': 'EU', 'total_teacher': 'EV', 'urban_teacher': 'EW', 'town_teacher': 'EX', 'rural_teacher': 'EY'},
{'year': 2024, 'total_staff': 'EZ', 'urban_staff': 'FA', 'town_staff': 'FB', 'rural_staff': 'FC', 'total_teacher': 'FD', 'urban_teacher': 'FE', 'town_teacher': 'FF', 'rural_teacher': 'FG'}
]
},
'junior_teachers': {
'years': [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
'columns': [
{'year': 2015, 'total_staff': 'FH', 'urban_staff': 'FI', 'town_staff': 'FJ', 'rural_staff': 'FK', 'total_teacher': 'FL', 'urban_teacher': 'FM', 'town_teacher': 'FN', 'rural_teacher': 'FO'},
{'year': 2016, 'total_staff': 'FP', 'urban_staff': 'FQ', 'town_staff': 'FR', 'rural_staff': 'FS', 'total_teacher': 'FT', 'urban_teacher': 'FU', 'town_teacher': 'FV', 'rural_teacher': 'FW'},
{'year': 2017, 'total_staff': 'FX', 'urban_staff': 'FY', 'town_staff': 'FZ', 'rural_staff': 'GA', 'total_teacher': 'GB', 'urban_teacher': 'GC', 'town_teacher': 'GD', 'rural_teacher': 'GE'},
{'year': 2018, 'total_staff': 'GF', 'urban_staff': 'GG', 'town_staff': 'GH', 'rural_staff': 'GI', 'total_teacher': 'GJ', 'urban_teacher': 'GK', 'town_teacher': 'GL', 'rural_teacher': 'GM'},
{'year': 2019, 'total_staff': 'GN', 'urban_staff': 'GO', 'town_staff': 'GP', 'rural_staff': 'GQ', 'total_teacher': 'GR', 'urban_teacher': 'GS', 'town_teacher': 'GT', 'rural_teacher': 'GU'},
{'year': 2020, 'total_staff': 'GV', 'urban_staff': 'GW', 'town_staff': 'GX', 'rural_staff': 'GY', 'total_teacher': 'GZ', 'urban_teacher': 'HA', 'town_teacher': 'HB', 'rural_teacher': 'HC'},
{'year': 2021, 'total_staff': 'HD', 'urban_staff': 'HE', 'town_staff': 'HF', 'rural_staff': 'HG', 'total_teacher': 'HH', 'urban_teacher': 'HI', 'town_teacher': 'HJ', 'rural_teacher': 'HK'},
{'year': 2022, 'total_staff': 'HL', 'urban_staff': 'HM', 'town_staff': 'HN', 'rural_staff': 'HO', 'total_teacher': 'HP', 'urban_teacher': 'HQ', 'town_teacher': 'HR', 'rural_teacher': 'HS'},
{'year': 2023, 'total_staff': 'HT', 'urban_staff': 'HU', 'town_staff': 'HV', 'rural_staff': 'HW', 'total_teacher': 'HX', 'urban_teacher': 'HY', 'town_teacher': 'HZ', 'rural_teacher': 'IA'},
{'year': 2024, 'total_staff': 'IB', 'urban_staff': 'IC', 'town_staff': 'ID', 'rural_staff': 'IE', 'total_teacher': 'IF', 'urban_teacher': 'IG', 'town_teacher': 'IH', 'rural_teacher': 'II'}
]
},
'senior_teachers': {
'years': [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
'columns': [
{'year': 2015, 'total_staff': 'IJ', 'urban_staff': 'IK', 'town_staff': 'IL', 'rural_staff': 'IM', 'total_teacher': 'IN', 'urban_teacher': 'IO', 'town_teacher': 'IP', 'rural_teacher': 'IQ'},
{'year': 2016, 'total_staff': 'IR', 'urban_staff': 'IS', 'town_staff': 'IT', 'rural_staff': 'IU', 'total_teacher': 'IV', 'urban_teacher': 'IW', 'town_teacher': 'IX', 'rural_teacher': 'IY'},
{'year': 2017, 'total_staff': 'IZ', 'urban_staff': 'JA', 'town_staff': 'JB', 'rural_staff': 'JC', 'total_teacher': 'JD', 'urban_teacher': 'JE', 'town_teacher': 'JF', 'rural_teacher': 'JG'},
{'year': 2018, 'total_staff': 'JH', 'urban_staff': 'JI', 'town_staff': 'JJ', 'rural_staff': 'JK', 'total_teacher': 'JL', 'urban_teacher': 'JM', 'town_teacher': 'JN', 'rural_teacher': 'JO'},
{'year': 2019, 'total_staff': 'JP', 'urban_staff': 'JQ', 'town_staff': 'JR', 'rural_staff': 'JS', 'total_teacher': 'JT', 'urban_teacher': 'JU', 'town_teacher': 'JV', 'rural_teacher': 'JW'},
{'year': 2020, 'total_staff': 'JX', 'urban_staff': 'JY', 'town_staff': 'JZ', 'rural_staff': 'KA', 'total_teacher': 'KB', 'urban_teacher': 'KC', 'town_teacher': 'KD', 'rural_teacher': 'KE'},
{'year': 2021, 'total_staff': 'KF', 'urban_staff': 'KG', 'town_staff': 'KH', 'rural_staff': 'KI', 'total_teacher': 'KJ', 'urban_teacher': 'KK', 'town_teacher': 'KL', 'rural_teacher': 'KM'},
{'year': 2022, 'total_staff': 'KN', 'urban_staff': 'KO', 'town_staff': 'KP', 'rural_staff': 'KQ', 'total_teacher': 'KR', 'urban_teacher': 'KS', 'town_teacher': 'KT', 'rural_teacher': 'KU'},
{'year': 2023, 'total_staff': 'KV', 'urban_staff': 'KW', 'town_staff': 'KX', 'rural_staff': 'KY', 'total_teacher': 'KZ', 'urban_teacher': 'LA', 'town_teacher': 'LB', 'rural_teacher': 'LC'},
{'year': 2024, 'total_staff': 'LD', 'urban_staff': 'LE', 'town_staff': 'LF', 'rural_staff': 'LG', 'total_teacher': 'LH', 'urban_teacher': 'LI', 'town_teacher': 'LJ', 'rural_teacher': 'LK'}
]
},
'vocational_teachers': {
'years': [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
'columns': [
{'year': 2015, 'total_staff': 'LL', 'total_teacher': 'LM'},
{'year': 2016, 'total_staff': 'LN', 'total_teacher': 'LO'},
{'year': 2017, 'total_staff': 'LP', 'total_teacher': 'LQ'},
{'year': 2018, 'total_staff': 'LR', 'total_teacher': 'LS'},
{'year': 2019, 'total_staff': 'LT', 'total_teacher': 'LU'},
{'year': 2020, 'total_staff': 'LV', 'total_teacher': 'LW'},
{'year': 2021, 'total_staff': 'LX', 'total_teacher': 'LY'},
{'year': 2022, 'total_staff': 'LZ', 'total_teacher': 'MA'},
{'year': 2023, 'total_staff': 'MB', 'total_teacher': 'MC'},
{'year': 2024, 'total_staff': 'MD', 'total_teacher': 'ME'}
]
}
}
def extract_stage_data(row: Tuple[Any, ...], year_config: Dict[str, str]) -> Dict[str, int]:
"""提取单个教育阶段单一年份的教师数据
Args:
row: 工作表行数据
year_config: 年份配置字典
Returns:
包含教职工和教师数据的字典
"""
year_data = {}
# 处理教职工数据
staff_cols = ['total_staff', 'urban_staff', 'town_staff', 'rural_staff']
has_staff_categories = all(col in year_config for col in staff_cols)
if has_staff_categories:
# 处理分类职工数据
for col in staff_cols:
col_name = year_config[col]
col_idx = column_index_from_string(col_name) - 1
value = row[col_idx] if col_idx < len(row) else None
year_data[col] = process_value(value)
else:
# 处理中职职工总数
col_name = year_config['total_staff']
col_idx = column_index_from_string(col_name) - 1
value = row[col_idx] if col_idx < len(row) else None
year_data['total_staff'] = process_value(value)
# 处理专任教师数据
teacher_cols = ['total_teacher', 'urban_teacher', 'town_teacher', 'rural_teacher']
has_teacher_categories = all(col in year_config for col in teacher_cols)
if has_teacher_categories:
# 处理分类专任教师数据
for col in teacher_cols:
col_name = year_config[col]
col_idx = column_index_from_string(col_name) - 1
value = row[col_idx] if col_idx < len(row) else None
year_data[col] = process_value(value)
else:
# 处理中职专任教师总数
col_name = year_config['total_teacher']
col_idx = column_index_from_string(col_name) - 1
value = row[col_idx] if col_idx < len(row) else None
year_data['total_teacher'] = process_value(value)
return year_data
def extract_teacher_data(sheet: Worksheet) -> Tuple[List[Dict[str, Any]], List[Dict[str, str]], List[str], int]:
"""提取所有区域的教师数据
Args:
sheet: 工作表对象
Returns:
教师数据列表、转换记录列表、错误列表、处理总数
"""
teacher_data = []
conversion_records = []
name_conversion_errors = []
processed_count = 0
region_col_index = column_index_from_string(REGION_NAME_COLUMN) - 1
print(f"✅ 开始处理教师数据,共{sheet.max_row}行数据")
# 遍历行数据
for row_idx, row in enumerate(sheet.iter_rows(min_row=START_ROW, values_only=True), start=START_ROW):
processed_count += 1
# 区域名称处理
raw_name = row[region_col_index] if (len(row) > region_col_index and row[region_col_index] is not None) else '未知地区'
if not raw_name: # 跳过空行
continue
# 区域名称转换
area_name, area_code, new_conversion, new_errors = convert_area_name(raw_name, row_idx)
conversion_records.extend(new_conversion)
name_conversion_errors.extend(new_errors)
# 创建区域数据对象
area_data = {
'area_name': area_name,
'area_code': area_code,
'raw_name': str(raw_name).strip(),
}
# 提取各教育阶段数据
for stage, config in EDUCATION_STAGES.items():
stage_data = {}
for year_config in config['columns']:
year = year_config['year']
stage_data[str(year)] = extract_stage_data(row, year_config)
area_data[stage] = stage_data
teacher_data.append(area_data)
return teacher_data, conversion_records, name_conversion_errors, processed_count
def main() -> None:
"""主函数:教师数据提取主流程"""
try:
# 初始化目录
init_directories(DATA_DIR)
# 加载工作表
sheet = load_workbook_sheet(EXCEL_PATH, SHEET_NAME)
if not sheet:
print("❌ 无法加载工作表,程序退出")
return
# 提取教师数据
teacher_data, conversion_records, name_conversion_errors, processed_count = extract_teacher_data(sheet)
# 保存数据到JSON
save_to_json(teacher_data, JSON_PATH)
# 打印转换统计
print_conversion_stats(conversion_records, name_conversion_errors)
except Exception as e:
print(f"🔴 处理数据时发生错误:{str(e)}{traceback.format_exc()}")
if __name__ == "__main__":
main()