Files
neo de18090304 🏗 Фаза 1-4: Архитектура, ядро, проверки, API, генератор отчетов
- scanner/engine.py — Playwright-движок сканирования
- scanner/models.py — Pydantic модели
- scanner/checks/ — 5 проверок (cookie, privacy, forms, server, external)
- api/server.py — FastAPI REST API
- reports/generator.py — PDF через WeasyPrint + Jinja2
- main.py — CLI + сервер

Проверки:
✅ cookie_banner — поиск cookie-баннера
✅ privacy_link — проверка ссылки на политику
✅ form_checkbox — проверка чекбоксов согласия
✅ server_location — геолокация сервера (RФ или нет)
✅ external_scripts — внешние скрипты аналитики
2026-06-09 21:10:21 +05:00

119 lines
5.7 KiB
Python

"""
Pydantic модели для 152-ФЗ Scanner.
Описывают структуры данных: результаты проверок, отчеты, сканирование.
"""
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Any, Optional
from pydantic import BaseModel, Field
class CheckStatus(str, Enum):
PASS = "pass" # ✅ Проверка пройдена
FAIL = "fail" # ❌ Проверка не пройдена
SKIP = "skip" # ⚠️ Проверка пропущена
ERROR = "error" # 🔴 Ошибка при проверке
class RiskLevel(str, Enum):
LOW = "low" # 🟢 Низкий риск
MEDIUM = "medium" # 🟡 Средний риск
HIGH = "high" # 🔴 Высокий риск
CRITICAL = "critical" # 🚫 Критический риск
class CheckResult(BaseModel):
"""Результат одной проверки."""
name: str # Имя проверки (например, "cookie_banner")
title: str # Название для отображения (например, "Cookie-баннер")
status: CheckStatus # Статус проверки
risk_level: RiskLevel = RiskLevel.LOW # Уровень риска при провале
message: str = "" # Описание результата
details: dict[str, Any] = {} # Детали для отладки/отчета
class SiteScan(BaseModel):
"""Результат сканирования одного сайта."""
url: str # URL сайта
scanned_at: datetime # Время сканирования
checks: list[CheckResult] = [] # Результаты всех проверок
score: int = 100 # Общий балл (0-100)
risks: list[CheckResult] = [] # Проваленные проверки (только fail)
critical_risks: list[CheckResult] = [] # Критические провалы
is_compliant: bool = True # Соответствует ли 152-ФЗ
summary: str = "" # Краткое резюме
def _calculate_score(self):
"""Вычисляет итоговый балл и статус соответствия."""
risk_weights = {
RiskLevel.LOW: 5,
RiskLevel.MEDIUM: 10,
RiskLevel.HIGH: 20,
RiskLevel.CRITICAL: 30,
}
score = 100
risks = []
critical_risks = []
for check in self.checks:
if check.status == CheckStatus.FAIL:
penalty = risk_weights.get(check.risk_level, 5)
score -= penalty
risks.append(check)
if check.risk_level == RiskLevel.CRITICAL:
critical_risks.append(check)
self.score = max(0, score)
self.risks = risks
self.critical_risks = critical_risks
self.is_compliant = self.score >= 70
# Формируем краткое резюме
fail_count = sum(1 for c in self.checks if c.status == CheckStatus.FAIL)
error_count = sum(1 for c in self.checks if c.status == CheckStatus.ERROR)
skip_count = sum(1 for c in self.checks if c.status == CheckStatus.SKIP)
if self.is_compliant:
emoji = "🟢" if self.score >= 90 else "🟡"
self.summary = f"{emoji} Сайт частично соответствует 152-ФЗ (балл: {self.score}/100). Найдено нарушений: {fail_count}."
else:
emoji = "🔴" if self.critical_risks else "🟠"
self.summary = f"{emoji} Сайт НЕ соответствует 152-ФЗ (балл: {self.score}/100). Критических нарушений: {len(self.critical_risks)}. Требуется доработка."
return self
class ScanRequest(BaseModel):
"""Запрос на сканирование (через API)."""
url: str = Field(..., description="URL сайта для сканирования")
checks: Optional[list[str]] = None # Какие проверки запустить (все если None)
class CSVScanRequest(BaseModel):
"""Запрос на сканирование CSV-файла."""
urls: list[str] = Field(..., description="Список URL для сканирования")
class ScanResponse(BaseModel):
"""Ответ API — результат сканирования."""
scan_id: str # Уникальный ID сканирования
status: str # pending / completed / error
url: Optional[str] = None # URL (если одно)
urls_count: Optional[int] = None # Количество URL (если CSV)
results: Optional[list[SiteScan]] = None # Результаты
error: Optional[str] = None # Ошибка (если есть)
created_at: datetime # Время создания запроса
class PDFReport(BaseModel):
"""Данные для генерации PDF-отчета."""
site_scan: SiteScan # Результаты сканирования
generated_at: datetime # Время генерации
company_name: Optional[str] = None # Название компании (если известно)
contact_email: Optional[str] = None # Email для связи
scanner_version: str = "1.0.0" # Версия сканера