初始归档:健康管理资料库(大医网/药膳/体质监测/中医理论等12个资料集,7712个文件)
This commit is contained in:
commit
80ae3811cf
7712 files changed
+4628547
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,524 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
国家学生体质健康标准(2014年修订)— 评测引擎
|
||||
按年级/性别/测试值自动计算总分、附加分、评级
|
||||
满分120分 = 100标准分 + 20附加分
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
BASE_DIR = '/home/songyi/Documents/ai_agent_scraper_study/data/国家学生体质健康标准'
|
||||
DATA_PATH = os.path.join(BASE_DIR, '02_加工数据/评分标准结构化数据.json')
|
||||
|
||||
|
||||
def load_data(path=DATA_PATH):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
# 年级 → 学段分组(用于匹配权重表)
|
||||
GRADE_TO_LEVEL = {
|
||||
'一年级': '小学一、二年级', '二年级': '小学一、二年级',
|
||||
'三年级': '小学三、四年级', '四年级': '小学三、四年级',
|
||||
'五年级': '小学五、六年级', '六年级': '小学五、六年级',
|
||||
'初一': '初中、高中、大学各年级', '初二': '初中、高中、大学各年级',
|
||||
'初三': '初中、高中、大学各年级',
|
||||
'高一': '初中、高中、大学各年级', '高二': '初中、高中、大学各年级',
|
||||
'高三': '初中、高中、大学各年级',
|
||||
'大学': '初中、高中、大学各年级',
|
||||
}
|
||||
|
||||
# 年级别名映射(JSON中的key vs 用户输入的简写)
|
||||
GRADE_ALIAS = {
|
||||
'大学': '大一 大二',
|
||||
'大一': '大一 大二', '大二': '大一 大二',
|
||||
'大三': '大三 大四', '大四': '大三 大四',
|
||||
'大一大二': '大一 大二', # 兼容无空格格式
|
||||
'大三大四': '大三 大四',
|
||||
}
|
||||
|
||||
# 年龄 → 年级映射(简化)
|
||||
AGE_TO_GRADE = {
|
||||
6: '一年级', 7: '二年级', 8: '三年级', 9: '四年级',
|
||||
10: '五年级', 11: '六年级',
|
||||
12: '初一', 13: '初二', 14: '初三',
|
||||
15: '高一', 16: '高二', 17: '高三',
|
||||
18: '大学', 19: '大学', 20: '大学', 21: '大学', 22: '大学',
|
||||
}
|
||||
|
||||
# 权重指标名 → 评分指标名映射(处理文档中的长名称/脏文本)
|
||||
WEIGHT_INDICATOR_MAP = {
|
||||
'体重指数(BMI)': 'BMI',
|
||||
'肺活量': '肺活量',
|
||||
'50米跑': '50米跑',
|
||||
'坐位体前屈': '坐位体前屈',
|
||||
'1分钟跳绳': '1分钟跳绳',
|
||||
'1分钟仰卧起坐': '1分钟仰卧起坐',
|
||||
'引体向上': '引体向上',
|
||||
'立定跳远': '立定跳远',
|
||||
'耐力跑': '耐力跑',
|
||||
'50米×8往返跑': '耐力跑',
|
||||
'1000米跑(男)/800米跑(女)': '耐力跑',
|
||||
}
|
||||
|
||||
|
||||
def normalize_grade(grade):
|
||||
"""标准化年级名"""
|
||||
return GRADE_ALIAS.get(grade, grade)
|
||||
|
||||
|
||||
def get_grade_for_level(grade, table):
|
||||
"""在表格的年级列中查找匹配的年级名"""
|
||||
grade_normalized = normalize_grade(grade)
|
||||
grade_stripped = grade_normalized.replace(' ', '')
|
||||
|
||||
# 直接匹配
|
||||
for gn in table['grade_names']:
|
||||
if gn == grade_normalized:
|
||||
return gn
|
||||
|
||||
# 去空格匹配
|
||||
for gn in table['grade_names']:
|
||||
if gn.replace(' ', '') == grade_stripped:
|
||||
return gn
|
||||
|
||||
# 部分匹配
|
||||
for gn in table['grade_names']:
|
||||
if grade_normalized in gn or gn in grade_normalized:
|
||||
return gn
|
||||
|
||||
# 模糊匹配
|
||||
for gn in table['grade_names']:
|
||||
for entry in table['data']:
|
||||
if grade in entry['grades'] or grade_normalized in entry['grades']:
|
||||
return grade
|
||||
for eg in entry['grades']:
|
||||
if grade in eg or grade_normalized in eg:
|
||||
return eg
|
||||
|
||||
return grade
|
||||
|
||||
|
||||
def age_to_grade(age):
|
||||
"""年龄转年级"""
|
||||
age_int = int(age)
|
||||
if age_int in AGE_TO_GRADE:
|
||||
return AGE_TO_GRADE[age_int]
|
||||
if age_int < 6:
|
||||
return '一年级'
|
||||
if age_int <= 11:
|
||||
return ['一年级','二年级','三年级','四年级','五年级','六年级'][age_int - 6]
|
||||
if age_int <= 14:
|
||||
return ['初一','初二','初三'][age_int - 12]
|
||||
if age_int <= 17:
|
||||
return ['高一','高二','高三'][age_int - 15]
|
||||
return '大学'
|
||||
|
||||
|
||||
def get_level_for_grade(grade):
|
||||
"""年级 → 学段名(权重表key)"""
|
||||
return GRADE_TO_LEVEL.get(grade, '小学一、二年级')
|
||||
|
||||
|
||||
# 指标方向:越高越好(+) 还是越低越好(-)
|
||||
INDICATOR_DIRECTION = {
|
||||
'BMI': 'category',
|
||||
'肺活量': '+',
|
||||
'坐位体前屈': '+',
|
||||
'立定跳远': '+',
|
||||
'1分钟跳绳': '+',
|
||||
'1分钟仰卧起坐': '+',
|
||||
'引体向上': '+',
|
||||
'50米跑': '-',
|
||||
'耐力跑': '-',
|
||||
}
|
||||
|
||||
|
||||
def find_in_scoring_table(table, grade, value, direction='+'):
|
||||
"""在评分表中查找得分
|
||||
学生标准采用阈值制:exact值为达标的门槛值
|
||||
- 越高越好(+):找最高得分,其中value >= exact
|
||||
- 越低越好(-):找最高得分,其中value <= exact
|
||||
- 'category':按分类匹配
|
||||
"""
|
||||
sorted_entries = sorted(table['data'], key=lambda x: x['score'], reverse=True)
|
||||
|
||||
for entry in sorted_entries:
|
||||
grade_data = entry.get('grades', {}).get(grade)
|
||||
if not grade_data:
|
||||
continue
|
||||
score = entry['score']
|
||||
r = grade_data
|
||||
|
||||
# 尝试解析raw时间格式: "3'40\"" → 220秒
|
||||
if 'raw' in r:
|
||||
parsed = parse_time_to_seconds(r['raw'])
|
||||
if parsed is not None:
|
||||
if direction == '-' and value <= parsed:
|
||||
return score
|
||||
elif direction == '+' and value >= parsed:
|
||||
return score
|
||||
|
||||
if 'min' in r and 'max' in r:
|
||||
if r['min'] <= value <= r['max']:
|
||||
return score
|
||||
elif 'min' in r and value >= r['min']:
|
||||
return score
|
||||
elif 'max' in r and value <= r['max']:
|
||||
return score
|
||||
elif 'exact' in r:
|
||||
if direction == '+' and value >= r['exact']:
|
||||
return score
|
||||
elif direction == '-' and value <= r['exact']:
|
||||
return score
|
||||
elif direction == 'category' and abs(value - r['exact']) < 0.01:
|
||||
return score
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def parse_time_to_seconds(raw):
|
||||
"""解析时间格式: \"3'40\\\"\" → 220 秒"""
|
||||
if not raw:
|
||||
return None
|
||||
raw = raw.strip().replace('"', '').replace("'", ' ').replace('′', ' ').replace('″', '')
|
||||
parts = raw.split()
|
||||
if len(parts) == 2:
|
||||
try:
|
||||
return int(parts[0]) * 60 + float(parts[1])
|
||||
except:
|
||||
pass
|
||||
elif len(parts) == 1:
|
||||
try:
|
||||
return int(parts[0])
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def find_bonus_in_table(table, grade, value):
|
||||
"""在加分表中查找加分
|
||||
table['data'] = [{'bonus_score': 5, 'grades': {'一年级': {'min': 20, 'max': 25}, ...}}, ...]
|
||||
加分逻辑:超过100分对应的值后,每多一个档次加对应分数
|
||||
"""
|
||||
total_bonus = 0
|
||||
for entry in table['data']:
|
||||
grade_data = entry.get('grades', {}).get(grade)
|
||||
if not grade_data:
|
||||
continue
|
||||
bonus = entry['bonus_score']
|
||||
r = grade_data
|
||||
matched = False
|
||||
if 'min' in r and 'max' in r:
|
||||
if r['min'] <= value <= r['max']:
|
||||
matched = True
|
||||
elif 'min' in r and value >= r['min']:
|
||||
matched = True
|
||||
elif 'max' in r and value <= r['max']:
|
||||
matched = True
|
||||
elif 'exact' in r and abs(value - r['exact']) < 0.01:
|
||||
matched = True
|
||||
|
||||
if matched:
|
||||
total_bonus = max(total_bonus, bonus)
|
||||
|
||||
return total_bonus
|
||||
|
||||
|
||||
def evaluate_student(age, gender, grade=None, test_values=None, data=None):
|
||||
"""
|
||||
评测一个学生
|
||||
参数:
|
||||
age: 年龄(周岁)
|
||||
gender: '男' 或 '女'
|
||||
grade: 年级(如'一年级','初一','高一','大学'),不传则从年龄推断
|
||||
test_values: dict
|
||||
BMI, 肺活量(ml), 50米跑(秒), 坐位体前屈(cm),
|
||||
1分钟跳绳(次) — 小学
|
||||
立定跳远(cm) — 初中以上
|
||||
引体向上(次) — 初中以上男
|
||||
1分钟仰卧起坐(次) — 三年级以上女/小学
|
||||
耐力跑(秒) — 五年级以上
|
||||
"""
|
||||
if data is None:
|
||||
data = load_data()
|
||||
|
||||
if grade is None:
|
||||
grade = age_to_grade(age)
|
||||
|
||||
gender_key = '男' if gender in ['男', 'male', 'M'] else '女'
|
||||
|
||||
results = {
|
||||
'age': age,
|
||||
'gender': gender_key,
|
||||
'grade': grade,
|
||||
'level': get_level_for_grade(grade),
|
||||
'standard': '国家学生体质健康标准(2014年修订)',
|
||||
'base_score': 0,
|
||||
'bonus_score': 0,
|
||||
'total_score': 0,
|
||||
'rating': '',
|
||||
'indicators': {},
|
||||
'bonus_detail': {}
|
||||
}
|
||||
|
||||
# 获取权重表
|
||||
level_name = get_level_for_grade(grade)
|
||||
weights = data['weight_table'].get(level_name, {})
|
||||
# 加上通用权重(BMI和肺活量是全部适用)
|
||||
if level_name != '小学一年级至大学四年级':
|
||||
common = data['weight_table'].get('小学一年级至大学四年级', {})
|
||||
for k, v in common.items():
|
||||
if k not in weights:
|
||||
weights[k] = v
|
||||
|
||||
if not test_values:
|
||||
test_values = {}
|
||||
|
||||
# 指标别名映射
|
||||
alias = {
|
||||
'肺活量(ml)': '肺活量', '肺活量': '肺活量',
|
||||
'50米跑(秒)': '50米跑', '50米跑': '50米跑',
|
||||
'坐位体前屈(cm)': '坐位体前屈', '坐位体前屈': '坐位体前屈',
|
||||
'1分钟跳绳(次)': '1分钟跳绳', '1分钟跳绳': '1分钟跳绳',
|
||||
'1分钟仰卧起坐(次)': '1分钟仰卧起坐', '1分钟仰卧起坐': '1分钟仰卧起坐',
|
||||
'立定跳远(cm)': '立定跳远', '立定跳远': '立定跳远',
|
||||
'引体向上(次)': '引体向上', '引体向上': '引体向上',
|
||||
'耐力跑(秒)': '耐力跑', '耐力跑': '耐力跑',
|
||||
'1000米跑(秒)': '耐力跑', '800米跑(秒)': '耐力跑',
|
||||
'50米×8往返跑(秒)': '耐力跑',
|
||||
}
|
||||
|
||||
# 评分表索引映射:表1-x → 指标名+性别
|
||||
table_index_map = {}
|
||||
for t in data['scoring_tables']:
|
||||
ti = t['table_index']
|
||||
gn = t['grade_names']
|
||||
# 通过表index和年级范围推断指标
|
||||
table_index_map[ti] = t
|
||||
|
||||
# 指标→表索引映射(手动)
|
||||
indicator_table_map = {
|
||||
('BMI', '男'): 1, ('BMI', '女'): 2,
|
||||
('肺活量', '男'): 3, ('肺活量', '女'): 4,
|
||||
('50米跑', '男'): 5, ('50米跑', '女'): 6,
|
||||
('坐位体前屈', '男'): 7, ('坐位体前屈', '女'): 8,
|
||||
('1分钟跳绳', '男'): 9, ('1分钟跳绳', '女'): 10,
|
||||
('立定跳远', '男'): 11, ('立定跳远', '女'): 12,
|
||||
('1分钟仰卧起坐/引体向上', '男'): 13,
|
||||
('引体向上', '男'): 13,
|
||||
('1分钟仰卧起坐', '女'): 14,
|
||||
('耐力跑', '男'): 15, ('耐力跑', '女'): 16,
|
||||
}
|
||||
|
||||
# 加分表索引映射
|
||||
bonus_table_map = {
|
||||
('1分钟跳绳', '男'): 17, ('1分钟跳绳', '女'): 18,
|
||||
('引体向上', '男'): 19,
|
||||
('1分钟仰卧起坐', '女'): 20,
|
||||
('耐力跑', '男'): 21, ('耐力跑', '女'): 22,
|
||||
}
|
||||
|
||||
# 1. 处理输入值并逐项评分
|
||||
processed = {}
|
||||
for raw_key, value in test_values.items():
|
||||
if value is None or value == '':
|
||||
continue
|
||||
try:
|
||||
value = float(value)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
indicator = alias.get(raw_key, raw_key)
|
||||
processed[indicator] = value
|
||||
|
||||
# BMI特殊处理(可能没直接传入,从身高体重计算)
|
||||
if 'BMI' not in processed:
|
||||
height_cm = test_values.get('身高(cm)', test_values.get('身高'))
|
||||
weight_kg = test_values.get('体重(kg)', test_values.get('体重'))
|
||||
if height_cm and weight_kg:
|
||||
bmi = weight_kg / ((height_cm / 100) ** 2)
|
||||
processed['BMI'] = round(bmi, 1)
|
||||
|
||||
# 2. 评分
|
||||
base_total = 0.0
|
||||
weight_used = 0.0
|
||||
|
||||
for indicator, value in processed.items():
|
||||
# 查评分表
|
||||
table_key = (indicator, gender_key)
|
||||
ti = indicator_table_map.get(table_key)
|
||||
table = table_index_map.get(ti) if ti else None
|
||||
|
||||
if not table:
|
||||
continue
|
||||
|
||||
# 使用通配年级匹配
|
||||
match_grade = get_grade_for_level(grade, table)
|
||||
direction = INDICATOR_DIRECTION.get(indicator, '+')
|
||||
score = find_in_scoring_table(table, match_grade, value, direction)
|
||||
|
||||
# 查找该指标权重(用WEIGHT_INDICATOR_MAP做模糊匹配)
|
||||
indicator_weight = 0
|
||||
for wname, wval in weights.items():
|
||||
mapped_indicator = WEIGHT_INDICATOR_MAP.get(wname, wname)
|
||||
if indicator == mapped_indicator or indicator in wname or mapped_indicator == indicator:
|
||||
indicator_weight = wval / 100.0
|
||||
break
|
||||
|
||||
weighted = score * indicator_weight
|
||||
base_total += weighted
|
||||
weight_used += indicator_weight
|
||||
|
||||
results['indicators'][indicator] = {
|
||||
'value': value,
|
||||
'score': score,
|
||||
'weight': indicator_weight,
|
||||
'weighted': round(weighted, 1)
|
||||
}
|
||||
|
||||
results['base_score'] = round(base_total, 1)
|
||||
|
||||
# 3. 附加分
|
||||
total_bonus = 0
|
||||
for indicator, value in processed.items():
|
||||
bti = bonus_table_map.get((indicator, gender_key))
|
||||
btable = table_index_map.get(bti) if bti else None
|
||||
if not btable:
|
||||
continue
|
||||
|
||||
# 检查该指标原始得分是否已达到100分(即需要先拿到100分才有加分资格)
|
||||
# 但在评分表中,低年级跳绳最高分可能是100对应的值
|
||||
# 加分表只对超过100分阈值的额外成绩加分
|
||||
bonus = find_bonus_in_table(btable, grade, value)
|
||||
if bonus > 0:
|
||||
if indicator == '1分钟跳绳':
|
||||
max_bonus = 20
|
||||
else:
|
||||
max_bonus = 10
|
||||
|
||||
total_bonus += min(bonus, max_bonus)
|
||||
results['bonus_detail'][indicator] = min(bonus, max_bonus)
|
||||
|
||||
results['bonus_score'] = min(total_bonus, 20)
|
||||
results['total_score'] = round(results['base_score'] + results['bonus_score'], 1)
|
||||
|
||||
# 4. 评级
|
||||
ts = results['total_score']
|
||||
if ts >= 90:
|
||||
results['rating'] = '优秀'
|
||||
elif ts >= 80:
|
||||
results['rating'] = '良好'
|
||||
elif ts >= 60:
|
||||
results['rating'] = '及格'
|
||||
else:
|
||||
results['rating'] = '不及格'
|
||||
|
||||
results['weight_coverage'] = round(weight_used, 2)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def format_report(result):
|
||||
lines = []
|
||||
lines.append("=" * 60)
|
||||
lines.append(" 国家学生体质健康标准(2014年修订)— 评测报告")
|
||||
lines.append("=" * 60)
|
||||
lines.append(f" 年级:{result['grade']} 性别:{result['gender']}")
|
||||
lines.append(f" 年龄:{result['age']}岁 学段:{result['level']}")
|
||||
lines.append("-" * 60)
|
||||
lines.append(f" {'指标':<18} {'实测值':<10} {'得分':<6} {'权重':<6} {'加权分':<8}")
|
||||
lines.append("-" * 60)
|
||||
|
||||
for ind, info in result['indicators'].items():
|
||||
val = info['value']
|
||||
val_str = f"{val:.1f}" if isinstance(val, (int, float)) else str(val)
|
||||
w_str = f"{info['weight']:.2f}" if info['weight'] > 0 else '-'
|
||||
wp_str = f"{info['weighted']:.1f}" if info['weight'] > 0 else '-'
|
||||
lines.append(f" {ind:<18} {val_str:<10} {info['score']:<6} {w_str:<6} {wp_str:<8}")
|
||||
|
||||
lines.append("-" * 60)
|
||||
lines.append(f" 标准分:{result['base_score']}")
|
||||
if result['bonus_score'] > 0:
|
||||
lines.append(f" 附加分:+{result['bonus_score']}")
|
||||
for ind, bonus in result['bonus_detail'].items():
|
||||
lines.append(f" {ind} 加分:+{bonus}")
|
||||
lines.append(f" 总分(满分120):{result['total_score']}")
|
||||
lines.append(f" 权重覆盖率:{result['weight_coverage']:.0%}")
|
||||
lines.append(f" 评级:{result['rating']}")
|
||||
lines.append("=" * 60)
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def demo():
|
||||
data = load_data()
|
||||
|
||||
demos = [
|
||||
(10, '男', '四年级', {'BMI': 17, '肺活量(ml)': 1800, '50米跑(秒)': 9.5,
|
||||
'坐位体前屈(cm)': 8, '1分钟跳绳(次)': 90, '1分钟仰卧起坐(次)': 25}),
|
||||
(15, '男', '初三', {'BMI': 21, '肺活量(ml)': 3200, '50米跑(秒)': 7.8,
|
||||
'坐位体前屈(cm)': 6, '立定跳远(cm)': 220, '引体向上(次)': 8, '耐力跑(秒)': 255}),
|
||||
(13, '女', '初二', {'身高(cm)': 160, '体重(kg)': 50, '肺活量(ml)': 2500,
|
||||
'50米跑(秒)': 8.5, '坐位体前屈(cm)': 14, '立定跳远(cm)': 180,
|
||||
'1分钟仰卧起坐(次)': 30, '耐力跑(秒)': 240}),
|
||||
]
|
||||
|
||||
for i, (age, gender, grade, tests) in enumerate(demos):
|
||||
print(f"\n{'='*60}")
|
||||
print(f" 演示{i+1}:{grade}{'男' if gender=='男' else '女'},{age}岁")
|
||||
print(f"{'='*60}")
|
||||
r = evaluate_student(age, gender, grade, tests, data)
|
||||
print(format_report(r))
|
||||
|
||||
|
||||
def interactive():
|
||||
data = load_data()
|
||||
print("国家学生体质健康标准(2014年修订)— 交互式评测")
|
||||
print("=" * 50)
|
||||
|
||||
age = int(input("年龄(周岁): ") or 0)
|
||||
grade = input("年级(如'初一',直接回车从年龄推断): ").strip() or None
|
||||
gender = input("性别(男/女): ").strip()
|
||||
if gender not in ['男', '女']:
|
||||
print("性别输入错误")
|
||||
return
|
||||
|
||||
if grade is None:
|
||||
grade = age_to_grade(age)
|
||||
grade = grade.replace(' ', '')
|
||||
print(f"\n→ 年级:{grade}({get_level_for_grade(grade)}组)")
|
||||
|
||||
# 提示输入
|
||||
test_values = {}
|
||||
prompts = {
|
||||
'BMI': '体重指数(BMI)(直接回车从身高体重计算)',
|
||||
'身高(cm)': '身高(cm)',
|
||||
'体重(kg)': '体重(kg)',
|
||||
'肺活量(ml)': '肺活量(ml)',
|
||||
'50米跑(秒)': '50米跑(秒)',
|
||||
'坐位体前屈(cm)': '坐位体前屈(cm)',
|
||||
'1分钟跳绳(次)': '1分钟跳绳(次)',
|
||||
'1分钟仰卧起坐(次)': '1分钟仰卧起坐(次)',
|
||||
'立定跳远(cm)': '立定跳远(cm)',
|
||||
'引体向上(次)': '引体向上(次)',
|
||||
'耐力跑(秒)': '耐力跑(秒)(如1000米跑238秒则输入238)',
|
||||
}
|
||||
print("\n请输入测试值(直接回车跳过):")
|
||||
for key, prompt in prompts.items():
|
||||
val = input(f" {prompt}: ").strip()
|
||||
if val:
|
||||
try:
|
||||
test_values[key] = float(val)
|
||||
except ValueError:
|
||||
test_values[key] = val
|
||||
|
||||
result = evaluate_student(age, gender, grade, test_values, data)
|
||||
print()
|
||||
print(format_report(result))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ('--interactive', '-i'):
|
||||
interactive()
|
||||
else:
|
||||
demo()
|
||||
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
国家学生体质健康标准(2014年修订)— 评分数据提取脚本
|
||||
教育部发布,适用于全日制小学至大学学生
|
||||
满分120分制(100基础分 + 20附加分)
|
||||
"""
|
||||
import json, os, re, sys
|
||||
from docx import Document
|
||||
|
||||
BASE = '/home/songyi/Documents/ai_agent_scraper_study/data/国家学生体质健康标准'
|
||||
DOCX = os.path.join(BASE, '01_来源数据/国家学生体质健康标准(2014年修订).docx')
|
||||
|
||||
|
||||
def parse_range(val):
|
||||
"""解析值域:'13.5~18.1', '≤13.4', '≥17.9'"""
|
||||
val = val.strip().replace(' ', '').replace('\n','')
|
||||
if re.match(r'[≥>]=?\s*([\d.]+)', val):
|
||||
m = re.match(r'[≥>]=?\s*([\d.]+)', val)
|
||||
return {"min": float(m.group(1))}
|
||||
if re.match(r'[≤<]=?\s*([\d.]+)', val):
|
||||
m = re.match(r'[≤<]=?\s*([\d.]+)', val)
|
||||
return {"max": float(m.group(1))}
|
||||
if re.match(r'([\d.]+)\s*[~-]\s*([\d.]+)', val):
|
||||
m = re.match(r'([\d.]+)\s*[~-]\s*([\d.]+)', val)
|
||||
lo, hi = float(m.group(1)), float(m.group(2))
|
||||
return {"min": min(lo, hi), "max": max(lo, hi)}
|
||||
if re.match(r'(\d+)[::·]\s*(\d+[\']?[\d.]*)', val):
|
||||
m = re.match(r'(\d+)[::·]\s*(\d+[\']?[\d.]*)', val)
|
||||
minutes = int(m.group(1))
|
||||
rest = m.group(2).replace("'", ".")
|
||||
seconds = float(rest) if '.' in rest else int(rest)
|
||||
return {"exact": minutes * 60 + seconds}
|
||||
if re.match(r'^([\d.]+)$', val):
|
||||
m = re.match(r'^([\d.]+)$', val)
|
||||
return {"exact": float(m.group(1))}
|
||||
return {"raw": val}
|
||||
|
||||
|
||||
def extract_all():
|
||||
doc = Document(DOCX)
|
||||
|
||||
result = {
|
||||
'metadata': {
|
||||
'title': '国家学生体质健康标准(2014年修订)',
|
||||
'publisher': '教育部',
|
||||
'year': 2014,
|
||||
'total_tables': len(doc.tables),
|
||||
'scoring_system': '100分基础 + 20分附加 = 120分满分',
|
||||
'rating_levels': {
|
||||
'优秀': {'min': 90.0},
|
||||
'良好': {'min': 80.0, 'max': 89.9},
|
||||
'及格': {'min': 60.0, 'max': 79.9},
|
||||
'不及格': {'max': 59.9}
|
||||
}
|
||||
},
|
||||
'weight_table': {},
|
||||
'scoring_tables': [],
|
||||
'bonus_tables': [],
|
||||
'registration_tables': []
|
||||
}
|
||||
|
||||
for i, table in enumerate(doc.tables):
|
||||
rows_data = []
|
||||
for row in table.rows:
|
||||
cells = [cell.text.strip().replace('\n', ' ') for cell in row.cells]
|
||||
rows_data.append(cells)
|
||||
|
||||
if not rows_data:
|
||||
continue
|
||||
|
||||
header = rows_data[0]
|
||||
header_str = ' '.join(str(h) for h in header).strip()
|
||||
|
||||
# 权重表(表0)
|
||||
if i == 0:
|
||||
for row in rows_data[1:]:
|
||||
if len(row) >= 3:
|
||||
obj = row[0].strip()
|
||||
indicator = row[1].strip()
|
||||
weight = row[2].strip()
|
||||
try:
|
||||
w = float(weight)
|
||||
result['weight_table'].setdefault(obj, {})[indicator] = w
|
||||
except ValueError:
|
||||
pass
|
||||
continue
|
||||
|
||||
# 登记卡表
|
||||
if '学校签章' in header_str or ('姓名' in header_str and '性别' in header_str):
|
||||
result['registration_tables'].append({
|
||||
'table_index': i, 'rows': len(rows_data), 'cols': len(header)
|
||||
})
|
||||
continue
|
||||
|
||||
# 判断加分表
|
||||
is_bonus = '加分' in header_str and '成绩' not in header_str
|
||||
|
||||
# 解析年级列
|
||||
grade_cols = []
|
||||
start_col = 1 if is_bonus else 2
|
||||
for ci, h in enumerate(header):
|
||||
h_clean = h.strip().replace('\n', ' ')
|
||||
if h_clean and ci >= start_col:
|
||||
# 跳过'单项得分'列
|
||||
if '单项' in h_clean or '得分' in h_clean:
|
||||
continue
|
||||
grade_cols.append((ci, h_clean))
|
||||
|
||||
if not grade_cols:
|
||||
continue
|
||||
|
||||
table_info = {
|
||||
'table_index': i,
|
||||
'header_sample': [h.strip() for h in header[:5]],
|
||||
'grade_names': [g[1] for g in grade_cols],
|
||||
'type': 'bonus' if is_bonus else 'standard',
|
||||
'rows_count': len(rows_data) - 1,
|
||||
'data': []
|
||||
}
|
||||
|
||||
for row in rows_data[1:]:
|
||||
if is_bonus:
|
||||
if len(row) < 2:
|
||||
continue
|
||||
bonus_str = row[0].strip()
|
||||
try:
|
||||
bonus_score = int(bonus_str) if bonus_str.lstrip('-').isdigit() else 0
|
||||
except:
|
||||
continue
|
||||
entry = {'bonus_score': bonus_score, 'grades': {}}
|
||||
for ci, gname in grade_cols:
|
||||
if ci < len(row):
|
||||
val = row[ci].strip()
|
||||
if val and val not in ['—', '-', '', '------']:
|
||||
entry['grades'][gname] = parse_range(val)
|
||||
if entry['grades']:
|
||||
table_info['data'].append(entry)
|
||||
else:
|
||||
if len(row) < 3:
|
||||
continue
|
||||
level_name = row[0].strip()
|
||||
score_str = row[1].strip()
|
||||
try:
|
||||
score_val = int(score_str) if score_str.isdigit() else 0
|
||||
except:
|
||||
continue
|
||||
entry = {'level': level_name, 'score': score_val, 'grades': {}}
|
||||
for ci, gname in grade_cols:
|
||||
if ci < len(row):
|
||||
val = row[ci].strip()
|
||||
if val and val not in ['—', '-', '', '------']:
|
||||
entry['grades'][gname] = parse_range(val)
|
||||
if entry['grades']:
|
||||
table_info['data'].append(entry)
|
||||
|
||||
if table_info['data']:
|
||||
if is_bonus:
|
||||
result['bonus_tables'].append(table_info)
|
||||
else:
|
||||
result['scoring_tables'].append(table_info)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def print_summary(data):
|
||||
m = data['metadata']
|
||||
print(f"总Word表格: {m['total_tables']}")
|
||||
print(f"标准评分表: {len(data['scoring_tables'])} 个")
|
||||
print(f"加分评分表: {len(data['bonus_tables'])} 个")
|
||||
print(f"登记卡/附表: {len(data['registration_tables'])} 个")
|
||||
|
||||
wt = data['weight_table']
|
||||
print(f"\n权重表 ({len(wt)}组):")
|
||||
for obj in sorted(wt.keys()):
|
||||
items = ', '.join(f"{k}={v}%" for k, v in sorted(wt[obj].items()))
|
||||
print(f" {obj}: {items}")
|
||||
|
||||
print(f"\n标准评分表:")
|
||||
for t in data['scoring_tables']:
|
||||
gn = t['grade_names']
|
||||
gs = ', '.join(gn[:3])
|
||||
if len(gn) > 3:
|
||||
gs += f"...({len(gn)}个年级)"
|
||||
levels = sorted(set(e['level'] for e in t['data']))
|
||||
ti = t['table_index']
|
||||
rc = len(t['data'])
|
||||
print(f" [表{ti}] {rc}行x{len(gn)}年级 等级={levels} 年级={gs}")
|
||||
|
||||
print(f"\n加分评分表:")
|
||||
for t in data['bonus_tables']:
|
||||
gn = t['grade_names']
|
||||
gs = ', '.join(gn[:3])
|
||||
if len(gn) > 3:
|
||||
gs += f"...({len(gn)}个年级)"
|
||||
ti = t['table_index']
|
||||
rc = len(t['data'])
|
||||
print(f" [表{ti}] {rc}行 年级={gs}")
|
||||
|
||||
|
||||
def output_json(data):
|
||||
path = os.path.join(BASE, '02_加工数据/评分标准结构化数据.json')
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
size = os.path.getsize(path)
|
||||
print(f"\nJSON输出: {path} ({size/1024:.0f}KB)")
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
data = extract_all()
|
||||
print_summary(data)
|
||||
output_json(data)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,70 @@
|
||||
# 归档分析报告:《国家学生体质健康标准(2014年修订)》
|
||||
|
||||
## 一、基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 标准名称 | 国家学生体质健康标准(2014年修订) |
|
||||
| 发布机构 | 教育部 |
|
||||
| 发布时间 | 2014年 |
|
||||
| 文件格式 | Word (.docx),88KB |
|
||||
| 数据量 | 33个Word内嵌表格 |
|
||||
|
||||
## 二、评分体系
|
||||
|
||||
### 总分结构
|
||||
|
||||
**学年总分 = 标准分 + 附加分**
|
||||
- 标准分:各单项指标得分 × 权重,满分为100分
|
||||
- 附加分:超过100分的加分指标进行加分,满分为20分
|
||||
- 小学:1分钟跳绳,加分幅度20分
|
||||
- 初中/高中/大学:男生引体向上和1000米跑,女生仰卧起坐和800米跑,各10分
|
||||
|
||||
### 评级标准
|
||||
|
||||
| 等级 | 分数范围 |
|
||||
|------|----------|
|
||||
| 优秀 | ≥ 90.0分 |
|
||||
| 良好 | 80.0 ~ 89.9分 |
|
||||
| 及格 | 60.0 ~ 79.9分 |
|
||||
| 不及格 | ≤ 59.9分 |
|
||||
|
||||
### 权重体系(按学段不同)
|
||||
|
||||
| 指标 | 小学1-2 | 小学3-4 | 小学5-6 | 初中/高中/大学 |
|
||||
|------|---------|---------|---------|---------------|
|
||||
| BMI | 15% | 15% | 15% | 15% |
|
||||
| 肺活量 | 15% | 15% | 15% | 15% |
|
||||
| 50米跑 | 20% | 20% | 20% | 20% |
|
||||
| 坐位体前屈 | 30% | 20% | 10% | 10% |
|
||||
| 1分钟跳绳 | 20% | 20% | 10% | - |
|
||||
| 仰卧起坐 | - | 10% | 20% | - |
|
||||
| 立定跳远 | - | - | - | 10% |
|
||||
| 引体向上(男)/仰卧起坐(女) | - | - | - | 10% |
|
||||
| 耐力跑(男1000/女800) | - | - | 10% | 20% |
|
||||
|
||||
## 三、数据质量评估
|
||||
|
||||
| 维度 | 评价 | 说明 |
|
||||
|------|------|------|
|
||||
| 完整性 | ★★★★★ | 所有评分表、加分表、登记卡完整 |
|
||||
| 准确性 | ★★★★★ | Word原生表格,无OCR误差 |
|
||||
| 结构化程度 | ★★★★☆ | Word表格直接解析,无HTML嵌套 |
|
||||
| 可复用性 | ★★★★★ | JSON可直接编程调用 |
|
||||
|
||||
## 四、加工说明
|
||||
|
||||
| 步骤 | 工具 | 结果 |
|
||||
|------|------|------|
|
||||
| 表格提取 | python-docx | 33个Word表格全部解析 |
|
||||
| 结构化JSON | parse_student_tables.py | 318KB JSON,含权重表+23个标准评分表+6个加分表 |
|
||||
|
||||
## 五、后续建议
|
||||
|
||||
1. **开发评分引擎** — 类似 `fitness_eval.py`,按年级/性别自动查表计算总分和评级
|
||||
2. **与国民体质测定标准对比分析** — 两套标准的覆盖人群、指标、评分差异
|
||||
3. **批量评测** — 支持CSV学生数据批量处理
|
||||
|
||||
---
|
||||
|
||||
*报告生成日期:2026年6月*
|
||||
@@ -0,0 +1,95 @@
|
||||
# 国家学生体质健康标准(2014年修订)— 数据谱系说明
|
||||
|
||||
## 概述
|
||||
|
||||
本标准由教育部于2014年修订发布,适用于全日制普通小学、初中、普通高中、中等职业学校、普通高等学校的学生。是评价学生综合素质、评估学校工作和衡量各地教育发展的重要依据。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 属性 | 内容 |
|
||||
|------|------|
|
||||
| 标准名称 | 国家学生体质健康标准(2014年修订) |
|
||||
| 发布机构 | 教育部 |
|
||||
| 发布时间 | 2014年 |
|
||||
| 文件格式 | Word (.docx),88KB |
|
||||
| 数据来源 | docx内嵌Word表格(33个) |
|
||||
| 评分体系 | 100分基础 + 20分附加 = 120分满分 |
|
||||
| 评级等级 | 优秀(≥90)、良好(80-89)、及格(60-79)、不及格(<60) |
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
国家学生体质健康标准/
|
||||
├── 01_来源数据/
|
||||
│ └── 国家学生体质健康标准(2014年修订).docx (原始文档)
|
||||
├── 02_加工数据/
|
||||
│ ├── 评分标准结构化数据.json (318KB,结构化提取)
|
||||
│ └── parse_student_tables.py (解析脚本)
|
||||
├── 03_关联融合/ (预留)
|
||||
└── 04_分析报告/
|
||||
└── 归档分析报告.md
|
||||
```
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 测试对象分组
|
||||
|
||||
| 学段 | 年级分组 |
|
||||
|------|----------|
|
||||
| 小学 | 一年级至六年级(各年级独立一组) |
|
||||
| 初中 | 初一至初三(各年级独立一组) |
|
||||
| 高中 | 高一至高三(各年级独立一组) |
|
||||
| 大学 | 一、二年级为一组;三、四年级为一组 |
|
||||
|
||||
### 测试指标(按年级)
|
||||
|
||||
| 年级 | 测试指标 | 加分指标 |
|
||||
|------|----------|----------|
|
||||
| 小学一、二年级 | BMI、肺活量、50米跑、坐位体前屈 | 1分钟跳绳 |
|
||||
| 小学三、四年级 | BMI、肺活量、50米跑、坐位体前屈、1分钟跳绳 | 1分钟跳绳 |
|
||||
| 小学五、六年级 | BMI、肺活量、50米跑、坐位体前屈、1分钟跳绳、1分钟仰卧起坐 | 1分钟跳绳 |
|
||||
| 初中/高中/大学 | BMI、肺活量、50米跑、坐位体前屈、立定跳远、引体向上(男)/仰卧起坐(女)、1000米跑(男)/800米跑(女) | 引体向上(男)/仰卧起坐(女)、1000米跑(男)/800米跑(女) |
|
||||
|
||||
### 评分表(23个标准评分表 + 6个加分表)
|
||||
|
||||
| 表号 | 内容 | 覆盖年级 |
|
||||
|------|------|----------|
|
||||
| 1-1 | 男生BMI评分表 | 小学一年级至大学 |
|
||||
| 1-2 | 女生BMI评分表 | 小学一年级至大学 |
|
||||
| 1-3 | 男生肺活量评分表 | 小学一年级至大学 |
|
||||
| 1-4 | 女生肺活量评分表 | 小学一年级至大学 |
|
||||
| 1-5 | 男生50米跑评分表 | 小学一年级至大学 |
|
||||
| 1-6 | 女生50米跑评分表 | 小学一年级至大学 |
|
||||
| 1-7 | 男生坐位体前屈评分表 | 小学一年级至大学 |
|
||||
| 1-8 | 女生坐位体前屈评分表 | 小学一年级至大学 |
|
||||
| 1-9 | 男生1分钟跳绳评分表 | 小学一年级至六年级 |
|
||||
| 1-10 | 女生1分钟跳绳评分表 | 小学一年级至六年级 |
|
||||
| 1-11 | 男生立定跳远评分表 | 初一至大学 |
|
||||
| 1-12 | 女生立定跳远评分表 | 初一至大学 |
|
||||
| 1-13 | 男生仰卧起坐/引体向上评分表 | 三年级至大学 |
|
||||
| 1-14 | 女生1分钟仰卧起坐评分表 | 三年级至大学 |
|
||||
| 1-15 | 男生耐力跑评分表 | 五年级至大学 |
|
||||
| 1-16 | 女生耐力跑评分表 | 五年级至大学 |
|
||||
| 2-1~2-6 | 加分指标评分表 | 对应年级范围 |
|
||||
|
||||
## 与国民体质测定标准的差异
|
||||
|
||||
| 对比项 | 学生体质健康标准(2014) | 国民体质测定标准(2023修订) |
|
||||
|--------|----------------------|--------------------------|
|
||||
| 发布机构 | 教育部 | 国家国民体质监测中心 |
|
||||
| 适用人群 | 在校学生(小学至大学) | 3-79岁全体国民 |
|
||||
| 分组方式 | 按年级 | 按年龄 |
|
||||
| 评分制 | 100分+20分附加 | 100分 |
|
||||
| 评级 | 优秀≥90 | 优秀≥83 |
|
||||
| 加分项 | 跳绳/引体向上/仰卧起坐/耐力跑 | 无 |
|
||||
|
||||
## 关联知识库
|
||||
|
||||
- **国民体质测定标准**(同目录下)
|
||||
- **健康管理师教材目录归档**
|
||||
- **大医网**中医药知识库
|
||||
|
||||
---
|
||||
|
||||
*归档日期:2026年6月*
|
||||
*归档工具:Hermes Agent 自动归档*
|
||||
Reference in new issue
Block a user