| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- """
- 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")
|