quality_rules.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. """
  2. Quality inspection rule definitions for PPT quality assurance.
  3. Each rule defines a check function, severity level, and auto-fix strategy.
  4. """
  5. from pptx.util import Emu, Pt
  6. from dataclasses import dataclass, field
  7. from typing import Callable, Optional
  8. @dataclass
  9. class QualityRule:
  10. rule_id: str
  11. category: str
  12. description: str
  13. severity: str
  14. auto_fixable: bool
  15. check_fn: str
  16. fix_fn: str
  17. QUALITY_RULES = [
  18. QualityRule('L001', 'layout', '元素飞出页面左边界', 'critical', True, '_check_left_bounds', '_fix_left_bounds'),
  19. QualityRule('L002', 'layout', '元素飞出页面右边界', 'critical', True, '_check_right_bounds', '_fix_right_bounds'),
  20. QualityRule('L003', 'layout', '元素飞出页面顶部', 'critical', True, '_check_top_bounds', '_fix_top_bounds'),
  21. QualityRule('L004', 'layout', '元素飞出页面底部', 'critical', True, '_check_bottom_bounds', '_fix_bottom_bounds'),
  22. QualityRule('L005', 'layout', '图文重叠', 'critical', True, '_check_overlap', '_fix_overlap'),
  23. QualityRule('L006', 'layout', '占位符未替换', 'critical', True, '_check_placeholders', '_fix_placeholders'),
  24. QualityRule('L007', 'layout', '元素紧贴页面边缘', 'minor', True, '_check_edge_proximity', '_fix_edge_proximity'),
  25. QualityRule('V001', 'visual', '字体不一致', 'minor', True, '_check_font_consistency', '_fix_font_consistency'),
  26. QualityRule('V002', 'visual', '字号过小(<8pt)', 'major', True, '_check_font_too_small', '_fix_font_too_small'),
  27. QualityRule('V003', 'visual', '字号过大(>60pt)', 'major', True, '_check_font_too_large', '_fix_font_too_large'),
  28. QualityRule('V004', 'visual', '颜色对比度不足', 'major', True, '_check_contrast', '_fix_contrast'),
  29. QualityRule('V005', 'visual', '图片拉伸变形', 'major', True, '_check_image_aspect', '_fix_image_aspect'),
  30. QualityRule('V006', 'visual', '封面文字颜色与背景冲突(白色文字在浅色背景上不可见)', 'critical', True,
  31. '_check_cover_text_visibility', '_fix_cover_text_visibility'),
  32. QualityRule('C001', 'content', '页面留白过多(填充率<35%)', 'critical', True, '_check_sparse_page', '_fix_sparse_page'),
  33. QualityRule('C002', 'content', 'KPI卡片数值为空', 'critical', True, '_check_empty_kpi', '_fix_empty_kpi'),
  34. QualityRule('C003', 'content', '图表无数据', 'critical', True, '_check_empty_chart', '_fix_empty_chart'),
  35. QualityRule('C004', 'content', '文本截断溢出', 'major', True, '_check_text_overflow', '_fix_text_overflow'),
  36. QualityRule('C005', 'content', '分析文本过短(<100字)', 'critical', True, '_check_short_text', '_fix_short_text'),
  37. QualityRule('C006', 'content', '页面缺少标题', 'critical', True, '_check_missing_title', '_fix_missing_title'),
  38. QualityRule('C007', 'content', '分析段数不足', 'critical', True, '_check_insight_count', '_fix_insight_count'),
  39. QualityRule('C008', 'content', '页面内容为空(<50字)', 'critical', True, '_check_empty_page', '_fix_empty_page'),
  40. QualityRule('C009', 'content', '图表缺少分析文本', 'critical', True, '_check_chart_no_text', '_fix_chart_no_text'),
  41. QualityRule('C012', 'content', '封面占位符仍为模板默认文字(未替换)', 'critical', False,
  42. '_check_cover_template_text', None),
  43. QualityRule('D001', 'data', '图表数据与文本矛盾', 'critical', False, '_check_data_text_contradiction', None),
  44. QualityRule('D002', 'data', '页码错乱', 'major', True, '_check_page_numbers', '_fix_page_numbers'),
  45. QualityRule('D003', 'data', '数据来源缺失', 'major', True, '_check_missing_source', '_fix_missing_source'),
  46. QualityRule('D004', 'data', '表格列宽不合理', 'minor', True, '_check_table_column_width', '_fix_table_column_width'),
  47. QualityRule('D005', 'data', '图表刻度异常', 'minor', True, '_check_axis_scale', '_fix_axis_scale'),
  48. ]
  49. QUALITY_RULES.extend([
  50. QualityRule('L008', 'layout', '空模板组件残留', 'major', True,
  51. '_check_empty_template_artifacts', '_fix_empty_template_artifacts'),
  52. QualityRule('C010', 'content', '动态页面与数据画像不匹配', 'critical', True,
  53. '_check_dynamic_page_fit', '_fix_rebuild_page'),
  54. QualityRule('C011', 'content', 'KPI布局容量不足', 'major', True,
  55. '_check_kpi_layout_capacity', '_fix_rebuild_page'),
  56. QualityRule('D006', 'data', '六项确认与输出不一致', 'critical', True,
  57. '_check_confirmation_alignment', '_fix_rebuild_page'),
  58. ])
  59. SEVERITY_WEIGHTS = {
  60. 'critical': 20,
  61. 'major': 10,
  62. 'minor': 3,
  63. }
  64. CATEGORY_WEIGHTS = {
  65. 'layout': 0.30,
  66. 'visual': 0.25,
  67. 'content': 0.25,
  68. 'data': 0.20,
  69. }
  70. FILL_RATIO_THRESHOLDS = {
  71. 'sparse': 0.20,
  72. 'low': 0.35,
  73. 'acceptable': 0.55,
  74. 'good': 0.70,
  75. }
  76. FONT_SIZE_MIN = Pt(8)
  77. FONT_SIZE_MAX = Pt(60)
  78. TEXT_MIN_LENGTH = 80
  79. INSIGHT_MIN_COUNT = 2
  80. PAGE_MIN_TEXT_LENGTH = 50
  81. SAFE_MARGIN = Emu(762000)
  82. CONTENT_LEFT = Emu(762000)
  83. CONTENT_TOP_BASE = Emu(1524000)
  84. FOOTER_TOP = Emu(8824000)
  85. SLIDE_WIDTH = 16256000
  86. SLIDE_HEIGHT = 9144000
  87. DEFAULT_FONT = '微软雅黑'
  88. DEFAULT_NUMBER_FONT = 'Arial'
  89. def get_rules_by_category(category: str) -> list[QualityRule]:
  90. return [r for r in QUALITY_RULES if r.category == category]
  91. def get_rules_by_severity(severity: str) -> list[QualityRule]:
  92. return [r for r in QUALITY_RULES if r.severity == severity]
  93. def calculate_score(issues_by_severity: dict, issues_by_category: dict, total_pages: int) -> int:
  94. if total_pages <= 0:
  95. return 100
  96. penalty = 0
  97. for sev, count in issues_by_severity.items():
  98. weight = SEVERITY_WEIGHTS.get(sev, 5)
  99. penalty += count * weight
  100. per_page_penalty = min(penalty / total_pages, 80)
  101. score = max(0, 100 - per_page_penalty)
  102. return int(score)
  103. def get_quality_label(score: int) -> str:
  104. if score >= 90:
  105. return '优质'
  106. elif score >= 75:
  107. return '良好'
  108. elif score >= 60:
  109. return '待改善'
  110. else:
  111. return '不合格'
  112. if __name__ == '__main__':
  113. print(f"Loaded {len(QUALITY_RULES)} quality rules")
  114. for cat in ['layout', 'visual', 'content', 'data']:
  115. rules = get_rules_by_category(cat)
  116. print(f" {cat}: {len(rules)} rules")
  117. for sev in ['critical', 'major', 'minor']:
  118. rules = get_rules_by_severity(sev)
  119. print(f" {sev}: {len(rules)} rules")