Spaces:
Running
Running
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Optional | |
| from typing_extensions import TypedDict | |
| from backend.models.issue import Category, Issue | |
| # --------------------------------------------------------------------------- | |
| # Output types | |
| # --------------------------------------------------------------------------- | |
| class CategoryScore: | |
| category: Category | |
| score: float # 0–100 | |
| weight: float | |
| issues: list[Issue] = field(default_factory=list) | |
| def weighted_score(self) -> float: | |
| return self.score * self.weight | |
| class AnalysisResult: | |
| overall_score: float | |
| category_scores: list[CategoryScore] | |
| issues: list[Issue] # all issues, sorted by estimated_gain descending | |
| def top_issues(self) -> list[Issue]: | |
| return self.issues[:10] | |
| def quick_wins(self) -> list[Issue]: | |
| return [ | |
| i for i in self.issues | |
| if i.estimated_gain >= 1.0 and "minute" in i.estimated_time | |
| ] | |
| def high_impact(self) -> list[Issue]: | |
| """Issues with the largest estimated score gain (top 5).""" | |
| return [i for i in self.issues if i.estimated_gain >= 2.0][:5] | |
| def easy_fixes(self) -> list[Issue]: | |
| """Issues that can be resolved in minutes (all severities).""" | |
| return [i for i in self.issues if "minute" in i.estimated_time] | |
| def accessibility_fixes(self) -> list[Issue]: | |
| """All accessibility-category issues, highest gain first.""" | |
| return [i for i in self.issues if i.category == Category.ACCESSIBILITY] | |
| def visual_improvements(self) -> list[Issue]: | |
| """Visual hierarchy and typography issues.""" | |
| visual_cats = {Category.VISUAL_HIERARCHY, Category.TYPOGRAPHY} | |
| return [i for i in self.issues if i.category in visual_cats] | |
| def consistency_improvements(self) -> list[Issue]: | |
| """Consistency and spacing issues.""" | |
| return [i for i in self.issues if i.category == Category.CONSISTENCY] | |
| def roadmap(self) -> dict: | |
| """Structured roadmap grouping all issue buckets.""" | |
| return { | |
| "top_issues": self.top_issues, | |
| "quick_wins": self.quick_wins, | |
| "high_impact": self.high_impact, | |
| "easy_fixes": self.easy_fixes, | |
| "accessibility_fixes": self.accessibility_fixes, | |
| "visual_improvements": self.visual_improvements, | |
| "consistency_improvements": self.consistency_improvements, | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Input schema — the contract between parsers and the rule engine. | |
| # Rules consume these shapes; parsers must produce them. | |
| # --------------------------------------------------------------------------- | |
| class HeadingData(TypedDict): | |
| level: int # 1–6 | |
| font_size_px: float | |
| text: str | |
| class ButtonData(TypedDict): | |
| padding_top_px: float | |
| padding_right_px: float | |
| padding_bottom_px: float | |
| padding_left_px: float | |
| background_color: str # hex, e.g. "#007bff" | |
| color: str # hex, text colour | |
| border_radius_px: float | |
| has_focus_style: bool | |
| height_px: float # for touch-target check | |
| text: str | |
| class InputData(TypedDict): | |
| has_label: bool | |
| label_text: Optional[str] | |
| placeholder: Optional[str] | |
| padding_px: float | |
| has_focus_style: bool | |
| input_type: str # "text", "email", "password", ... | |
| class TableData(TypedDict): | |
| has_header: bool | |
| has_zebra_striping: bool | |
| cell_padding_px: float | |
| has_border: bool | |
| class CardData(TypedDict): | |
| padding_top_px: float | |
| padding_right_px: float | |
| padding_bottom_px: float | |
| padding_left_px: float | |
| class ColorPairData(TypedDict): | |
| foreground: str # hex | |
| background: str # hex | |
| font_size_px: float | |
| is_bold: bool | |
| context: str # "body", "button", "heading", "link" | |
| class ChartData(TypedDict): | |
| has_axis_labels: bool | |
| color_count: int | |
| colors: list[str] # hex colours used in the chart | |
| class ImageData(TypedDict): | |
| src: str | |
| has_alt: bool | |
| alt_text: str | |
| has_srcset: bool | |
| has_width: bool | |
| has_height: bool | |
| class ParsedPage(TypedDict): | |
| # Typography | |
| fonts: list[str] | |
| headings: list[HeadingData] | |
| body_font_size_px: Optional[float] | |
| line_height: Optional[float] # unitless ratio, e.g. 1.5 | |
| # Components | |
| buttons: list[ButtonData] | |
| inputs: list[InputData] | |
| tables: list[TableData] | |
| cards: list[CardData] | |
| charts: list[ChartData] | |
| # Colours | |
| text_color_pairs: list[ColorPairData] | |
| # Dashboard-level | |
| kpi_card_count: int | |
| whitespace_ratio: float # 0.0–1.0 estimate | |
| # Spacing inventory | |
| spacing_values_px: list[float] # all margin/padding values found | |
| # Mobile / responsive (content_signals) | |
| has_viewport_meta: bool | |
| has_horizontal_overflow: bool | |
| media_query_breakpoints: list[int] # e.g. [768, 1024] | |
| # Images (content_signals) | |
| images: list[ImageData] | |
| # Interactivity (content_signals) | |
| has_hover_styles: bool | |
| has_transitions: bool | |
| has_focus_outline_removed: bool | |
| has_skip_link: bool | |
| # Performance (content_signals) | |
| web_fonts: list[str] | |
| has_font_display: bool | |
| # Conversion / UX (content_signals) | |
| trust_signal_count: int | |
| has_testimonials: bool | |
| cta_texts: list[str] | |
| has_hero: bool | |
| hero_word_count: int | |
| has_error_states: bool | |
| has_empty_states: bool | |
| form_field_counts: list[int] | |
| page_word_count: int | |
| has_pricing_section: bool | |
| # Device context — set by analyzers; consumed by mobile_analyzer | |
| device_type: str # "mobile" | "desktop" | "auto" | |