初始归档:健康管理资料库(大医网/药膳/体质监测/中医理论等12个资料集,7712个文件)

This commit is contained in:
512song committed 2026-09-23 21:59:25 +08:00
commit 80ae3811cf
7712 files changed
+4628547

No files matched your search

@@ -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()