"""Draw Jev correctness overlays and review-routing summaries.""" from __future__ import annotations from dataclasses import dataclass from typing import Sequence from PIL import Image, ImageDraw, ImageFont @dataclass class ScoredRegion: index: int label: str category: str box: tuple[int, int, int, int] text: str formatted: str score: float | None verdict: str detail: str = "" def _font(size: int = 16) -> ImageFont.ImageFont: for name in ( "DejaVuSans-Bold.ttf", "Arial.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "C:\\Windows\\Fonts\\arialbd.ttf", "C:\\Windows\\Fonts\\arial.ttf", ): try: return ImageFont.truetype(name, size=size) except OSError: continue return ImageFont.load_default() def route(score: float | None, verdict: str, threshold: float) -> str: if verdict != "scored" or score is None: return "error" if score >= threshold: return "accept" if score >= max(0.0, threshold - 0.15): return "borderline" return "review" def route_color(route_name: str) -> tuple[int, int, int]: return { "accept": (22, 163, 74), "borderline": (202, 138, 4), "review": (220, 38, 38), "error": (107, 114, 128), "skip": (156, 163, 175), }.get(route_name, (107, 114, 128)) def draw_overlay( page: Image.Image, regions: Sequence[ScoredRegion], threshold: float, ) -> Image.Image: canvas = page.convert("RGB").copy() draw = ImageDraw.Draw(canvas, "RGBA") label_font = _font(15) for region in regions: x1, y1, x2, y2 = region.box if region.score is None and region.verdict == "skipped": color = route_color("skip") tag = "skip" else: name = route(region.score, region.verdict, threshold) color = route_color(name) if region.verdict == "scored" and region.score is not None: tag = f"{region.score:.2f}" else: tag = region.verdict outline = (*color, 230) fill = (*color, 36) draw.rectangle([x1, y1, x2, y2], outline=outline, width=3, fill=fill) badge = f"#{region.index} {tag}" tw = max(48, int(draw.textlength(badge, font=label_font)) + 10) th = 20 by1 = max(0, y1 - th - 2) draw.rectangle([x1, by1, x1 + tw, by1 + th], fill=(*color, 230)) draw.text((x1 + 5, by1 + 2), badge, fill=(255, 255, 255), font=label_font) return canvas def summary_html(regions: Sequence[ScoredRegion], threshold: float) -> str: scored = [r for r in regions if r.verdict == "scored" and r.score is not None] accepted = [r for r in scored if r.score >= threshold] review = [r for r in scored if r.score < threshold] errors = [r for r in regions if r.verdict not in {"scored", "skipped"}] skipped = [r for r in regions if r.verdict == "skipped"] avg = sum(r.score for r in scored) / len(scored) if scored else 0.0 return f"""