| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- """
- Report configuration data models for the universal data report generator.
- Defines ReportConfig, MetricDef, PageDef, ThemeConfig, and related enums.
- """
- from dataclasses import dataclass, field
- from enum import Enum
- from datetime import date
- from typing import Optional
- class PeriodType(str, Enum):
- DAILY = 'daily'
- WEEKLY = 'weekly'
- MONTHLY = 'monthly'
- QUARTERLY = 'quarterly'
- CUSTOM = 'custom'
- class AudienceType(str, Enum):
- MANAGEMENT = 'management'
- OPERATION = 'operation'
- CLIENT = 'client'
- CUSTOM = 'custom'
- class ComparisonType(str, Enum):
- PREV_PERIOD = 'prev_period'
- YOY = 'yoy'
- NONE = 'none'
- class ColumnRole(str, Enum):
- TIME = 'time'
- NUMERIC = 'numeric'
- CATEGORY = 'category'
- TEXT = 'text'
- ID = 'id'
- BOOLEAN = 'boolean'
- UNKNOWN = 'unknown'
- class AggregationType(str, Enum):
- SUM = 'sum'
- COUNT = 'count'
- AVG = 'avg'
- MAX = 'max'
- MIN = 'min'
- DISTINCT_COUNT = 'distinct_count'
- class MetricType(str, Enum):
- KPI = 'kpi'
- TREND = 'trend'
- DISTRIBUTION = 'distribution'
- RANKING = 'ranking'
- FUNNEL = 'funnel'
- ALERT = 'alert'
- class ChartType(str, Enum):
- COLUMN = 'column'
- BAR = 'bar'
- LINE = 'line'
- DOUGHNUT = 'doughnut'
- PIE = 'pie'
- FUNNEL = 'funnel'
- TABLE = 'table'
- GROUPED_BAR = 'grouped_bar'
- class ThemePreset(str, Enum):
- BUSINESS_CLASSIC = 'business_classic'
- FRESH_SIMPLE = 'fresh_simple'
- DARK_PROFESSIONAL = 'dark_professional'
- WARM_BRAND = 'warm_brand'
- CUSTOM = 'custom'
- FROM_TEMPLATE = 'from_template'
- @dataclass
- class ColumnProfile:
- column_name: str
- dtype: str
- role: ColumnRole
- null_count: int
- null_rate: float
- unique_count: int
- sample_values: list = field(default_factory=list)
- numeric_stats: Optional[dict] = None
- inferred_label: str = ''
- @dataclass
- class MetricDef:
- name: str
- label: str
- column: str
- aggregation: AggregationType
- metric_type: MetricType = MetricType.KPI
- unit: str = ''
- format_spec: str = ',.0f'
- selected: bool = True
- is_primary: bool = False
- @dataclass
- class PageDef:
- page_id: str
- title: str
- page_type: str
- order: int
- selected: bool = True
- elements: list[dict] = field(default_factory=list)
- conclusion_title: str = ''
- @dataclass
- class ConfirmationSpec:
- """Six user confirmations required before building a report."""
- period_and_page_range: bool = False
- core_metrics: bool = False
- audience_and_decision: bool = False
- visual_style_and_palette: bool = False
- page_structure_and_template: bool = False
- data_scope_and_field_mapping: bool = False
- def missing_items(self) -> list[str]:
- labels = {
- 'period_and_page_range': '报告周期与页数范围',
- 'core_metrics': '核心指标集',
- 'audience_and_decision': '受众与决策场景',
- 'visual_style_and_palette': '视觉风格与配色方向',
- 'page_structure_and_template': '页面结构与模板方案',
- 'data_scope_and_field_mapping': '数据范围与字段映射',
- }
- return [
- label for field_name, label in labels.items()
- if not getattr(self, field_name)
- ]
- def is_complete(self) -> bool:
- return not self.missing_items()
- @dataclass
- class ThemeConfig:
- preset: ThemePreset = ThemePreset.BUSINESS_CLASSIC
- name: str = '商务经典'
- primary: str = '#1E3A5F'
- accent: str = '#10B981'
- accent_neg: str = '#EF4444'
- secondary: str = '#64748B'
- dark: str = '#1F3A5C'
- white: str = '#FFFFFF'
- gray_bg: str = '#F2F2F2'
- card_bg: str = '#E7F0F7'
- text: str = '#333333'
- text_gray: str = '#666666'
- line: str = '#D9D9D9'
- chart_series: list[str] = field(default_factory=lambda: [
- '#1E3A5F', '#10B981', '#ED7D31', '#64748B',
- '#EF4444', '#707070', '#4472C4', '#10B981'
- ])
- title_font: str = '微软雅黑'
- body_font: str = '微软雅黑'
- number_font: str = 'Arial'
- @dataclass
- class ReportConfig:
- title: str
- period_type: PeriodType
- date_range: tuple[date, date]
- period_str: str = ''
- metrics: list[MetricDef] = field(default_factory=list)
- pages: list[PageDef] = field(default_factory=list)
- audience: AudienceType = AudienceType.MANAGEMENT
- decision_scenario: str = ''
- custom_audience: str = ''
- theme: ThemeConfig = field(default_factory=ThemeConfig)
- template_path: str = ''
- template_profile: Optional[object] = None # TemplateProfile from template_parser
- use_template_theme: bool = True
- visual_style_direction: str = ''
- page_structure_template: str = ''
- filters: dict = field(default_factory=dict)
- comparison: ComparisonType = ComparisonType.PREV_PERIOD
- page_count_range: tuple[int, int] = (6, 15)
- source_label: str = '数据报告系统'
- data_scope: str = ''
- data_field_mapping: dict = field(default_factory=dict)
- data_profiling: Optional[dict] = None
- agent_recommendations: Optional[dict] = None
- user_confirmation: ConfirmationSpec = field(default_factory=ConfirmationSpec)
- require_six_confirmations: bool = True
- quality_threshold: int = 85
- max_fix_iterations: int = 5
- def to_dict(self) -> dict:
- return {
- 'title': self.title,
- 'period_type': self.period_type.value,
- 'period_str': self.period_str,
- 'page_count_range': list(self.page_count_range),
- 'audience': self.audience.value,
- 'theme_preset': self.theme.preset.value,
- 'metrics_count': len(self.metrics),
- 'pages_count': len(self.pages),
- 'six_confirmations_complete': self.user_confirmation.is_complete(),
- }
- def validate_six_confirmations(config: ReportConfig, data_columns: Optional[list[str]] = None) -> list[str]:
- """Return validation gaps for the six confirmation contract."""
- issues = []
- missing = config.user_confirmation.missing_items()
- if missing:
- issues.append('六项确认未完成:' + '、'.join(missing))
- if not config.period_str and not config.date_range:
- issues.append('缺少报告周期。')
- if not config.page_count_range or len(config.page_count_range) != 2:
- issues.append('缺少页数范围。')
- if not [m for m in config.metrics if m.selected]:
- issues.append('缺少已确认的核心指标集。')
- if not config.decision_scenario:
- issues.append('缺少受众与决策场景说明。')
- if not config.visual_style_direction and not config.theme:
- issues.append('缺少视觉风格与配色方向。')
- if not config.pages:
- issues.append('缺少页面结构与模板方案。')
- if not config.data_field_mapping:
- issues.append('缺少数据范围与字段映射。')
- if data_columns:
- missing_cols = []
- for metric in config.metrics:
- if metric.selected and metric.column and metric.column not in data_columns:
- missing_cols.append(f'{metric.label} -> {metric.column}')
- if missing_cols:
- issues.append('核心指标字段映射不存在:' + '、'.join(missing_cols[:8]))
- selected_pages = [p for p in config.pages if p.selected]
- if config.page_count_range and selected_pages:
- low, high = config.page_count_range
- if len(selected_pages) < low - 1 or len(selected_pages) > high + 1:
- issues.append(f'页面数量 {len(selected_pages)} 不在确认范围 {low}-{high} 页附近。')
- return issues
|