""" Quality inspection rule definitions for PPT quality assurance. Each rule defines a check function, severity level, and auto-fix strategy. """ from pptx.util import Emu, Pt from dataclasses import dataclass, field from typing import Callable, Optional @dataclass class QualityRule: rule_id: str category: str description: str severity: str auto_fixable: bool check_fn: str fix_fn: str QUALITY_RULES = [ QualityRule('L001', 'layout', '元素飞出页面左边界', 'critical', True, '_check_left_bounds', '_fix_left_bounds'), QualityRule('L002', 'layout', '元素飞出页面右边界', 'critical', True, '_check_right_bounds', '_fix_right_bounds'), QualityRule('L003', 'layout', '元素飞出页面顶部', 'critical', True, '_check_top_bounds', '_fix_top_bounds'), QualityRule('L004', 'layout', '元素飞出页面底部', 'critical', True, '_check_bottom_bounds', '_fix_bottom_bounds'), QualityRule('L005', 'layout', '图文重叠', 'critical', True, '_check_overlap', '_fix_overlap'), QualityRule('L006', 'layout', '占位符未替换', 'critical', True, '_check_placeholders', '_fix_placeholders'), QualityRule('L007', 'layout', '元素紧贴页面边缘', 'minor', True, '_check_edge_proximity', '_fix_edge_proximity'), QualityRule('V001', 'visual', '字体不一致', 'minor', True, '_check_font_consistency', '_fix_font_consistency'), QualityRule('V002', 'visual', '字号过小(<8pt)', 'major', True, '_check_font_too_small', '_fix_font_too_small'), QualityRule('V003', 'visual', '字号过大(>60pt)', 'major', True, '_check_font_too_large', '_fix_font_too_large'), QualityRule('V004', 'visual', '颜色对比度不足', 'major', True, '_check_contrast', '_fix_contrast'), QualityRule('V005', 'visual', '图片拉伸变形', 'major', True, '_check_image_aspect', '_fix_image_aspect'), QualityRule('C001', 'content', '页面留白过多(填充率<35%)', 'critical', True, '_check_sparse_page', '_fix_sparse_page'), QualityRule('C002', 'content', 'KPI卡片数值为空', 'critical', True, '_check_empty_kpi', '_fix_empty_kpi'), QualityRule('C003', 'content', '图表无数据', 'critical', True, '_check_empty_chart', '_fix_empty_chart'), QualityRule('C004', 'content', '文本截断溢出', 'major', True, '_check_text_overflow', '_fix_text_overflow'), QualityRule('C005', 'content', '分析文本过短(<100字)', 'critical', True, '_check_short_text', '_fix_short_text'), QualityRule('C006', 'content', '页面缺少标题', 'critical', True, '_check_missing_title', '_fix_missing_title'), QualityRule('C007', 'content', '分析段数不足', 'critical', True, '_check_insight_count', '_fix_insight_count'), QualityRule('C008', 'content', '页面内容为空(<50字)', 'critical', True, '_check_empty_page', '_fix_empty_page'), QualityRule('C009', 'content', '图表缺少分析文本', 'critical', True, '_check_chart_no_text', '_fix_chart_no_text'), QualityRule('D001', 'data', '图表数据与文本矛盾', 'critical', False, '_check_data_text_contradiction', None), QualityRule('D002', 'data', '页码错乱', 'major', True, '_check_page_numbers', '_fix_page_numbers'), QualityRule('D003', 'data', '数据来源缺失', 'major', True, '_check_missing_source', '_fix_missing_source'), QualityRule('D004', 'data', '表格列宽不合理', 'minor', True, '_check_table_column_width', '_fix_table_column_width'), QualityRule('D005', 'data', '图表刻度异常', 'minor', True, '_check_axis_scale', '_fix_axis_scale'), ] QUALITY_RULES.extend([ QualityRule('L008', 'layout', '空模板组件残留', 'major', True, '_check_empty_template_artifacts', '_fix_empty_template_artifacts'), QualityRule('C010', 'content', '动态页面与数据画像不匹配', 'critical', True, '_check_dynamic_page_fit', '_fix_rebuild_page'), QualityRule('C011', 'content', 'KPI布局容量不足', 'major', True, '_check_kpi_layout_capacity', '_fix_rebuild_page'), QualityRule('D006', 'data', '六项确认与输出不一致', 'critical', True, '_check_confirmation_alignment', '_fix_rebuild_page'), ]) SEVERITY_WEIGHTS = { 'critical': 20, 'major': 10, 'minor': 3, } CATEGORY_WEIGHTS = { 'layout': 0.30, 'visual': 0.25, 'content': 0.25, 'data': 0.20, } FILL_RATIO_THRESHOLDS = { 'sparse': 0.20, 'low': 0.35, 'acceptable': 0.55, 'good': 0.70, } FONT_SIZE_MIN = Pt(8) FONT_SIZE_MAX = Pt(60) TEXT_MIN_LENGTH = 80 INSIGHT_MIN_COUNT = 2 PAGE_MIN_TEXT_LENGTH = 50 SAFE_MARGIN = Emu(762000) CONTENT_LEFT = Emu(762000) CONTENT_TOP_BASE = Emu(1524000) FOOTER_TOP = Emu(8824000) SLIDE_WIDTH = 16256000 SLIDE_HEIGHT = 9144000 DEFAULT_FONT = '微软雅黑' DEFAULT_NUMBER_FONT = 'Arial' def get_rules_by_category(category: str) -> list[QualityRule]: return [r for r in QUALITY_RULES if r.category == category] def get_rules_by_severity(severity: str) -> list[QualityRule]: return [r for r in QUALITY_RULES if r.severity == severity] def calculate_score(issues_by_severity: dict, issues_by_category: dict, total_pages: int) -> int: if total_pages <= 0: return 100 penalty = 0 for sev, count in issues_by_severity.items(): weight = SEVERITY_WEIGHTS.get(sev, 5) penalty += count * weight per_page_penalty = min(penalty / total_pages, 80) score = max(0, 100 - per_page_penalty) return int(score) def get_quality_label(score: int) -> str: if score >= 90: return '优质' elif score >= 75: return '良好' elif score >= 60: return '待改善' else: return '不合格' if __name__ == '__main__': print(f"Loaded {len(QUALITY_RULES)} quality rules") for cat in ['layout', 'visual', 'content', 'data']: rules = get_rules_by_category(cat) print(f" {cat}: {len(rules)} rules") for sev in ['critical', 'major', 'minor']: rules = get_rules_by_severity(sev) print(f" {sev}: {len(rules)} rules")