Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
211 lines
8.3 KiB
Python
211 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
覆盖率审计脚本
|
||
检查:
|
||
1. 二期ETL输出的字段名称,赋分引擎实际用到了多少
|
||
2. 赋分引擎里按关键词匹配的字段,在二期数据中能否命中
|
||
3. 每个维度的赋分数据源覆盖情况
|
||
"""
|
||
import sys
|
||
import re
|
||
import inspect
|
||
from pathlib import Path
|
||
from collections import defaultdict
|
||
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "backend"))
|
||
|
||
from data_engine_era2 import DataEngineEra2
|
||
from app.engines.scoring_engine import ScoringEngine
|
||
from config_era2 import DIMENSION_FRAMEWORK, SUBJECTS
|
||
|
||
|
||
def section(title):
|
||
print(f"\n{'=' * 80}")
|
||
print(f" {title}")
|
||
print(f"{'=' * 80}")
|
||
|
||
|
||
def main():
|
||
# ===== 加载二期全量数据(不分区,看全貌) =====
|
||
section("1. 加载二期全量数据")
|
||
engine = DataEngineEra2(district_filter=None)
|
||
engine.load_all()
|
||
print(f" 学校数: {len(engine.schools)}")
|
||
|
||
# 获取全量字段名称
|
||
course_df = engine._course_impl
|
||
subject_df = engine._subject_impl
|
||
basic_df = engine._basic_info
|
||
|
||
course_fields = sorted(course_df["字段名称"].dropna().unique())
|
||
subject_fields = sorted(subject_df["字段名称"].dropna().unique())
|
||
basic_fields = sorted(basic_df["字段名称"].dropna().unique())
|
||
|
||
print(f" 课程实施表字段名称数: {len(course_fields)}")
|
||
print(f" 学科课程表字段名称数: {len(subject_fields)}")
|
||
print(f" 基础信息表字段名称数: {len(basic_fields)}")
|
||
|
||
# ===== 2. 提取赋分引擎中所有用到的字段名称和关键词 =====
|
||
section("2. 赋分引擎字段引用分析")
|
||
|
||
# 从 ScoringEngine 源码中提取所有字符串常量(字段名称/关键词)
|
||
src = inspect.getsource(ScoringEngine)
|
||
|
||
# 提取所有中文字符串(字段名称)
|
||
# 匹配双引号和单引号中的中文字符串
|
||
field_refs = set()
|
||
keyword_refs = set()
|
||
|
||
# 精确字段引用:df["字段名称"] == "xxx" 或 字段名称 == field
|
||
exact_patterns = re.findall(r'["\']([^"\']*[\u4e00-\u9fff][^"\']*)["\']', src)
|
||
for p in exact_patterns:
|
||
# 跳过注释性文字
|
||
if len(p) > 30 or ':' in p or '。' in p or '赋分' in p:
|
||
continue
|
||
field_refs.add(p)
|
||
|
||
# 关键词引用:str.contains("xxx")
|
||
contains_patterns = re.findall(r'\.str\.contains\(["\']([^"\']+)["\']', src)
|
||
for p in contains_patterns:
|
||
# 这些是用 | 分隔的关键词
|
||
for kw in p.split('|'):
|
||
keyword_refs.add(kw.strip())
|
||
|
||
# 其他关键词引用(in循环中的列表)
|
||
keyword_lists = re.findall(r'for (?:keyword|field|hw_type|resource) in \[([^\]]+)\]', src)
|
||
for kl in keyword_lists:
|
||
items = re.findall(r'["\']([^"\']+)["\']', kl)
|
||
for item in items:
|
||
if any('\u4e00' <= c <= '\u9fff' for c in item):
|
||
field_refs.add(item)
|
||
|
||
print(f"\n 赋分引擎中精确引用的字段名称: {len(field_refs)}个")
|
||
print(f" 赋分引擎中关键词引用: {len(keyword_refs)}个")
|
||
|
||
# ===== 3. 逐一检查精确字段在二期数据中的命中情况 =====
|
||
section("3. 精确字段匹配检查")
|
||
|
||
all_data_fields = set(course_fields) | set(subject_fields) | set(basic_fields)
|
||
|
||
matched = []
|
||
missing = []
|
||
for f in sorted(field_refs):
|
||
if f in all_data_fields:
|
||
matched.append(f)
|
||
else:
|
||
missing.append(f)
|
||
|
||
print(f"\n ✅ 命中: {len(matched)}/{len(field_refs)}")
|
||
print(f" ❌ 未命中: {len(missing)}/{len(field_refs)}")
|
||
|
||
if missing:
|
||
print(f"\n 未命中的字段(赋分引擎引用但二期数据中不存在):")
|
||
for f in missing:
|
||
# 尝试模糊匹配
|
||
fuzzy = [df for df in all_data_fields if f.replace('_', '') in df.replace('_', '') or df.replace('_', '') in f.replace('_', '')]
|
||
if fuzzy:
|
||
print(f" ❌ {f}")
|
||
print(f" → 可能对应: {fuzzy[:3]}")
|
||
else:
|
||
print(f" ❌ {f} (无近似匹配)")
|
||
|
||
# ===== 4. 关键词匹配检查 =====
|
||
section("4. 关键词匹配检查")
|
||
|
||
for kw in sorted(keyword_refs):
|
||
course_hits = course_df[course_df["字段名称"].str.contains(kw, na=False)]["字段名称"].unique()
|
||
subject_hits = subject_df[subject_df["字段名称"].str.contains(kw, na=False)]["字段名称"].unique()
|
||
total = len(course_hits) + len(subject_hits)
|
||
status = "✅" if total > 0 else "❌"
|
||
print(f" {status} '{kw}': 课程表{len(course_hits)}个, 学科表{len(subject_hits)}个")
|
||
if total == 0:
|
||
# 看看有没有相近的
|
||
all_names = list(course_fields) + list(subject_fields)
|
||
similar = [n for n in all_names if kw[:2] in n][:3]
|
||
if similar:
|
||
print(f" → 近似: {similar}")
|
||
|
||
# ===== 5. 按维度逐一检查赋分数据覆盖 =====
|
||
section("5. 按维度检查赋分数据覆盖(抽样一所学校)")
|
||
|
||
# 取一所数据较完整的学校
|
||
test_school = engine.schools[0]
|
||
print(f" 测试学校: {test_school}")
|
||
|
||
scoring = ScoringEngine(engine)
|
||
scores = scoring._score_school(test_school)
|
||
|
||
print(f"\n {'维度':<20} {'赋分项数':>8} {'均值':>8} {'是否有效':>8}")
|
||
print(f" {'─' * 50}")
|
||
for dim, vals in scores.items():
|
||
n = len(vals)
|
||
avg = np.mean(vals) if vals else 0
|
||
# 判断是否有效:是否全是默认值
|
||
is_default = (n <= 1 and abs(avg - 1.0) < 0.01) or (n <= 1 and abs(avg - 1.5) < 0.01) or (n <= 1 and abs(avg - 2.0) < 0.01) or (n <= 1 and abs(avg - 0.5) < 0.01)
|
||
status = "⚠️ 默认值" if is_default else "✅"
|
||
print(f" {dim:<20} {n:>8} {avg:>8.2f} {status:>8}")
|
||
|
||
# ===== 6. 多校抽样统计 =====
|
||
section("6. 多校统计:各维度赋分项数分布")
|
||
|
||
# 取前20所学校统计
|
||
sample_schools = engine.schools[:20]
|
||
dim_stats = defaultdict(list)
|
||
|
||
for school in sample_schools:
|
||
s = scoring._score_school(school)
|
||
for dim, vals in s.items():
|
||
dim_stats[dim].append(len(vals))
|
||
|
||
print(f"\n 抽样学校数: {len(sample_schools)}")
|
||
print(f"\n {'维度':<20} {'最小':>6} {'最大':>6} {'均值':>6} {'全为1':>8}")
|
||
print(f" {'─' * 50}")
|
||
for dim in DIMENSION_FRAMEWORK:
|
||
for sub in DIMENSION_FRAMEWORK[dim]["sub_dimensions"]:
|
||
vals = dim_stats.get(sub, [0])
|
||
min_v = min(vals)
|
||
max_v = max(vals)
|
||
avg_v = np.mean(vals)
|
||
all_one = sum(1 for v in vals if v <= 1)
|
||
warn = "⚠️" if all_one > len(vals) * 0.5 else ""
|
||
print(f" {sub:<20} {min_v:>6} {max_v:>6} {avg_v:>6.1f} {all_one:>4}/{len(vals)} {warn}")
|
||
|
||
# ===== 7. 二期新增但赋分引擎未使用的字段 =====
|
||
section("7. 二期数据中存在但赋分引擎未引用的高频字段(Top 30)")
|
||
|
||
# 统计二期中每个字段名称出现的学校数
|
||
course_field_school_count = course_df.groupby("字段名称")["学校名称"].nunique().sort_values(ascending=False)
|
||
subject_field_school_count = subject_df.groupby("字段名称")["学校名称"].nunique().sort_values(ascending=False)
|
||
|
||
# 过滤掉已被赋分引擎引用的
|
||
unused_course = course_field_school_count[~course_field_school_count.index.isin(field_refs)]
|
||
unused_subject = subject_field_school_count[~subject_field_school_count.index.isin(field_refs)]
|
||
|
||
# 进一步过滤:去掉被关键词匹配可能命中的
|
||
def is_keyword_matched(field_name):
|
||
for kw in keyword_refs:
|
||
if kw in str(field_name):
|
||
return True
|
||
return False
|
||
|
||
unused_course_strict = unused_course[~unused_course.index.map(is_keyword_matched)]
|
||
unused_subject_strict = unused_subject[~unused_subject.index.map(is_keyword_matched)]
|
||
|
||
print(f"\n [课程实施表] 未被引用的字段 (按学校覆盖率排序, Top 20):")
|
||
for field, cnt in unused_course_strict.head(20).items():
|
||
print(f" {field}: {cnt}所学校有数据")
|
||
|
||
print(f"\n [学科课程表] 未被引用的字段 (Top 20):")
|
||
for field, cnt in unused_subject_strict.head(20).items():
|
||
print(f" {field}: {cnt}所学校有数据")
|
||
|
||
print(f"\n✅ 覆盖率审计完成!")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|