""" radar_svg_gen.py — серверная генерация SVG паутинной диаграммы. Используется при сохранении результата: файл пишется в APP_PATH/data/radar/. Не требует сторонних зависимостей. """ import math import os import base64 def generate_radar_svg(items: list[dict], max_val: float = 14.0, size: int = 520) -> str: """ Возвращает строку SVG паутинной диаграммы. items — список осей: [{dimension_name, dimension_color, value}, ...] max_val — максимум шкалы (14 для теста Голланда) size — размер холста в пикселях """ n = len(items) if n < 3: return ( f'' f'' f'Нет данных' ) cx = cy = size / 2 R = size * 0.27 lbl_r = R + size * 0.13 safe_max = max_val if max_val > 0 else 1.0 def axis_xy(i: int, frac: float = 1.0) -> tuple[float, float]: a = math.radians(-90.0 + 360.0 * i / n) return cx + R * frac * math.cos(a), cy + R * frac * math.sin(a) def poly_pts(pts: list[tuple]) -> str: return " ".join(f"{x:.2f},{y:.2f}" for x, y in pts) lines: list[str] = [ f'', f'', ] # Концентрические сетки for lvl in range(1, 6): pts = [axis_xy(i, lvl / 5) for i in range(n)] lines.append( f'' ) # Оси от центра for i in range(n): x, y = axis_xy(i) lines.append( f'' ) # Полигон данных data_pts = [axis_xy(i, min(it["value"] / safe_max, 1.0)) for i, it in enumerate(items)] lines.append( f'' ) # Точки на вершинах for x, y in data_pts: lines.append(f'') # Подписи for i, item in enumerate(items): a = math.radians(-90.0 + 360.0 * i / n) lx = cx + lbl_r * math.cos(a) ly = cy + lbl_r * math.sin(a) col = item.get("dimension_color") or "#F4EFFA" name = _xml_escape(item["dimension_name"]) val = int(item["value"]) lines.append( f'{name}' ) lines.append( f'{val}' ) lines.append("") return "\n".join(lines) def _xml_escape(s: str) -> str: return s.replace("&", "&").replace("<", "<").replace(">", ">") def save_radar_svg(result_id: str, items: list[dict], max_val: float = 14.0) -> str: """ Генерирует SVG и сохраняет его на диск. Возвращает относительный путь к файлу (относительно APP_PATH). Файл пишется в: {APP_PATH}/data/radar/{result_id}.svg Возвращает: data/radar/{result_id}.svg """ app_path = os.getenv("APP_PATH", "/app") radar_dir = os.path.join(app_path, "data", "radar") os.makedirs(radar_dir, exist_ok=True) filename = f"{result_id}.svg" full_path = os.path.join(radar_dir, filename) svg = generate_radar_svg(items, max_val) with open(full_path, "w", encoding="utf-8") as f: f.write(svg) return f"data/radar/{filename}"